Skip to content

Commit 7cfe6c4

Browse files
Lenar Fatikhovmeta-codesync[bot]
authored andcommitted
fix BigValueRoute sscanf over-read on non-NUL-terminated reply
Summary: Root cause: BigValueRoute<RouterInfo>::ChunksInfo(folly::StringPiece replyValue) parsed the chunks-info header by calling sscanf(replyValue.data(), "%u-%u-%lu%n", ...). The StringPiece is a (ptr, len) view over a coalesced IOBuf reply payload and is NOT NUL-terminated. glibc sscanf computes the input C-string length up front by scanning for a NUL with rawmemchr (via _IO_str_init_static_internal called with size=-1), BEFORE any parsing happens. When the byte after the payload is not a NUL, that scan reads past the buffer; if the payload abuts an unmapped page the read SIGSEGVs. The existing charsRead == replyValue.size() guard cannot prevent it because it runs only after the faulting sscanf call returns. Evidence: confirmed in two production CachiusServer coredumps on mcrpxy-web* threads. In the non-marker core the rawmemchr fault register was rdi=0x7f8744dfffe0, exactly 0x20 below the page boundary 0x7f8744e00000, with rsi=0 (scanning for NUL) - i.e. a length-scan running off the end of a mapped page. Both cores had a valid v1 ChunksInfo input ("1-2-7419256", "1-2-3745222203") followed by adjacent garbage, so the crash requires no malformed or attacker-controlled data. This accounts for ~480 SIGSEGV/week (~19% of CachiusServer crashes); it is steady and version-independent. (It is distinct from the larger CachiusCPUPool SIGABRT population, which is unrelated to mcrouter.) Fix: parse strictly within the StringPiece bounds. Extracted a bounded detail::parseChunksInfo() helper using folly::split('-', ...) (exact 3 fields) + folly::tryTo<uint32_t/uint32_t/uint64_t>, which never reads past replyValue. Semantics are preserved (and slightly stricter on malformed input: leading '+', overflow, and negative fields are now rejected - none reachable from the toStringType() producer, which emits pure "{}-{}-{}" digits). numChunks_/suffix_ are now zero-initialized. Bumps ClientVersion 182 -> 183. Reviewed By: disylh Differential Revision: D107896217 fbshipit-source-id: 77e80ddc660d66e46b0f0bbc66ec2028aec261cc
1 parent dbb0ad7 commit 7cfe6c4

2 files changed

Lines changed: 57 additions & 14 deletions

File tree

‎mcrouter/routes/BigValueRoute-inl.h‎

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
#include <fmt/format.h>
1414

15+
#include <folly/Conv.h>
1516
#include <folly/Range.h>
17+
#include <folly/String.h>
1618
#include <folly/fibers/FiberManager.h>
1719
#include <folly/fibers/WhenN.h>
1820
#include <folly/io/Cursor.h>
@@ -52,6 +54,30 @@ uint64_t hashBigValue(const folly::IOBuf& value);
5254

5355
BigValueRouteOptions parseBigValueRouteSettings(const folly::dynamic& json);
5456

57+
// Parses a chunks-info string of the form "version-numChunks-suffix".
58+
// replyValue is not NUL-terminated (it views into an IOBuf payload), so it must
59+
// never be passed to sscanf, which scans for a NUL and reads past the buffer.
60+
inline bool parseChunksInfo(
61+
folly::StringPiece replyValue,
62+
uint32_t& version,
63+
uint32_t& numChunks,
64+
uint64_t& suffix) {
65+
folly::StringPiece versionStr, numChunksStr, suffixStr;
66+
if (!folly::split('-', replyValue, versionStr, numChunksStr, suffixStr)) {
67+
return false;
68+
}
69+
auto v = folly::tryTo<uint32_t>(versionStr);
70+
auto n = folly::tryTo<uint32_t>(numChunksStr);
71+
auto s = folly::tryTo<uint64_t>(suffixStr);
72+
if (!v || !n || !s) {
73+
return false;
74+
}
75+
version = *v;
76+
numChunks = *n;
77+
suffix = *s;
78+
return true;
79+
}
80+
5581
} // namespace detail
5682

5783
template <class RouterInfo>
@@ -383,20 +409,9 @@ McLeaseGetReply BigValueRoute<RouterInfo>::doLeaseGetRoute(
383409

384410
template <class RouterInfo>
385411
BigValueRoute<RouterInfo>::ChunksInfo::ChunksInfo(folly::StringPiece replyValue)
386-
: infoVersion_(1), valid_(true) {
387-
// Verify that replyValue is of the form version-numChunks-suffix,
388-
// where version, numChunks and suffix should be numeric
389-
uint32_t version;
390-
int charsRead;
391-
valid_ &=
392-
(sscanf(
393-
replyValue.data(),
394-
"%u-%u-%lu%n",
395-
&version,
396-
&numChunks_,
397-
&suffix_,
398-
&charsRead) == 3);
399-
valid_ &= (static_cast<size_t>(charsRead) == replyValue.size());
412+
: infoVersion_(1), numChunks_(0), suffix_(0), valid_(true) {
413+
uint32_t version = 0;
414+
valid_ &= detail::parseChunksInfo(replyValue, version, numChunks_, suffix_);
400415
valid_ &= (version == infoVersion_);
401416
}
402417

‎mcrouter/routes/test/BigValueRouteTest.cpp‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,31 @@ TEST(BigValueRouteTest, bigvalue) {
2626
facebook::memcache::mcrouter::testBigvalue<
2727
facebook::memcache::MemcacheRouterInfo>();
2828
}
29+
30+
TEST(BigValueRouteTest, chunksInfoParsing) {
31+
namespace mc = facebook::memcache::mcrouter;
32+
uint32_t version = 0, numChunks = 0;
33+
uint64_t suffix = 0;
34+
35+
EXPECT_TRUE(
36+
mc::detail::parseChunksInfo("1-2-7419256", version, numChunks, suffix));
37+
EXPECT_EQ(1, version);
38+
EXPECT_EQ(2, numChunks);
39+
EXPECT_EQ(7419256, suffix);
40+
41+
EXPECT_FALSE(mc::detail::parseChunksInfo("1-2", version, numChunks, suffix));
42+
EXPECT_FALSE(
43+
mc::detail::parseChunksInfo("1-2-3-4", version, numChunks, suffix));
44+
EXPECT_FALSE(
45+
mc::detail::parseChunksInfo("1-2-x", version, numChunks, suffix));
46+
EXPECT_FALSE(mc::detail::parseChunksInfo("", version, numChunks, suffix));
47+
48+
// Regression: must stay within the StringPiece bounds and never read trailing
49+
// bytes. The crash was sscanf scanning a non-NUL-terminated buffer past its
50+
// end.
51+
folly::StringPiece backing("1-2-3456789");
52+
EXPECT_TRUE(
53+
mc::detail::parseChunksInfo(
54+
backing.subpiece(0, 7), version, numChunks, suffix));
55+
EXPECT_EQ(345, suffix);
56+
}

0 commit comments

Comments
 (0)