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 f03649d4 Enforce body size limits in protocol parsers (#3382)
f03649d4 is described below
commit f03649d4b87a6743c9cdd6c5586fd69487d7c4d2
Author: Weibing Wang <[email protected]>
AuthorDate: Sun Jul 12 22:38:25 2026 +0800
Enforce body size limits in protocol parsers (#3382)
* Enforce body size limits in protocol parsers
* Use scoped FlagSaver
---
src/brpc/details/http_message.cpp | 9 +++++++
src/brpc/details/http_message.h | 3 +++
src/brpc/policy/couchbase_protocol.cpp | 4 +++
src/brpc/policy/http2_rpc_protocol.cpp | 3 +++
src/brpc/policy/http_rpc_protocol.cpp | 19 ++++++++++++++
src/brpc/policy/memcache_binary_protocol.cpp | 4 +++
test/brpc_couchbase_unittest.cpp | 29 ++++++++++++++++++++-
test/brpc_http_rpc_protocol_unittest.cpp | 38 ++++++++++++++++++++++++++++
test/brpc_memcache_unittest.cpp | 29 +++++++++++++++++++++
9 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/src/brpc/details/http_message.cpp
b/src/brpc/details/http_message.cpp
index 0ffe5b11..003bafa0 100644
--- a/src/brpc/details/http_message.cpp
+++ b/src/brpc/details/http_message.cpp
@@ -39,6 +39,7 @@ DEFINE_bool(http_verbose, false,
DEFINE_int32(http_verbose_max_body_length, 512,
"[DEBUG] Max body length printed when -http_verbose is on");
DECLARE_int64(socket_max_unwritten_bytes);
+DECLARE_uint64(max_body_size);
// Implement callbacks for http parser
@@ -254,6 +255,14 @@ int HttpMessage::on_message_complete_cb(http_parser
*parser) {
}
int HttpMessage::OnBody(const char *at, const size_t length) {
+ if (!_read_body_progressively) {
+ if (length > FLAGS_max_body_size ||
+ _body_size > FLAGS_max_body_size - length) {
+ _body_too_large = true;
+ return -1;
+ }
+ _body_size += length;
+ }
if (_vmsgbuilder) {
if (_stage != HTTP_ON_BODY) {
// only add prefix at first entry.
diff --git a/src/brpc/details/http_message.h b/src/brpc/details/http_message.h
index 655ba1a9..ae4a016d 100644
--- a/src/brpc/details/http_message.h
+++ b/src/brpc/details/http_message.h
@@ -71,6 +71,7 @@ public:
HttpHeader& header() { return _header; }
const HttpHeader& header() const { return _header; }
size_t parsed_length() const { return _parsed_length; }
+ bool body_too_large() const { return _body_too_large; }
// Http parser callback functions
static int on_message_begin(http_parser *);
@@ -115,6 +116,8 @@ private:
// Read body progressively
ProgressiveReader* _body_reader{NULL};
butil::IOBuf _body;
+ size_t _body_size{0};
+ bool _body_too_large{false};
// Store the IOBuf information in `ParseFromIOBuf'
// for later zero-copy usage in `OnBody'.
diff --git a/src/brpc/policy/couchbase_protocol.cpp
b/src/brpc/policy/couchbase_protocol.cpp
index 0ece53db..0ab79bfb 100644
--- a/src/brpc/policy/couchbase_protocol.cpp
+++ b/src/brpc/policy/couchbase_protocol.cpp
@@ -39,6 +39,7 @@
namespace brpc {
DECLARE_bool(enable_rpcz);
+DECLARE_uint64(max_body_size);
namespace policy {
@@ -96,6 +97,9 @@ ParseResult ParseCouchbaseMessage(butil::IOBuf* source,
Socket* socket,
}
const CouchbaseResponseHeader* header = (const CouchbaseResponseHeader*)p;
uint32_t total_body_length = butil::NetToHost32(header->total_body_length);
+ if (total_body_length > FLAGS_max_body_size) {
+ return MakeParseError(PARSE_ERROR_TOO_BIG_DATA);
+ }
if (source->size() < sizeof(*header) + total_body_length) {
return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
}
diff --git a/src/brpc/policy/http2_rpc_protocol.cpp
b/src/brpc/policy/http2_rpc_protocol.cpp
index 043f53eb..b2de496c 100644
--- a/src/brpc/policy/http2_rpc_protocol.cpp
+++ b/src/brpc/policy/http2_rpc_protocol.cpp
@@ -739,6 +739,9 @@ H2ParseResult H2StreamContext::OnData(
for (size_t i = 0; i < data.backing_block_num(); ++i) {
const butil::StringPiece blk = data.backing_block(i);
if (OnBody(blk.data(), blk.size()) != 0) {
+ if (body_too_large()) {
+ return MakeH2Error(H2_ENHANCE_YOUR_CALM, stream_id());
+ }
LOG(ERROR) << "Fail to parse data";
return MakeH2Error(H2_PROTOCOL_ERROR);
}
diff --git a/src/brpc/policy/http_rpc_protocol.cpp
b/src/brpc/policy/http_rpc_protocol.cpp
index e5e22d92..8cbe0698 100644
--- a/src/brpc/policy/http_rpc_protocol.cpp
+++ b/src/brpc/policy/http_rpc_protocol.cpp
@@ -1217,6 +1217,25 @@ ParseResult ParseHttpMessage(butil::IOBuf *source,
Socket *socket,
// comments in http_message.h
rc = http_imsg->ParseFromIOBuf(*source);
}
+ if (rc < 0 && http_imsg->body_too_large()) {
+ if (socket->CreatedByConnect()) {
+ return MakeParseError(PARSE_ERROR_TOO_BIG_DATA);
+ }
+ const int release_rc = socket->ReleaseAdditionalReference();
+ if (release_rc == 0) {
+ butil::IOBuf resp;
+ HttpHeader header;
+ header.set_status_code(HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE);
+ header.SetHeader("Connection", "close");
+ MakeRawHttpResponse(&resp, &header, NULL);
+ Socket::WriteOptions wopt;
+ wopt.ignore_eovercrowded = true;
+ socket->Write(&resp, &wopt);
+ } else if (release_rc > 0) {
+ LOG(ERROR) << "Impossible: Recycled!";
+ }
+ return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
+ }
if (http_imsg->is_stage2()) {
// The header part is already parsed as an intact HTTP message
// to the ProcessHttpXXX. Here parses the body part.
diff --git a/src/brpc/policy/memcache_binary_protocol.cpp
b/src/brpc/policy/memcache_binary_protocol.cpp
index 46432c4f..e3174be5 100644
--- a/src/brpc/policy/memcache_binary_protocol.cpp
+++ b/src/brpc/policy/memcache_binary_protocol.cpp
@@ -40,6 +40,7 @@
namespace brpc {
DECLARE_bool(enable_rpcz);
+DECLARE_uint64(max_body_size);
namespace policy {
@@ -90,6 +91,9 @@ ParseResult ParseMemcacheMessage(butil::IOBuf* source,
}
const MemcacheResponseHeader* header = (const
MemcacheResponseHeader*)p;
uint32_t total_body_length =
butil::NetToHost32(header->total_body_length);
+ if (total_body_length > FLAGS_max_body_size) {
+ return MakeParseError(PARSE_ERROR_TOO_BIG_DATA);
+ }
if (source->size() < sizeof(*header) + total_body_length) {
return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA);
}
diff --git a/test/brpc_couchbase_unittest.cpp b/test/brpc_couchbase_unittest.cpp
index dacc9a09..e2f2fc0a 100644
--- a/test/brpc_couchbase_unittest.cpp
+++ b/test/brpc_couchbase_unittest.cpp
@@ -16,11 +16,17 @@
// under the License.
#include <brpc/couchbase.h>
+#include <brpc/policy/couchbase_protocol.h>
+#include <brpc/socket.h>
+#include <butil/sys_byteorder.h>
+#include <butil/iobuf.h>
#include <butil/logging.h>
+#include <gflags/gflags.h>
#include <gtest/gtest.h>
namespace brpc {
DECLARE_int32(idle_timeout_second);
+DECLARE_uint64(max_body_size);
}
int main(int argc, char* argv[]) {
@@ -34,6 +40,27 @@ namespace {
// Unit Tests - No Server Required
class CouchbaseUnitTest : public testing::Test {};
+TEST_F(CouchbaseUnitTest, RejectOversizedResponseBeforeBufferingBody) {
+ GFLAGS_NAMESPACE::FlagSaver flag_saver;
+ brpc::FLAGS_max_body_size = 1024;
+
+ brpc::SocketId id;
+ brpc::SocketOptions options;
+ ASSERT_EQ(0, brpc::Socket::Create(options, &id));
+ brpc::SocketUniquePtr socket;
+ ASSERT_EQ(0, brpc::Socket::Address(id, &socket));
+
+ brpc::policy::CouchbaseResponseHeader couchbase_header = {};
+ couchbase_header.magic = brpc::policy::CB_MAGIC_RESPONSE;
+ couchbase_header.total_body_length = butil::HostToNet32(1025);
+ butil::IOBuf couchbase_buf;
+ couchbase_buf.append(&couchbase_header, sizeof(couchbase_header));
+ EXPECT_EQ(brpc::PARSE_ERROR_TOO_BIG_DATA,
+ brpc::policy::ParseCouchbaseMessage(
+ &couchbase_buf, socket.get(), false, NULL).error());
+
+}
+
TEST_F(CouchbaseUnitTest, RequestBuilders) {
brpc::CouchbaseOperations::CouchbaseRequest req;
req.Clear();
@@ -82,4 +109,4 @@ TEST_F(CouchbaseUnitTest, EdgeCases) {
EXPECT_TRUE(true);
}
-} // namespace
\ No newline at end of file
+} // namespace
diff --git a/test/brpc_http_rpc_protocol_unittest.cpp
b/test/brpc_http_rpc_protocol_unittest.cpp
index 3f3290bd..a75a1225 100644
--- a/test/brpc_http_rpc_protocol_unittest.cpp
+++ b/test/brpc_http_rpc_protocol_unittest.cpp
@@ -60,6 +60,7 @@ DECLARE_string(rpc_dump_dir);
DECLARE_int32(rpc_dump_max_requests_in_one_file);
DECLARE_bool(allow_chunked_length);
DECLARE_int32(max_connection_pool_size);
+DECLARE_uint64(max_body_size);
extern bvar::CollectorSpeedLimit g_rpc_dump_sl;
}
@@ -344,6 +345,43 @@ protected:
MyAuthenticator _auth;
};
+TEST_F(HttpTest, reject_oversized_http_body) {
+ GFLAGS_NAMESPACE::FlagSaver flag_saver;
+ brpc::FLAGS_max_body_size = 4;
+ butil::IOBuf buf;
+ buf.append("POST / HTTP/1.1\r\nContent-Length: 5\r\n\r\nhello");
+
+ brpc::ParseResult result =
+ brpc::policy::ParseHttpMessage(&buf, _socket.get(), false, NULL);
+ EXPECT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, result.error());
+ int bytes_in_pipe = 0;
+ ASSERT_EQ(0, ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe));
+ ASSERT_GT(bytes_in_pipe, 0);
+ butil::IOPortal response;
+ ASSERT_EQ(bytes_in_pipe,
+ response.append_from_file_descriptor(_pipe_fds[0],
bytes_in_pipe));
+ EXPECT_NE(std::string::npos, response.to_string().find(" 413 "));
+}
+
+TEST_F(HttpTest, reject_oversized_chunked_http_body) {
+ GFLAGS_NAMESPACE::FlagSaver flag_saver;
+ brpc::FLAGS_max_body_size = 4;
+ butil::IOBuf buf;
+ buf.append("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
+ "3\r\nabc\r\n2\r\nde\r\n0\r\n\r\n");
+
+ brpc::ParseResult result =
+ brpc::policy::ParseHttpMessage(&buf, _socket.get(), false, NULL);
+ EXPECT_EQ(brpc::PARSE_ERROR_NOT_ENOUGH_DATA, result.error());
+ int bytes_in_pipe = 0;
+ ASSERT_EQ(0, ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe));
+ ASSERT_GT(bytes_in_pipe, 0);
+ butil::IOPortal response;
+ ASSERT_EQ(bytes_in_pipe,
+ response.append_from_file_descriptor(_pipe_fds[0],
bytes_in_pipe));
+ EXPECT_NE(std::string::npos, response.to_string().find(" 413 "));
+}
+
int AllocateFreePortOrDie() {
butil::fd_guard fd(tcp_listen(butil::EndPoint(butil::my_ip(), 0)));
EXPECT_GE(fd, 0);
diff --git a/test/brpc_memcache_unittest.cpp b/test/brpc_memcache_unittest.cpp
index f929767f..b1da9584 100644
--- a/test/brpc_memcache_unittest.cpp
+++ b/test/brpc_memcache_unittest.cpp
@@ -20,10 +20,17 @@
#include "butil/logging.h"
#include <brpc/memcache.h>
#include <brpc/channel.h>
+#include <brpc/policy/memcache_binary_header.h>
+#include <brpc/policy/memcache_binary_protocol.h>
+#include <brpc/socket.h>
+#include <butil/iobuf.h>
+#include <butil/sys_byteorder.h>
+#include <gflags/gflags.h>
#include <gtest/gtest.h>
namespace brpc {
DECLARE_int32(idle_timeout_second);
+DECLARE_uint64(max_body_size);
}
int main(int argc, char* argv[]) {
@@ -33,6 +40,28 @@ int main(int argc, char* argv[]) {
}
namespace {
+
+TEST(MemcacheParserTest, RejectOversizedResponseBeforeBufferingBody) {
+ GFLAGS_NAMESPACE::FlagSaver flag_saver;
+ brpc::FLAGS_max_body_size = 1024;
+
+ brpc::SocketId id;
+ brpc::SocketOptions options;
+ ASSERT_EQ(0, brpc::Socket::Create(options, &id));
+ brpc::SocketUniquePtr socket;
+ ASSERT_EQ(0, brpc::Socket::Address(id, &socket));
+
+ brpc::policy::MemcacheResponseHeader header = {};
+ header.magic = brpc::policy::MC_MAGIC_RESPONSE;
+ header.total_body_length = butil::HostToNet32(1025);
+ butil::IOBuf buf;
+ buf.append(&header, sizeof(header));
+ EXPECT_EQ(brpc::PARSE_ERROR_TOO_BIG_DATA,
+ brpc::policy::ParseMemcacheMessage(
+ &buf, socket.get(), false, NULL).error());
+
+}
+
static pthread_once_t download_memcached_once = PTHREAD_ONCE_INIT;
static pid_t g_mc_pid = -1;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]