This is an automated email from the ASF dual-hosted git repository.
chenBright 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 7c270762 Check field length against remaining buffer in mysql
Field::Parse (#3381)
7c270762 is described below
commit 7c27076255b9fe15bd88a250f73eede95904eb35
Author: UB <[email protected]>
AuthorDate: Thu Jul 16 14:42:34 2026 +0530
Check field length against remaining buffer in mysql Field::Parse (#3381)
* check field length against remaining buffer in mysql Field::Parse
Signed-off-by: ubeddulla khan <[email protected]>
* test: reject oversized text field length in MysqlReply parse
Signed-off-by: ubeddulla khan <[email protected]>
---------
Signed-off-by: ubeddulla khan <[email protected]>
---
src/brpc/policy/mysql/mysql_reply.cpp | 10 +++
test/brpc_mysql_reply_parse_unittest.cpp | 126 +++++++++++++++++++++++++++++++
2 files changed, 136 insertions(+)
diff --git a/src/brpc/policy/mysql/mysql_reply.cpp
b/src/brpc/policy/mysql/mysql_reply.cpp
index 0a299647..46778f2e 100644
--- a/src/brpc/policy/mysql/mysql_reply.cpp
+++ b/src/brpc/policy/mysql/mysql_reply.cpp
@@ -830,6 +830,16 @@ ParseError MysqlReply::Field::Parse(butil::IOBuf& buf,
set_parsed();
return PARSE_OK;
}
+ // The length-encoded value must fit in the remaining buffer. cutn clamps
to
+ // what is available, so an oversized len leaves the tail of the len-byte
+ // allocation uninitialized while _data.str is published as len bytes,
+ // exposing uninitialized arena memory and desyncing the packet stream.
+ // The binary Field::Parse and Column::Parse paths already guard this.
+ if (len > buf.size()) {
+ LOG(WARNING) << "MysqlReply::Field::Parse: field length " << len
+ << " exceeds remaining buffer size " << buf.size();
+ return PARSE_ERROR_ABSOLUTELY_WRONG;
+ }
// field is not null
butil::IOBuf str;
buf.cutn(&str, len);
diff --git a/test/brpc_mysql_reply_parse_unittest.cpp
b/test/brpc_mysql_reply_parse_unittest.cpp
new file mode 100644
index 00000000..33ab95a5
--- /dev/null
+++ b/test/brpc_mysql_reply_parse_unittest.cpp
@@ -0,0 +1,126 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+#include "butil/iobuf.h"
+#include "butil/arena.h"
+#include "brpc/policy/mysql/mysql_reply.h"
+#include "brpc/policy/mysql/mysql_common.h"
+
+namespace {
+
+// Append a MySQL packet: 3-byte little-endian payload length + 1-byte
+// sequence id, followed by the payload.
+void AppendPacket(std::string* out, uint8_t seq, const std::string& payload) {
+ const uint32_t len = (uint32_t)payload.size();
+ out->push_back((char)(len & 0xFF));
+ out->push_back((char)((len >> 8) & 0xFF));
+ out->push_back((char)((len >> 16) & 0xFF));
+ out->push_back((char)seq);
+ out->append(payload);
+}
+
+// A minimal text-protocol column definition for a single VAR_STRING column.
+std::string MakeColumnDef() {
+ std::string p;
+ for (int i = 0; i < 6; ++i) { //
catalog/database/table/origin_table/name/origin_name
+ p.push_back(0x00); // length-encoded empty string
+ }
+ p.push_back(0x0c); // length of the fixed-length fields
+ p.push_back(0x21); // charset (2 bytes)
+ p.push_back(0x00);
+ p.append(4, '\x00'); // column length (4 bytes)
+ p.push_back((char)brpc::MYSQL_FIELD_TYPE_VAR_STRING); // field type
+ p.push_back(0x00); // flag (2 bytes): not-null and unsigned
both off
+ p.push_back(0x00);
+ p.push_back(0x00); // decimals
+ p.append(2, '\x00'); // filler
+ return p;
+}
+
+std::string MakeEof() {
+ std::string p;
+ p.push_back((char)0xFE); // EOF header
+ p.append(4, '\x00'); // warnings (2) + status flags (2)
+ return p;
+}
+
+// A text result set carrying a single VAR_STRING column and one row whose only
+// field is |field_payload| (the length-encoded field value as raw bytes).
+std::string MakeTextResultSet(const std::string& field_payload,
+ bool with_trailing_eof) {
+ std::string buf;
+ AppendPacket(&buf, 1, std::string(1, '\x01')); // result-set header, 1
column
+ AppendPacket(&buf, 2, MakeColumnDef());
+ AppendPacket(&buf, 3, MakeEof()); // EOF after column defs
+ AppendPacket(&buf, 4, field_payload); // the row
+ if (with_trailing_eof) {
+ AppendPacket(&buf, 5, MakeEof()); // EOF after rows
+ }
+ return buf;
+}
+
+// A row field whose length-encoded prefix claims far more bytes than the
packet
+// (0xFD + 3-byte length 0xFFFFFF) must be rejected rather than clamped. Before
+// the fix cutn would clamp to what remained, allocate the full claimed length,
+// and publish a StringPiece over uninitialized arena memory while desyncing
the
+// stream.
+TEST(MysqlReplyParseTest, RejectOversizedTextFieldLength) {
+ std::string field;
+ field.push_back((char)0xFD); // 3-byte length-encoded prefix
+ field.push_back((char)0xFF);
+ field.push_back((char)0xFF);
+ field.push_back((char)0xFF); // claims 0xFFFFFF bytes, none of which
follow
+
+ butil::IOBuf buf;
+ buf.append(MakeTextResultSet(field, false));
+
+ brpc::MysqlReply reply;
+ butil::Arena arena;
+ bool more_results = false;
+ brpc::ParseError rc = reply.ConsumePartialIOBuf(
+ buf, &arena, false, brpc::MYSQL_NORMAL_STATEMENT, &more_results);
+ ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, rc);
+}
+
+// A well-formed field whose length matches the bytes present still parses, so
+// the guard does not reject legitimate result sets.
+TEST(MysqlReplyParseTest, AcceptWellFormedTextField) {
+ std::string field;
+ field.push_back((char)0x02); // length-encoded length 2
+ field.append("hi");
+
+ butil::IOBuf buf;
+ buf.append(MakeTextResultSet(field, true));
+
+ brpc::MysqlReply reply;
+ butil::Arena arena;
+ bool more_results = false;
+ brpc::ParseError rc = reply.ConsumePartialIOBuf(
+ buf, &arena, false, brpc::MYSQL_NORMAL_STATEMENT, &more_results);
+ ASSERT_EQ(brpc::PARSE_OK, rc);
+ ASSERT_TRUE(reply.is_resultset());
+ ASSERT_EQ(1u, reply.column_count());
+ ASSERT_EQ(1u, reply.row_count());
+ ASSERT_EQ("hi", reply.next().field(0).string());
+}
+
+} // namespace
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]