This is an automated email from the ASF dual-hosted git repository.
wasphin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new 541686634 reject microseconds above 999999 in mysql
Field::ParseMicrosecs (#3717)
541686634 is described below
commit 5416866347efb0b5040f846f3b4bb3f792a86e51
Author: sahvx655-wq <[email protected]>
AuthorDate: Sun Nov 8 16:04:45 2026 +0530
reject microseconds above 999999 in mysql Field::ParseMicrosecs (#3717)
ParseMicrosecs splits the 4-byte microseconds field of a binary
DATETIME/TIMESTAMP or TIME value into three uint8_t groups and uses them
to index the 101-byte digits10/digits01 literals. The other components
are range-checked before the same lookups, but the microseconds value
was not, so a server sending e.g. 1010000 made the parser read past the
end of digits10 and copy those bytes into the field string.
Reject values above 999999 right after reading them, which covers both
ParseBinaryDataTime and ParseBinaryTime.
---
src/brpc/policy/mysql/mysql_reply.cpp | 8 +++-
test/brpc_mysql_reply_parse_unittest.cpp | 65 ++++++++++++++++++++++++++++++++
2 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/src/brpc/policy/mysql/mysql_reply.cpp
b/src/brpc/policy/mysql/mysql_reply.cpp
index bf39e1e30..0ced0e33c 100644
--- a/src/brpc/policy/mysql/mysql_reply.cpp
+++ b/src/brpc/policy/mysql/mysql_reply.cpp
@@ -1408,10 +1408,16 @@ ParseError
MysqlReply::Field::ParseMicrosecs(butil::IOBuf& buf, uint8_t decimal,
// only invokes this when the value length includes them); format them only
// when the column declares 1..6 fractional digits (0 / 0x1f == no
fraction).
MY_PARSE_CHECK(parse_fixed(buf, µsecs, 4));
+ microsecs = mysql_uint4korr((uint8_t*)µsecs);
+ // MySQL never sends more than 999999. A larger value would split into
+ // p1/p2/p3 above 99 and index past the digits01/digits10 tables.
+ if (microsecs > 999999) {
+ LOG(ERROR) << "invalid microseconds value " << microsecs;
+ return PARSE_ERROR_ABSOLUTELY_WRONG;
+ }
if (decimal == 0 || decimal > 6) {
return PARSE_OK;
}
- microsecs = mysql_uint4korr((uint8_t*)µsecs);
p1 = microsecs / 10000;
microsecs -= 10000 * p1;
p2 = microsecs / 100;
diff --git a/test/brpc_mysql_reply_parse_unittest.cpp
b/test/brpc_mysql_reply_parse_unittest.cpp
index 629ae12b6..dd71d4744 100644
--- a/test/brpc_mysql_reply_parse_unittest.cpp
+++ b/test/brpc_mysql_reply_parse_unittest.cpp
@@ -813,4 +813,69 @@ TEST(MysqlReplyParseTest,
RejectTruncatedBinaryTimeAndDatetime) {
}
}
+// A binary DATETIME (len 11) or TIME (len 12) value with its 4-byte
+// microseconds field appended, in little-endian order.
+std::string MakeBinaryTemporalRow(uint8_t type, uint32_t micros) {
+ std::string row;
+ row.push_back('\x00'); // binary row marker
+ row.push_back('\x00'); // NULL bitmap
+ if (type == brpc::MYSQL_FIELD_TYPE_TIME) {
+ row.push_back('\x0c'); // len 12
+ row.append(8, '\x00'); // sign, days(4), hour, min, sec
+ } else {
+ row.push_back('\x0b'); // len 11
+ row.append("\xe8\x07\x01\x01", 4); // 2024-01-01
+ row.append(3, '\x00'); // hour, min, sec
+ }
+ for (int i = 0; i < 4; ++i) {
+ row.push_back((char)((micros >> (8 * i)) & 0xFF));
+ }
+ return row;
+}
+
+// The microseconds field is formatted two digits at a time through lookup
+// tables that only cover 0..99, so a value above 999999 must be rejected
+// rather than indexing past the end of those tables.
+TEST(MysqlReplyParseTest, RejectOutOfRangeBinaryMicroseconds) {
+ const uint8_t types[] = {brpc::MYSQL_FIELD_TYPE_DATETIME,
+ brpc::MYSQL_FIELD_TYPE_TIME};
+ const uint32_t micros[] = {1000000, 1010000, 0xFFFFFFFF};
+ for (size_t t = 0; t < sizeof(types) / sizeof(types[0]); ++t) {
+ for (size_t m = 0; m < sizeof(micros) / sizeof(micros[0]); ++m) {
+ brpc::MysqlReply reply;
+ butil::Arena arena;
+ ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG,
+ ParseWire(MakeBinaryResultSet(
+ MakeColumnDef(types[t], 0, 6),
+ MakeBinaryTemporalRow(types[t],
micros[m])),
+ &reply, &arena,
brpc::MYSQL_PREPARED_STATEMENT))
+ << "type " << (int)types[t] << " micros " << micros[m];
+ }
+ }
+}
+
+TEST(MysqlReplyParseTest, AcceptMaxBinaryMicroseconds) {
+ const struct {
+ uint8_t type;
+ const char* expected;
+ } cases[] = {
+ {brpc::MYSQL_FIELD_TYPE_DATETIME, "2024-01-01 00:00:00.999999"},
+ {brpc::MYSQL_FIELD_TYPE_TIME, "00:00:00.999999"},
+ };
+ for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
+ brpc::MysqlReply reply;
+ butil::Arena arena;
+ ASSERT_EQ(brpc::PARSE_OK,
+ ParseWire(MakeBinaryResultSet(
+ MakeColumnDef(cases[i].type, 0, 6),
+ MakeBinaryTemporalRow(cases[i].type, 999999)),
+ &reply, &arena, brpc::MYSQL_PREPARED_STATEMENT))
+ << "case " << i;
+ ASSERT_EQ(1u, reply.row_count());
+ ASSERT_EQ(butil::StringPiece(cases[i].expected),
+ reply.next().field(0).string())
+ << "case " << i;
+ }
+}
+
} // namespace
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]