guard underflowed value size in couchbase collection responses (#3402)
* guard underflowed value size in couchbase collection responses
* Compute couchbase value_size as int64_t to avoid int overflow
diff --git a/src/brpc/couchbase.cpp b/src/brpc/couchbase.cpp
index a707d75..7e622b1 100644
--- a/src/brpc/couchbase.cpp
+++ b/src/brpc/couchbase.cpp
@@ -30,6 +30,7 @@
} \
} while (0)
+#include <cinttypes>
#include <iostream>
#include "brpc/policy/couchbase_protocol.h"
@@ -1506,10 +1507,16 @@
if (header.status != 0) {
// handle error case
- _buf.pop_front(sizeof(header) + header.extras_length + header.key_length);
// Possibly read error message from value if present
- size_t value_size =
- header.total_body_length - header.extras_length - header.key_length;
+ const int64_t value_size = static_cast<int64_t>(header.total_body_length) -
+ static_cast<int64_t>(header.extras_length) -
+ static_cast<int64_t>(header.key_length);
+ if (value_size < 0) {
+ butil::string_printf(&_err, "value_size=%" PRId64 " is negative",
+ value_size);
+ return false;
+ }
+ _buf.pop_front(sizeof(header) + header.extras_length + header.key_length);
if (value_size > 0) {
std::string err_msg;
_buf.cutn(&err_msg, value_size);
@@ -1583,10 +1590,16 @@
if (header.key_length != 0) {
DEBUG_PRINT("Get Collections Manifest response must not have key");
}
- _buf.pop_front(sizeof(header) + header.extras_length + header.key_length);
// Possibly read error message from value if present
- size_t value_size =
- header.total_body_length - header.extras_length - header.key_length;
+ const int64_t value_size = static_cast<int64_t>(header.total_body_length) -
+ static_cast<int64_t>(header.extras_length) -
+ static_cast<int64_t>(header.key_length);
+ if (value_size < 0) {
+ butil::string_printf(&_err, "value_size=%" PRId64 " is negative",
+ value_size);
+ return false;
+ }
+ _buf.pop_front(sizeof(header) + header.extras_length + header.key_length);
if (value_size > 0) {
std::string err_msg;
_buf.cutn(&err_msg, value_size);
@@ -1599,8 +1612,14 @@
}
// Success case: the manifest should be in the value section
- size_t value_size =
- header.total_body_length - header.extras_length - header.key_length;
+ const int64_t value_size = static_cast<int64_t>(header.total_body_length) -
+ static_cast<int64_t>(header.extras_length) -
+ static_cast<int64_t>(header.key_length);
+ if (value_size < 0) {
+ butil::string_printf(&_err, "value_size=%" PRId64 " is negative",
+ value_size);
+ return false;
+ }
if (value_size == 0) {
butil::string_printf(&_err, "No manifest data in response");
_buf.pop_front(sizeof(header) + header.total_body_length);
diff --git a/test/brpc_couchbase_unittest.cpp b/test/brpc_couchbase_unittest.cpp
index e2f2fc0..febf31c 100644
--- a/test/brpc_couchbase_unittest.cpp
+++ b/test/brpc_couchbase_unittest.cpp
@@ -98,6 +98,36 @@
EXPECT_EQ(0x00, result.status_code);
}
+// The header fields are attacker-controlled, extras_length + key_length may
+// exceed total_body_length. popManifest/popCollectionId must not treat the
+// underflowed remainder as a value size and drain the pipelined buffer.
+TEST_F(CouchbaseUnitTest, CollectionResponseWithInconsistentBodyLength) {
+ const std::string next_response(64, 'A');
+
+ brpc::policy::CouchbaseResponseHeader header = {};
+ header.magic = brpc::policy::CB_MAGIC_RESPONSE;
+ header.command = brpc::policy::CB_GET_COLLECTIONS_MANIFEST;
+ header.extras_length = 1;
+ header.total_body_length = 0;
+
+ brpc::CouchbaseOperations::CouchbaseResponse res;
+ res.rawBuffer().append(&header, sizeof(header));
+ res.rawBuffer().append(next_response);
+ std::string manifest = "untouched";
+ EXPECT_FALSE(res.popManifest(&manifest));
+ EXPECT_EQ("untouched", manifest);
+ EXPECT_EQ(sizeof(header) + next_response.size(), res.rawBuffer().size());
+
+ header.command = brpc::policy::CB_COLLECTIONS_GET_CID;
+ header.status = 1;
+ brpc::CouchbaseOperations::CouchbaseResponse res2;
+ res2.rawBuffer().append(&header, sizeof(header));
+ res2.rawBuffer().append(next_response);
+ uint8_t collection_id = 0;
+ EXPECT_FALSE(res2.popCollectionId(&collection_id));
+ EXPECT_EQ(sizeof(header) + next_response.size(), res2.rawBuffer().size());
+}
+
TEST_F(CouchbaseUnitTest, EdgeCases) {
brpc::CouchbaseOperations::CouchbaseRequest req;
req.addRequest("", "value", 0, 0, 0);