-
Notifications
You must be signed in to change notification settings - Fork 4.1k
fix(protocol): fix attachment size limit issue when size exceeds uint32 #3169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
76f0954
94ef1ae
7ec37a0
5b5b623
e27ed16
a4df271
e1fdd37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,7 +30,7 @@ message NsheadMeta { | |||||||||||
|
|
||||||||||||
| optional int64 correlation_id = 2; | ||||||||||||
| optional int64 log_id = 3; | ||||||||||||
| optional int32 attachment_size = 4; | ||||||||||||
| optional int64 attachment_size = 4; | ||||||||||||
|
||||||||||||
| optional int64 attachment_size = 4; | |
| // For backward compatibility, keep attachment_size as int32. | |
| optional int32 attachment_size = 4; | |
| // Use attachment_size_long for large attachments. | |
| optional int64 attachment_size_long = 10; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,8 @@ | |||||
| // under the License. | ||||||
|
|
||||||
|
|
||||||
| #include <cinttypes> // PRId64, PRIu64 | ||||||
| #include <cstdint> // UINT32_MAX | ||||||
| #include <google/protobuf/descriptor.h> // MethodDescriptor | ||||||
| #include <google/protobuf/message.h> // Message | ||||||
| #include <google/protobuf/io/zero_copy_stream_impl_lite.h> | ||||||
|
|
@@ -62,36 +64,100 @@ DEFINE_bool(baidu_std_protocol_deliver_timeout_ms, false, | |||||
| DECLARE_bool(pb_enum_as_number); | ||||||
|
|
||||||
| // Notes: | ||||||
| // 1. 12-byte header [PRPC][body_size][meta_size] | ||||||
| // 1. Header format: | ||||||
| // - Normal format (12 bytes): [PRPC][body_size(32bit)][meta_size(32bit)] | ||||||
| // - Extended format (20 bytes): [PRPC][UINT32_MAX][meta_size(32bit)][body_size(64bit)] | ||||||
| // Extended format is used when body_size > UINT32_MAX | ||||||
|
Comment on lines
+69
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If body_size equals UINT32_MAX, the client uses the normal format, while the server uses the extended format.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The two formats have the same structure, only the magic number and body size differ. This makes them simpler to process. @wwbmmm What do you think of the approach?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there anything else that needs to be modified in the code?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's nothing else that needs to be changed.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this approach is clearer, but current approach is also acceptable.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@wwbmmm I think that this incompatible case is unacceptable.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree with you. |
||||||
| // 2. body_size and meta_size are in network byte order | ||||||
| // 3. Use service->full_name() + method_name to specify the method to call | ||||||
| // 4. `attachment_size' is set iff request/response has attachment | ||||||
| // 5. Not supported: chunk_info | ||||||
|
|
||||||
| // Helper function to get attachment size from RpcMeta, with backward compatibility | ||||||
| static int64_t GetAttachmentSize(const RpcMeta& meta) { | ||||||
| if (meta.has_attachment_size_long()) { | ||||||
| return meta.attachment_size_long(); | ||||||
| } | ||||||
| if (meta.has_attachment_size()) { | ||||||
| return static_cast<int64_t>(meta.attachment_size()); | ||||||
| } | ||||||
| return 0; | ||||||
| } | ||||||
|
|
||||||
| // Helper function to set attachment size in RpcMeta, with backward compatibility | ||||||
| static void SetAttachmentSize(RpcMeta* meta, size_t size) { | ||||||
| const int32_t INT32_MAX_VALUE = 0x7FFFFFFF; | ||||||
| if (size > static_cast<size_t>(INT32_MAX_VALUE)) { | ||||||
| meta->set_attachment_size_long(static_cast<int64_t>(size)); | ||||||
| } else { | ||||||
| meta->set_attachment_size(static_cast<int32_t>(size)); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Helper function to get attachment size from RpcDumpMeta, with backward compatibility | ||||||
| static int64_t GetAttachmentSizeFromDump(const RpcDumpMeta& meta) { | ||||||
| if (meta.has_attachment_size_long()) { | ||||||
| return meta.attachment_size_long(); | ||||||
| } | ||||||
| if (meta.has_attachment_size()) { | ||||||
| return static_cast<int64_t>(meta.attachment_size()); | ||||||
| } | ||||||
| return 0; | ||||||
| } | ||||||
|
|
||||||
| // Helper function to set attachment size in RpcDumpMeta, with backward compatibility | ||||||
| static void SetAttachmentSizeInDump(RpcDumpMeta* meta, size_t size) { | ||||||
| const int32_t INT32_MAX_VALUE = 0x7FFFFFFF; | ||||||
| if (size > static_cast<size_t>(INT32_MAX_VALUE)) { | ||||||
| meta->set_attachment_size_long(static_cast<int64_t>(size)); | ||||||
| } else { | ||||||
| meta->set_attachment_size(static_cast<int32_t>(size)); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Pack header into `buf' | ||||||
| inline void PackRpcHeader(char* rpc_header, uint32_t meta_size, int payload_size) { | ||||||
| // Returns the size of header written (12 for normal, 20 for extended) | ||||||
| inline size_t PackRpcHeader(char* rpc_header, uint32_t meta_size, size_t payload_size) { | ||||||
| uint32_t* dummy = (uint32_t*)rpc_header; // suppress strict-alias warning | ||||||
| *dummy = *(uint32_t*)"PRPC"; | ||||||
| butil::RawPacker(rpc_header + 4) | ||||||
| .pack32(meta_size + payload_size) | ||||||
| .pack32(meta_size); | ||||||
| const uint64_t total_size = static_cast<uint64_t>(meta_size) + payload_size; | ||||||
| if (total_size > UINT32_MAX) { | ||||||
| // Extended format: use UINT32_MAX as flag, followed by 64-bit total_size | ||||||
| butil::RawPacker(rpc_header + 4) | ||||||
| .pack32(UINT32_MAX) | ||||||
| .pack32(meta_size) | ||||||
| .pack64(total_size); | ||||||
| return 20; // 4 (magic) + 4 (flag) + 4 (meta_size) + 8 (total_size) | ||||||
| } else { | ||||||
| // Normal format: 32-bit total_size | ||||||
| butil::RawPacker(rpc_header + 4) | ||||||
| .pack32(static_cast<uint32_t>(total_size)) | ||||||
| .pack32(meta_size); | ||||||
| return 12; // 4 (magic) + 4 (body_size) + 4 (meta_size) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| static void SerializeRpcHeaderAndMeta( | ||||||
| butil::IOBuf* out, const RpcMeta& meta, int payload_size) { | ||||||
| butil::IOBuf* out, const RpcMeta& meta, size_t payload_size) { | ||||||
| const uint32_t meta_size = GetProtobufByteSize(meta); | ||||||
| if (meta_size <= 244) { // most common cases | ||||||
| const uint64_t total_size = static_cast<uint64_t>(meta_size) + payload_size; | ||||||
| const bool use_extended = (total_size > UINT32_MAX); | ||||||
|
|
||||||
| if (meta_size <= 244 && !use_extended) { | ||||||
| // Most common cases with normal format: optimize by combining header and meta | ||||||
| char header_and_meta[12 + meta_size]; | ||||||
| PackRpcHeader(header_and_meta, meta_size, payload_size); | ||||||
| const size_t actual_header_size = PackRpcHeader(header_and_meta, meta_size, payload_size); | ||||||
| CHECK_EQ(actual_header_size, 12U); // Should be 12 for normal format | ||||||
| ::google::protobuf::io::ArrayOutputStream arr_out(header_and_meta + 12, meta_size); | ||||||
| ::google::protobuf::io::CodedOutputStream coded_out(&arr_out); | ||||||
| meta.SerializeWithCachedSizes(&coded_out); // not calling ByteSize again | ||||||
| CHECK(!coded_out.HadError()); | ||||||
| CHECK_EQ(0, out->append(header_and_meta, sizeof(header_and_meta))); | ||||||
| } else { | ||||||
| char header[12]; | ||||||
| PackRpcHeader(header, meta_size, payload_size); | ||||||
| CHECK_EQ(0, out->append(header, sizeof(header))); | ||||||
| // Extended format or large meta: write header and meta separately | ||||||
| char header[20]; // Enough for both normal and extended format | ||||||
| const size_t actual_header_size = PackRpcHeader(header, meta_size, payload_size); | ||||||
| CHECK_EQ(0, out->append(header, actual_header_size)); | ||||||
| butil::IOBufAsZeroCopyOutputStream buf_stream(out); | ||||||
| ::google::protobuf::io::CodedOutputStream coded_out(&buf_stream); | ||||||
| meta.SerializeWithCachedSizes(&coded_out); | ||||||
|
|
@@ -101,8 +167,9 @@ static void SerializeRpcHeaderAndMeta( | |||||
|
|
||||||
| ParseResult ParseRpcMessage(butil::IOBuf* source, Socket* socket, | ||||||
| bool /*read_eof*/, const void*) { | ||||||
| char header_buf[12]; | ||||||
| const size_t n = source->copy_to(header_buf, sizeof(header_buf)); | ||||||
| // First read at least 12 bytes to check magic and determine format | ||||||
| char header_buf[20]; | ||||||
| const size_t n = source->copy_to(header_buf, 12); | ||||||
| if (n >= 4) { | ||||||
| void* dummy = header_buf; | ||||||
| if (*(const uint32_t*)dummy != *(const uint32_t*)"PRPC") { | ||||||
|
|
@@ -113,29 +180,50 @@ ParseResult ParseRpcMessage(butil::IOBuf* source, Socket* socket, | |||||
| return MakeParseError(PARSE_ERROR_TRY_OTHERS); | ||||||
| } | ||||||
| } | ||||||
| if (n < sizeof(header_buf)) { | ||||||
| if (n < 12) { | ||||||
| return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); | ||||||
| } | ||||||
| uint32_t body_size; | ||||||
|
|
||||||
| uint32_t body_size_32; | ||||||
| uint32_t meta_size; | ||||||
| butil::RawUnpacker(header_buf + 4).unpack32(body_size).unpack32(meta_size); | ||||||
| uint64_t body_size; | ||||||
| size_t header_size; | ||||||
|
|
||||||
| butil::RawUnpacker unpacker(header_buf + 4); | ||||||
| unpacker.unpack32(body_size_32).unpack32(meta_size); | ||||||
|
|
||||||
| if (body_size_32 == UINT32_MAX) { | ||||||
| // Extended format: read 8 more bytes for 64-bit body_size | ||||||
| if (source->length() < 20) { | ||||||
| return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); | ||||||
| } | ||||||
| source->copy_to(header_buf, 20); | ||||||
| unpacker = butil::RawUnpacker(header_buf + 12); | ||||||
| unpacker.unpack64(body_size); | ||||||
| header_size = 20; | ||||||
| } else { | ||||||
| // Normal format: use 32-bit body_size | ||||||
| body_size = static_cast<uint64_t>(body_size_32); | ||||||
| header_size = 12; | ||||||
| } | ||||||
|
|
||||||
| if (body_size > FLAGS_max_body_size) { | ||||||
| // We need this log to report the body_size to give users some clues | ||||||
| // which is not printed in InputMessenger. | ||||||
| LOG(ERROR) << "body_size=" << body_size << " from " | ||||||
| << socket->remote_side() << " is too large"; | ||||||
| return MakeParseError(PARSE_ERROR_TOO_BIG_DATA); | ||||||
| } else if (source->length() < sizeof(header_buf) + body_size) { | ||||||
| } else if (source->length() < header_size + body_size) { | ||||||
| return MakeParseError(PARSE_ERROR_NOT_ENOUGH_DATA); | ||||||
| } | ||||||
| if (meta_size > body_size) { | ||||||
| LOG(ERROR) << "meta_size=" << meta_size << " is bigger than body_size=" | ||||||
| << body_size; | ||||||
| // Pop the message | ||||||
| source->pop_front(sizeof(header_buf) + body_size); | ||||||
| source->pop_front(header_size + body_size); | ||||||
| return MakeParseError(PARSE_ERROR_TRY_OTHERS); | ||||||
| } | ||||||
| source->pop_front(sizeof(header_buf)); | ||||||
| source->pop_front(header_size); | ||||||
| MostCommonMessage* msg = MostCommonMessage::Get(); | ||||||
| source->cutn(&msg->meta, meta_size); | ||||||
| source->cutn(&msg->payload, body_size - meta_size); | ||||||
|
|
@@ -347,7 +435,7 @@ void SendRpcResponse(int64_t correlation_id, Controller* cntl, | |||||
| meta.set_checksum_type(cntl->response_checksum_type()); | ||||||
| meta.set_checksum_value(accessor.checksum_value()); | ||||||
| if (attached_size > 0) { | ||||||
| meta.set_attachment_size(attached_size); | ||||||
| SetAttachmentSize(&meta, attached_size); | ||||||
| } | ||||||
| StreamId response_stream_id = INVALID_STREAM_ID; | ||||||
| SocketUniquePtr stream_ptr; | ||||||
|
|
@@ -585,7 +673,10 @@ void ProcessRpcRequest(InputMessageBase* msg_base) { | |||||
| sample->meta.set_method_name(request_meta.method_name()); | ||||||
| sample->meta.set_compress_type((CompressType)meta.compress_type()); | ||||||
| sample->meta.set_protocol_type(PROTOCOL_BAIDU_STD); | ||||||
| sample->meta.set_attachment_size(meta.attachment_size()); | ||||||
| const int64_t attachment_size = GetAttachmentSize(meta); | ||||||
| if (attachment_size > 0) { | ||||||
| SetAttachmentSizeInDump(&sample->meta, attachment_size); | ||||||
| } | ||||||
| sample->meta.set_authentication_data(meta.authentication_data()); | ||||||
| sample->request = msg->payload; | ||||||
| sample->submit(start_parse_us); | ||||||
|
|
@@ -676,12 +767,13 @@ void ProcessRpcRequest(InputMessageBase* msg_base) { | |||||
| break; | ||||||
| } | ||||||
|
|
||||||
| const int req_size = static_cast<int>(msg->payload.size()); | ||||||
| if (meta.has_attachment_size()) { | ||||||
| if (req_size < meta.attachment_size()) { | ||||||
| const size_t req_size = msg->payload.size(); | ||||||
| const int64_t attachment_size = GetAttachmentSize(meta); | ||||||
| if (attachment_size > 0) { | ||||||
| if (static_cast<size_t>(attachment_size) > req_size) { | ||||||
|
||||||
| cntl->SetFailed(EREQUEST, | ||||||
| "attachment_size=%d is larger than request_size=%d", | ||||||
| meta.attachment_size(), req_size); | ||||||
| "attachment_size=%" PRId64 " is larger than request_size=%zu", | ||||||
| attachment_size, req_size); | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
|
|
@@ -723,9 +815,10 @@ void ProcessRpcRequest(InputMessageBase* msg_base) { | |||||
| } | ||||||
|
|
||||||
| messages = BaiduProxyPBMessages::Get(); | ||||||
| const int64_t attachment_size = GetAttachmentSize(meta); | ||||||
|
||||||
| const int64_t attachment_size = GetAttachmentSize(meta); | |
| // Use the previously retrieved attachment_size variable here. |
Outdated
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant call to GetAttachmentSize(meta). The attachment_size was already retrieved and validated at lines 772-780. Consider reusing the same variable to avoid duplicate calls and improve code clarity.
| const int64_t attachment_size = GetAttachmentSize(meta); | |
| // attachment_size already retrieved and validated earlier |
Outdated
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation for negative attachment_size values. The GetAttachmentSize() function returns int64_t which could be negative. When a negative int64_t is cast to size_t at line 1065, it becomes a very large unsigned value that would correctly fail the > res_size check. However, the condition should explicitly check attachment_size < 0 before the cast to make the validation logic clearer and more robust.
Consider adding: if (attachment_size < 0 || static_cast<size_t>(attachment_size) > res_size) {
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -181,11 +181,23 @@ static void* replay_thread(void* arg) { | |
| memcpy(&nshead_req.head, sample->meta.nshead().c_str(), sample->meta.nshead().length()); | ||
| nshead_req.body = sample->request; | ||
| req_ptr = &nshead_req; | ||
| } else if (sample->meta.attachment_size() > 0) { | ||
| sample->request.cutn( | ||
| &req.serialized_data(), | ||
| sample->request.size() - sample->meta.attachment_size()); | ||
| cntl->request_attachment() = sample->request.movable(); | ||
| } else { | ||
| // Get attachment size with backward compatibility | ||
| int64_t attachment_size = 0; | ||
| if (sample->meta.has_attachment_size_long()) { | ||
| attachment_size = sample->meta.attachment_size_long(); | ||
| } else if (sample->meta.has_attachment_size()) { | ||
| attachment_size = static_cast<int64_t>(sample->meta.attachment_size()); | ||
| } | ||
| if (attachment_size > 0 && | ||
| static_cast<size_t>(attachment_size) < sample->request.size()) { | ||
|
||
| sample->request.cutn( | ||
| &req.serialized_data(), | ||
| sample->request.size() - static_cast<size_t>(attachment_size)); | ||
| cntl->request_attachment() = sample->request.movable(); | ||
| } else { | ||
| req.serialized_data() = sample->request.movable(); | ||
| } | ||
| } else { | ||
|
||
| req.serialized_data() = sample->request.movable(); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to change nshead protocol.