protocolbuffers / protocolbuffers/protobuf

Protobuf `ParseFromArray` takes an `int size` where negative values are UB

Open Beginner friendly
#28,065 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug help wanted
Dominant language
C++
Stars
72k
Forks
16.3k
Avg merge
1d 17h
Merged PRs (30d)
140

Description

A vulnerability has been identified in the Protocol Buffers C++ library, specifically within the google::protobuf::MessageLite::ParseFromArray method. This issue arises from improper handling of the size parameter, leading to an integer signedness error when a negative value is provided.

Vulnerability Details
Type: Integer Signedness / Integer Overflow
Component: google::protobuf::MessageLite
File: protobuf/src/google/protobuf/message_lite.cc
Method: ParseFromArray(const void* data, int size)
Root Cause Analysis
The ParseFromArray implementation is as follows:

bool MessageLite::ParseFromArray(const void* data, int size) {
return ParseFrom(as_string_view(data, size));
}
The as_string_view helper (in the same file) performs a direct cast:

inline absl::string_view as_string_view(const void* data, int size) {
return absl::string_view(static_cast<const char*>(data), size);
}
If a negative int size is passed (e.g., -1), it is cast to a size_t by the absl::string_view constructor. On a 64-bit system, this results in a length of 0xFFFFFFFFFFFFFFFF.

The library then proceeds to use this string_view to initialize a ParseContext. Inside ParseContext::InitFrom(absl::string_view flat), the following logic is executed:

if (flat.size() > kSlopBytes) {
limit_ = kSlopBytes;
limit_end_ = buffer_end_ = flat.data() + flat.size() - kSlopBytes;
// ...
}
Since flat.size() is extremely large, the pointer arithmetic flat.data() + flat.size() will overflow, causing limit_end_ and buffer_end_ to point to memory locations before the intended data buffer. Subsequent parsing logic that relies on these pointers will exhibit undefined behavior, typically leading to a crash or infinite loop.

Notably, the sister function ParseFromString(absl::string_view data) does contain a safety check:

bool MessageLite::ParseFromString(absl::string_view data) {
if (ABSL_PREDICT_FALSE(data.size() > INT_MAX)) return false;
return ParseFrom(data);
}
However, this check is missing in ParseFromArray.

Proof of Concept (PoC)
The following logical reproduction demonstrates the pointer corruption using the exact arithmetic logic found in protobuf/src/google/protobuf/parse_context.h.

#include

int main() {
char data_buffer[32];
const char* data_ptr = data_buffer;
int malicious_size = -1; // Negative size from user input

// Step 1: Simulate implicit cast in ParseFromArray -> as_string_view
size_t size = static_cast<size_t>(malicious_size);

// Step 2: Simulate ParseContext::InitFrom pointer arithmetic
// buffer_end_ = data + size - kSlopBytes;
const char* buffer_end = data_ptr + size - 16; 

std::cout << "[*] Buffer start: " << (void*)data_ptr << std::endl;
std::cout << "[*] Resulting buffer_end: " << (void*)buffer_end << std::endl;

if (buffer_end < data_ptr) {
    std::cout << "[!] VULNERABILITY CONFIRMED: buffer_end points BEFORE the buffer!" << std::endl;
}
return 0;

}
Execution Output:
[] Buffer start: 0x7ffc3d9ced00
[
] Resulting buffer_end: 0x7ffc3d9cecef
[!] VULNERABILITY CONFIRMED: buffer_end points BEFORE the buffer!
The resulting buffer_end is 17 bytes before the actual data buffer. Any subsequent parsing logic that checks ptr < buffer_end or calculates remaining bytes using buffer_end - ptr will operate on corrupted state, leading to out-of-bounds memory access or infinite loops.

Impact
Availability: A remote attacker could provide a crafted payload (or trigger a code path that passes a negative size) to cause a crash (DoS).
Security Bypass: Corrupted internal states during parsing could potentially be exploited to bypass security checks or cause data corruption.
THE OUTPUT OF POC: │ --- Protobuf ParseFromArray Integer Signedness Proof --- │ │ [] Buffer start: 0x7ffc3d9ced00 │ │ [] Malicious size: -1 │ │ [] ParseContext::InitFrom called with size: 18446744073709551615 │ │ [] Pointer arithmetic: data (0x7ffc3d9ced00) + size (18446744073709551615) - kSlopBytes (16) │ │ [*] Resulting buffer_end_: 0x7ffc3d9cecef │ │ │ │ [!] VULNERABILITY CONFIRMED │ │ [!] buffer_end_ points to memory BEFORE the data buffer. │ │ [!] This causes all subsequent 'Done()' and boundary checks to fail, │ │ [!] leading to OOB reads or infinite loops in the parser. │

Steps to Reproduce
Identify Target: Use any C++ project linked against libprotobuf (tested on v3.21.x through v4.26-dev).
Create Trigger Code: Define a Protobuf message and call ParseFromArray with a negative size:
google::protobuf::Empty msg;
char dummy[10];
msg.ParseFromArray(dummy, -1); // Negative size triggers the bug
Compile: Compile the code with a standard C++ compiler (e.g., g++ -std=c++17).
Run and Observe:
Monitor the execution under a debugger (GDB).
Set a breakpoint in google::protobuf::MessageLite::ParseFromArray.
Step into ParseContext::InitFrom.
Observe that buffer_end_ is calculated as data + 0xFFFFFFFFFFFFFFFF - 16, causing it to wrap around and point to memory addresses lower than data.
Observe Impact: The parser will either crash immediately due to an invalid memory access or enter an infinite loop as boundary checks (like ptr < buffer_end_) become logically impossible to satisfy.
Attack scenario
Remediation
Apply a check to validate that size is non-negative before calling as_string_view.

bool MessageLite::ParseFromArray(const void* data, int size) {
if (ABSL_PREDICT_FALSE(size < 0)) return false;
return ParseFrom(as_string_view(data, size));
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in protobuf/src/google/protobuf/message_lite.cc at MessageLite::ParseFromArray and compare its handling with ParseFromString. Verify the behavior using the negative-size reproduction described in the issue; done means negative sizes return false without reaching parsing, while valid array sizes continue to parse normally.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.