Skip to content

Commit d6ab87f

Browse files
generatedunixname1383054420177565meta-codesync[bot]
authored andcommitted
Fix integer overflow in McAsciiParser uint action (T267172046)
Reviewed By: alikhtarov Differential Revision: D111235612 fbshipit-source-id: 5a99db12bc80c5443a127b6c014687fc0b0d55f4
1 parent d3de770 commit d6ab87f

3 files changed

Lines changed: 54 additions & 0 deletions

File tree

mcrouter/lib/network/McAsciiParser.rl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "mcrouter/lib/network/McAsciiParser.h"
99

10+
#include <limits>
11+
1012
#include "mcrouter/lib/mc/msg.h"
1113
#include "mcrouter/lib/network/gen/MemcacheMessages.h"
1214
#include "mcrouter/lib/network/gen/MemcacheRoutingGroups.h"
@@ -109,6 +111,18 @@ multi_token = (print+ -- ( '\r' | '\n' )) >key_start %key_end %{
109111

110112
# Unsigned integer value.
111113
uint = digit+ > { currentUInt_ = 0; } ${
114+
// `digit+` is unbounded; without this guard the accumulator wraps modulo
115+
// 2^64 and a 20+-digit field decodes to an attacker-chosen value (e.g. a
116+
// value_bytes count that then slips past the maxValueBytes check).
117+
if (FOLLY_UNLIKELY(
118+
currentUInt_ >
119+
(std::numeric_limits<uint64_t>::max() -
120+
static_cast<uint64_t>(fc - '0')) /
121+
10)) {
122+
state_ = State::ERROR;
123+
currentErrorDescription_ = "Integer field overflows uint64.";
124+
fbreak;
125+
}
112126
currentUInt_ = currentUInt_ * 10 + (fc - '0');
113127
};
114128

@@ -1245,6 +1259,14 @@ McAsciiParserBase::State McServerAsciiParser::consume(folly::IOBuf& buffer) {
12451259
appendKeyPiece(buffer, currentKey_, keyPieceStart_, p_);
12461260
}
12471261

1262+
// A parser action may set State::ERROR directly (e.g. integer-field
1263+
// overflow) on grammatically valid input, without driving the machine to
1264+
// errorCs_. Honor it here so the request is rejected now, before a later
1265+
// finishReq() resets state_ and the truncated request is dispatched.
1266+
if (state_ == State::ERROR) {
1267+
break;
1268+
}
1269+
12481270
if (savedCs_ == errorCs_) {
12491271
handleError(buffer);
12501272
break;

mcrouter/lib/network/test/McAsciiParserTest.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,25 @@ TYPED_TEST(McAsciiParserTestGet, GetHit_Error) {
270270
h.runTest(1);
271271
}
272272

273+
// A value-size field of exactly 2^64 overflows the uint64 accumulator. Before
274+
// the overflow guard it wrapped to 0 and the reply parsed as an empty value;
275+
// the parser must now reject it as a protocol error.
276+
TYPED_TEST(McAsciiParserTestGet, GetHit_ValueBytesOverflow_Error) {
277+
McAsciiParserHarness h("VALUE test 10 18446744073709551616\r\n\r\nEND\r\n");
278+
h.expectNext<TypeParam>(ReplyT<TypeParam>(), true);
279+
h.runTest(1);
280+
}
281+
282+
// The largest in-range uint64 (2^64 - 1) must still parse, confirming the
283+
// overflow guard rejects only genuine overflow and not the boundary value.
284+
TYPED_TEST(McAsciiParserTestGet, GetHit_MaxUint64Flags) {
285+
McAsciiParserHarness h("VALUE test 18446744073709551615 2\r\nte\r\nEND\r\n");
286+
h.expectNext<TypeParam>(setFlags(
287+
setValue(ReplyT<TypeParam>(carbon::Result::FOUND), "te"),
288+
18446744073709551615ULL));
289+
h.runTest(1);
290+
}
291+
273292
TYPED_TEST(McAsciiParserTestGet, GetMiss) {
274293
McAsciiParserHarness h("END\r\n");
275294
h.expectNext<TypeParam>(ReplyT<TypeParam>(carbon::Result::NOTFOUND));

mcrouter/lib/network/test/McServerAsciiParserTest.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,14 @@ void setLikeTest(std::string opCmd) {
406406
strlen(kTestValue),
407407
kTestValue);
408408

409+
// A value-size field that overflows uint64 must be rejected rather than
410+
// wrapping to a small size and truncating the value (which would let the
411+
// trailing bytes be reparsed as a second, smuggled command).
412+
TestRunner().expectError().run(
413+
"{} test:stepan:1 123 651342 18446744073709551616\r\n{}\r\n",
414+
opCmd,
415+
kTestValue);
416+
409417
// Test noreply.
410418
TestRunner()
411419
.expectNext(
@@ -478,6 +486,11 @@ void arithmeticTest(std::string opCmd) {
478486
.run(opCmd + " test:stepan:1 noreply\r\n")
479487
.run(opCmd + " test:stepan:1 noreply \r\n")
480488
.run(opCmd + " test:stepan:1 noreply \r\n");
489+
490+
// A delta that overflows uint64 must be rejected, not silently truncated to
491+
// a wrapped value and dispatched.
492+
TestRunner().expectError().run(
493+
opCmd + " test:stepan:1 18446744073709551616\r\n");
481494
}
482495

483496
} // namespace

0 commit comments

Comments
 (0)