7373#include < bmqst_statcontext.h>
7474#include < bmqu_blob.h>
7575#include < bmqu_memoutstream.h>
76+ #include < bmqu_stringutil.h>
7677#include < bmqu_time.h>
7778
7879// BDE
7980#include < ball_log.h>
81+ #include < bdlb_chartype.h>
8082#include < bdlf_bind.h>
8183#include < bdlf_placeholder.h>
8284#include < bdlma_localsequentialallocator.h>
8890#include < bslma_managedptr.h>
8991#include < bsls_assert.h>
9092#include < bsls_atomic.h>
93+ #include < bsls_performancehint.h>
94+ #include < bsls_review.h>
9195#include < bsls_timeinterval.h>
9296
9397// NTC
@@ -196,19 +200,60 @@ void loadBrokerIdentity(bmqp_ctrlmsg::ClientIdentity* identity,
196200 identity->clusterNodeId () = nodeId;
197201}
198202
203+ // / Replace every non-printable character in `*str` with `?`, in place.
204+ void sanitize (bsl::string* str)
205+ {
206+ // PRECONDITIONS
207+ BSLS_ASSERT (str);
208+
209+ for (bsl::string::iterator it = str->begin (); it != str->end (); ++it) {
210+ if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY (
211+ !bdlb::CharType::isPrint (*it))) {
212+ BSLS_PERFORMANCEHINT_UNLIKELY_HINT ;
213+ *it = ' ?' ;
214+ }
215+ }
216+ }
217+
218+ // / Return the streamed representation of `obj`, safe to log (non-printable
219+ // / characters replaced with `?`).
220+ template <class TYPE >
221+ bsl::string logSafe (const TYPE & obj)
222+ {
223+ bmqu::MemOutStream os;
224+ os << obj;
225+
226+ bsl::string result (os.str ().data (), os.str ().length ());
227+ sanitize (&result);
228+ return result;
229+ }
230+
231+ // / Return `true` if the streamed representation of `obj` contains only
232+ // / printable characters, and `false` otherwise.
233+ template <class TYPE >
234+ bool isPrintable (const TYPE & obj)
235+ {
236+ bmqu::MemOutStream os;
237+ os << obj;
238+
239+ return bmqu::StringUtil::isPrintable (os.str ());
240+ }
241+
199242// / Load in the specified `out` the short description representing the
200243// / specified `identity` from the specified `peerChannel`. The format is as
201244// / follow:
202245// / tskName:pid.sessionId[\@hostId]
203246// / Where:
204- // / - tskName : the task name, without any optional leading path
247+ // / - tskName : the process/ task name, without any optional leading path
205248// / - pid : the pid of the task
206249// / - sessionId : the sessionId, omitted if 1
207250// / - hostId : the identity of the host where the peer is running
208251// / (omitted if local), note that the format is either
209252// / `ip:port`, or `ip~resolvedHostname:port` depending on
210253// / whether the async DNS resolution already took place or
211254// / not.
255+ // /
256+ // / Note: the result is sanitized as it derives from untrusted input.
212257void loadSessionDescription (bsl::string* out,
213258 const bmqp_ctrlmsg::ClientIdentity& identity,
214259 const bmqio::Channel& peerChannel)
@@ -246,6 +291,7 @@ void loadSessionDescription(bsl::string* out,
246291 }
247292
248293 out->assign (os.str ().data (), os.str ().length ());
294+ sanitize (out);
249295}
250296} // close unnamed namespace
251297
@@ -275,6 +321,10 @@ int SessionNegotiator::createSessionOnMsgType(
275321 const NegotiationContextSp& negotiationContext =
276322 context_p->negotiationContext ();
277323
324+ // Detect non-printable characters in negotiation messages.
325+ // TODO: fail negotiation if message contains non-printable characters
326+ BSLS_REVIEW_OPT (isPrintable (negotiationContext->negotiationMessage ()));
327+
278328 switch (negotiationContext->negotiationMessage ().selectionId ()) {
279329 case bmqp_ctrlmsg::NegotiationMessage::SELECTION_INDEX_CLIENT_IDENTITY : {
280330 // This is the first message of the negotiation protocol; can either
@@ -371,7 +421,8 @@ bsl::shared_ptr<mqbnet::Session> SessionNegotiator::onClientIdentityMessage(
371421 negotiationMessage.clientIdentity ();
372422
373423 BALL_LOG_INFO << " Handle negotiation message received from '"
374- << context_p->channel ().get () << " ': " << clientIdentity;
424+ << context_p->channel ().get ()
425+ << " ': " << logSafe (clientIdentity);
375426
376427 bsl::shared_ptr<mqbnet::Session> session;
377428
@@ -389,7 +440,7 @@ bsl::shared_ptr<mqbnet::Session> SessionNegotiator::onClientIdentityMessage(
389440 case bmqp_ctrlmsg::ClientType::E_UNKNOWN :
390441 default : {
391442 errorDescription << " Unknown ClientIdentity client type: "
392- << clientIdentity;
443+ << logSafe ( clientIdentity) ;
393444 return session; // RETURN
394445 }
395446 }
@@ -442,9 +493,10 @@ bsl::shared_ptr<mqbnet::Session> SessionNegotiator::onClientIdentityMessage(
442493 // but we are not member of that cluster; emit an error (but
443494 // still accept the connection).
444495 BALL_LOG_ERROR << " #CONNECTION_UNEXPECTED Client '"
445- << clientIdentity
496+ << logSafe ( clientIdentity)
446497 << " ' connected to me as part of cluster '"
447- << clusterName << " ' to which I do not belong!" ;
498+ << logSafe (clusterName)
499+ << " ' to which I do not belong!" ;
448500 }
449501 // Virtual clusters do not advertise node status. Therefore, the
450502 // identity should not advertise k_BROADCAST_TO_PROXIES feature.
@@ -537,14 +589,15 @@ bsl::shared_ptr<mqbnet::Session> SessionNegotiator::onBrokerResponseMessage(
537589 negotiationMessage.brokerResponse ();
538590
539591 BALL_LOG_DEBUG << " Received negotiation message from '"
540- << context_p->channel ().get () << " ': " << brokerResponse;
592+ << context_p->channel ().get ()
593+ << " ': " << logSafe (brokerResponse);
541594
542595 bsl::shared_ptr<mqbnet::Session> session;
543596
544597 if (brokerResponse.result ().category () !=
545598 bmqp_ctrlmsg::StatusCategory::E_SUCCESS ) {
546- errorDescription << " Failure broker's response [" << brokerResponse
547- << " ]" ;
599+ errorDescription << " Failure broker's response ["
600+ << logSafe (brokerResponse) << " ]" ;
548601 return session; // RETURN
549602 }
550603
@@ -830,7 +883,7 @@ bool SessionNegotiator::checkIsDeprecatedSdkVersion(
830883 // keep a central location of all deprecated clients.
831884 BALL_LOG_WARN << " #CLIENT_SDKVERSION_DEPRECATED "
832885 << " Client is using a deprecated SDK: "
833- << " [client: " << clientIdentity
886+ << " [client: " << logSafe ( clientIdentity)
834887 << " , minimumSDKVersionRecommended: "
835888 << mqbu::SDKVersionUtil::minSdkVersionRecommended (
836889 clientIdentity.sdkLanguage ())
@@ -858,7 +911,7 @@ bool SessionNegotiator::checkIsUnsupportedSdkVersion(
858911 // keep a central location of all rejected clients.
859912 BALL_LOG_WARN << " #CLIENT_SDKVERSION_UNSUPPORTED "
860913 << " Client is using an unsupported SDK: "
861- << " [client: " << clientIdentity
914+ << " [client: " << logSafe ( clientIdentity)
862915 << " , minimumSDKVersionSupported: "
863916 << mqbu::SDKVersionUtil::minSdkVersionSupported (
864917 clientIdentity.sdkLanguage ())
0 commit comments