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]

Reply via email to