Skip to content

Commit 54316eb

Browse files
committed
Fix[mqb]: improve non-printable handling in negotiation/session logs
Restrict negotiation messages (and derived fields) to printable characters in logs. Additionally, add a BSLS_REVIEW check for negotiation messages containing non-printable characters in anticipation of enforcing that string fields are limited to printable ASCII characters. Signed-off-by: Christopher Beard <cbeard9@bloomberg.net>
1 parent 6cd9b20 commit 54316eb

6 files changed

Lines changed: 141 additions & 10 deletions

File tree

src/groups/bmq/bmqp/bmqp_ctrlmsg.xsd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,8 @@
16741674
userAgent.........: a string identifying the agent the client is using
16751675
to connect. Should include the SDK name/language
16761676
and version.
1677+
1678+
String fields must only contain printable ASCII characters.
16771679
</documentation>
16781680
</annotation>
16791681
<sequence>

src/groups/bmq/bmqp/bmqp_ctrlmsg_messages.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/groups/bmq/bmqu/bmqu_stringutil.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717

1818
#include <bmqscm_version.h>
1919
// BDE
20+
#include <bdlb_chartype.h>
2021
#include <bsl_algorithm.h>
2122
#include <bsl_bitset.h>
2223
#include <bsl_cctype.h>
2324
#include <bsl_climits.h>
2425
#include <bsl_functional.h>
2526
#include <bsls_assert.h>
27+
#include <bsls_performancehint.h>
2628

2729
namespace BloombergLP {
2830
namespace bmqu {
@@ -117,6 +119,20 @@ bool StringUtil::endsWith(const bslstl::StringRef& str,
117119
return true;
118120
}
119121

122+
bool StringUtil::isPrintable(const bslstl::StringRef& str)
123+
{
124+
for (bslstl::StringRef::const_iterator it = str.begin(); it != str.end();
125+
++it) {
126+
if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(
127+
!bdlb::CharType::isPrint(*it))) {
128+
BSLS_PERFORMANCEHINT_UNLIKELY_HINT;
129+
return false; // RETURN
130+
}
131+
}
132+
133+
return true;
134+
}
135+
120136
bsl::string& StringUtil::trim(bsl::string* str)
121137
{
122138
return ltrim(&rtrim(str));

src/groups/bmq/bmqu/bmqu_stringutil.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ struct StringUtil {
5757
static bool endsWith(const bslstl::StringRef& str,
5858
const bslstl::StringRef& suffix);
5959

60+
/// Return `true` if every character in the specified string `str` is
61+
/// printable, and `false` otherwise. An empty string is printable.
62+
static bool isPrintable(const bslstl::StringRef& str);
63+
6064
/// Perform an in-place white spaces trimming at the beginning and the
6165
/// end of the specified string `str` and return a reference offering
6266
/// modifiable access to it.

src/groups/bmq/bmqu/bmqu_stringutil.t.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,60 @@ static void test9_squeeze()
524524
}
525525
}
526526

527+
static void test10_isPrintable()
528+
// ------------------------------------------------------------------------
529+
// bmqu::StringUtil::isPrintable
530+
//
531+
// Concerns:
532+
// Ensure proper behavior of the 'isPrintable' method.
533+
//
534+
// Plan:
535+
// Test printable strings and the empty string, plus strings containing
536+
// various non-printable bytes (control characters, DEL, and high-bit
537+
// bytes), including one with an embedded NUL.
538+
//
539+
// Testing:
540+
// Proper behavior of the 'isPrintable(str)' method.
541+
// ------------------------------------------------------------------------
542+
{
543+
bmqtst::TestHelper::printTestName("isPrintable");
544+
545+
struct Test {
546+
int d_line;
547+
const char* d_str;
548+
bool d_result;
549+
} k_DATA[] = {{L_, "", true},
550+
{L_, "hello world", true},
551+
{L_, " ", true}, // 0x20, first printable
552+
{L_, "~", true}, // 0x7e, last printable
553+
{L_, "abc\ndef", false},
554+
{L_, "abc\tdef", false},
555+
{L_, "\r", false},
556+
{L_, "\x1f", false}, // just below first printable
557+
{L_, "\x7f", false}, // DEL
558+
{L_, "\x80", false}, // high-bit byte
559+
{L_, "\xff", false}}; // high-bit byte
560+
561+
const size_t k_NUM_DATA = sizeof(k_DATA) / sizeof(*k_DATA);
562+
563+
for (size_t idx = 0; idx < k_NUM_DATA; ++idx) {
564+
const Test& test = k_DATA[idx];
565+
566+
PVV(test.d_line << ": checking printability of line");
567+
BMQTST_ASSERT_EQ_D("line " << test.d_line,
568+
bmqu::StringUtil::isPrintable(test.d_str),
569+
test.d_result);
570+
}
571+
572+
// A NUL embedded mid-string must be detected, so pass an explicit length
573+
// rather than relying on NUL-termination.
574+
const char k_EMBEDDED_NUL[] = {'a', '\0', 'b'};
575+
BMQTST_ASSERT_EQ(
576+
bmqu::StringUtil::isPrintable(
577+
bslstl::StringRef(k_EMBEDDED_NUL, sizeof(k_EMBEDDED_NUL))),
578+
false);
579+
}
580+
527581
// ============================================================================
528582
// MAIN PROGRAM
529583
// ----------------------------------------------------------------------------
@@ -534,6 +588,7 @@ int main(int argc, char* argv[])
534588

535589
switch (_testCase) {
536590
case 0:
591+
case 10: test10_isPrintable(); break;
537592
case 9: test9_squeeze(); break;
538593
case 8: test8_match(); break;
539594
case 7: test7_strTokenizeRef(); break;

src/groups/mqb/mqba/mqba_sessionnegotiator.cpp

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@
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>
@@ -88,6 +90,8 @@
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.
212257
void 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

Comments
 (0)