Skip to content

Protobuf ParseFromArray takes an int size where negative values are UB #28065

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));
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions