protocolbuffers / protocolbuffers/protobuf

Out-of-bounds Read via Negative Size Parameter in MessageLite::ParseFromArray

Open
#27,949 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

**Details
Vulnerability Details Product: Google Protocol Buffers (Protobuf)

Component: C++ runtime library (libprotobuf-lite / libprotobuf)

Affected API(s): google::protobuf::MessageLite::ParseFromArray(const void* data, int size) google::protobuf::MessageLite::ParsePartialFromArray(const void* data, int size) google::protobuf::MessageLite::ParseFromBoundedZeroCopyStream(io::ZeroCopyInputStream* input, int size) google::protobuf::MessageLite::ParsePartialFromBoundedZeroCopyStream(io::ZeroCopyInputStream* input, int size)

Vulnerability Type: Out-of-bounds Read / Pointer Arithmetic Wrapper / Memory Safety Vulnerability

A critical bounds validation vulnerability exists in the C++ implementation of Google Protocol Buffers when parsing messages from raw arrays or bounded streams. The ParseFromArray and ParseFromBoundedZeroCopyStream APIs accept a signed integer size parameter. If a negative value is passed (e.g., due to integer underflow or unchecked external inputs in downstream applications), the library implicitly casts the size to an unsigned type (size_t), leading to a large positive value.

This bypasses size validation checks and causes pointer wrapping during parser initialization inside ParseContext::InitFrom. As a result, pointers are calculated to refer to memory prior to the allocated buffer, leading to out-of-bounds memory reads, undefined behavior, or segmentation faults.

Vulnerability Analysis & Technical Details A. Implicit Conversion of Negative Sizes The vulnerability begins in src/google/protobuf/message_lite.cc in the definition of ParseFromArray:

bool MessageLite::ParseFromArray(const void* data, int size) { return ParseFrom(as_string_view(data, size)); }

Here, as_string_view is defined as:

inline absl::string_view as_string_view(const void* data, int size) { return absl::string_view(static_cast<const char*>(data), size); }

absl::string_view's constructor accepts a length parameter of type size_t (unsigned 64-bit on x86_64). When a negative int size is passed (e.g., -1), C++ implicitly converts it to size_t, converting it to 18446744073709551615ULL (or 0xFFFFFFFFFFFFFFFF).

B. Pointer Wrapping in ParseContext The resulting absl::string_view is then processed by ParseContext::InitFrom in src/google/protobuf/parse_context.h:

const char* InitFrom(absl::string_view flat) { overall_limit_ = 0; if (flat.size() > kSlopBytes) { limit_ = kSlopBytes; limit_end_ = buffer_end_ = flat.data() + flat.size() - kSlopBytes; ...

Since flat.size() is extremely large, the addition flat.data() + flat.size() wraps around in memory. For size = -1, flat.data() + flat.size() - kSlopBytes wraps to flat.data() - 17.

Consequently, the parsing bounds indicator limit_end_ is positioned before the start of the actual input buffer.

C. Downstream Consequences When the parsing loop (internal::TcParser::ParseLoop) runs, it operates under the assumption that ptr (starting at flat.data()) is less than limit_end_. Since limit_end_ is actually located before flat.data(), bounds checking is entirely compromised. This allows the parser to read past bounds, causing memory leaks/disclosures (if data is returned to the user) or immediate crashes (segmentation faults).

Proof of Concept (PoC) Below is a minimal C++ program illustrating how the vulnerability can be triggered.

#include #include #include "google/protobuf/message_lite.h"

// Define a simple dummy message class inheriting from MessageLite for PoC purposes class PoCMessage : public google::protobuf::MessageLite { public: PoCMessage() {} ~PoCMessage() override {}

// Standard MessageLite interface overrides... std::string GetTypeName() const override { return "PoCMessage"; } PoCMessage* New(google::protobuf::Arena* arena) const override { return google::protobuf::Arena::Create(arena); } void Clear() override {} bool IsInitialized() const override { return true; } size_t ByteSizeLong() const override { return 0; }

const char* _InternalParse(const char* ptr, google::protobuf::internal::ParseContext* ctx) override { // Parser loop simulation return ptr; } };

int main() { PoCMessage message; char dummy_data[16] = {0};

std::cout << "[*] Parsing with size = -1..." << std::endl;

// This will trigger implicit cast, pointer wrap, and undefined behavior bool success = message.ParseFromArray(dummy_data, -1);

std::cout << "[*] Parse finished. Success status: " << (success ? "TRUE" : "FALSE") << std::endl; return 0; }

Attack scenario
Threat Model & Security Impact Attack Vector: An attacker provides a payload where the size is computed/supplied dynamically. If an application calculates the payload size incorrectly (leading to an integer underflow / negative value) and directly passes it to ParseFromArray, the attacker can trigger this vulnerability.

Impact: Denial of Service (DoS): High probability of application crashes via segmentation faults. Information Disclosure / Out-of-bounds Read: Under specific memory layouts, the parser may parse and extract adjacent heap/stack memory, returning structured protobuf messages containing sensitive process memory to the attacker.

Recommended Mitigation Add explicit size validation checks to the affected entry-points in src/google/protobuf/message_lite.cc and related files:

bool MessageLite::ParseFromArray(const void* data, int size) {

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

if (ABSL_PREDICT_FALSE(size < 0)) return false; return ParseFrom(as_string_view(data, size)); }
bool MessageLite::ParseFromBoundedZeroCopyStream(io::ZeroCopyInputStream* input, int size) {

if (ABSL_PREDICT_FALSE(size < 0)) return false; return ParseFrom(internal::BoundedZCIS{input, 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 with the listed ParseFromArray and ParseFromBoundedZeroCopyStream entry points in src/google/protobuf/message_lite.cc, then read ParseContext::InitFrom in src/google/protobuf/parse_context.h. Check how negative size values reach the parser and cover all four affected APIs. Done means negative sizes are rejected without pointer wrapping or out-of-bounds reads.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.