high · 7.5CVE-2026-63126Sep 17, 2026

CVE-2026-63126: wire-runtime 32-bit Integer Overflow in ByteArrayProtoReader32

Rohit Hatagale
AI Security Researcher, SecureLayer7

Sending a six-byte protobuf message with a max-int length field to any Wire-decoded endpoint crashes the service with an unchecked Java exception instead of a normal decode error, enabling…

Packagecom.squareup.wire:wire-runtime
Ecosystemmaven
Affected<= 6.4.4
Fixed in6.4.5

The problem

ByteArrayProtoReader32.internalNextLengthDelimited() read an attacker-supplied varint length into a signed 32-bit Int and rejected only negative values. A length of 2147483647 is non-negative, so it passed that check. Adding it to the current position (pos + length) then silently overflowed to a large negative number, which became the new limit.

The guard if (limit > pushedLimit) did not catch the wrapped value because it was negative. That corrupted limit then propagated into string, bytes, skip, and scalar reads, where it surfaced as IllegalArgumentException or ArrayIndexOutOfBoundsException. Because callers of ProtoAdapter.decode(ByteArray) typically catch only IOException, these unchecked exceptions escaped the error boundary and crashed request handlers or the whole process.

Proof of concept

A working proof-of-concept for CVE-2026-63126 in com.squareup.wire:wire-runtime, with the exact payload below.

java
# Field 1, length-delimited, varint length = 2147483647 (Int.MAX_VALUE)
# Triggers pos + length signed 32-bit overflow in ByteArrayProtoReader32
# Pre-fix crash: IllegalArgumentException: startIndex: 6 > endIndex: -2147483643

byte[] payload = new byte[] {
    (byte) 0x0A,                                      // tag: field 1, wire type 2 (length-delimited)
    (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x07  // varint 2147483647
};
Person.ADAPTER.decode(payload); // throws IllegalArgumentException pre-fix

// Hex: 0A FF FF FF FF 07

// Alternate: field 3 unknown (triggers ArrayIndexOutOfBoundsException instead)
// Hex: 1A FF FF FF FF 07

The root cause is CWE-190: the code did the bounds check (length < 0) before completing the overflow-safe cursor computation. Because pos and length are both signed 32-bit Ints, pos + length wraps when length is close to Int.MAX_VALUE. The wrapped negative limit is numerically smaller than pushedLimit, so the existing guard silently accepted it.

The patch (commit 25ebcabb, PR #3635) centralises cursor advancement into shared helpers that first compute remaining = limit - pos, then verify length <= remaining, before touching any cursor or limit variable. This keeps all three steps, decode the length, validate it fits, advance the cursor, in the correct order and uses subtraction rather than addition to avoid overflow.

The fix

Upgrade to wire-runtime 6.4.5 (or 7.0.0-alpha04 for alpha users). The fix is in PR #3635, commit 25ebcabb9ab7f12d1d77af75ecbc51726fddc015. If an immediate upgrade is not possible: cap or reject untrusted protobuf byte lengths before passing them to Wire, and treat any unchecked RuntimeException from ProtoAdapter.decode as a fatal malformed-input signal at service trust boundaries so it cannot propagate and crash the process.

Reported by Ta Duc Thien and Duc Anh Nguyen.

References: [1][2][3][4][5][6]

Related research