GHSA-9rm7-3qhh-h2mc: Integer Overflow

Published Sep 17, 2026
·
Updated

Wire's protobuf decoders did not consistently validate attacker-controlled length-delimited sizes against the current reader bounds before computing cursor, limit, or pointer positions.

In the Kotlin runtime, ProtoAdapter.decode(ByteArray) and ProtoAdapter.decode(ByteString) use the ProtoReader32 fast path implemented by ByteArrayProtoReader32. In ByteArrayProtoReader32.internalNextLengthDelimited(), Wire read an untrusted varint length into an Int and rejected only negative values. A length such as 2147483647 is non-negative, so it passed that check, but pos + length overflowed the signed 32-bit cursor and produced a negative limit. The following if (limit > pushedLimit) guard did not catch this because the overflowed value was negative.

That invalid limit then reached string, bytes, skip, and scalar-reading paths as an invalid byte count or invalid range. Instead of failing as a checked decode error such as IOException, malformed input could throw unchecked runtime exceptions including IllegalArgumentException and ArrayIndexOutOfBoundsException. Applications commonly treat malformed protobuf input as an expected decode failure; unchecked runtime exceptions escaping that boundary can crash request handling or the process.

The original report is a sibling of the negative-length skipped-group bug fixed as CVE-2026-45799. It is not the same bug. The length in this advisory is positive, and the overflow occurs when setting a length-delimited message limit, not only when skipping a group.

While auditing for the same bug class, related boundary flaws were also found and fixed:

- Kotlin ProtoReader now validates logical message limits before varint, fixed32, fixed64, and skip operations. The originally reported byte-array overflow payload did not reproduce as the same signed overflow in ProtoReader, because that reader tracks positions as Long, but the streaming reader still needed consistent current-message-limit enforcement. - Swift ReadBuffer.readVarint() read pointer.pointee before checking that one byte remained. A tag-only varint field could read past the end of the buffer. - Swift ReadBuffer.verifyAdditional(count:) formed pointer.advanced(by: count) before proving the requested count fit within the remaining buffer, so pointer arithmetic ran before the bounds were established. (The distinct Swift negative-length skipGroup() crash is tracked separately as GHSA-86wm-r4c5-2rc9 / CVE-2026-61695; this advisory covers the positive/oversized-length boundary failures.) - Swift nested-message decoding and packed-repeated decoding computed end pointers from untrusted lengths before validating that the bytes were present. - Swift packed-repeated decoding reserved array capacity from an untrusted length before validating that the length existed in the current buffer. - Swift size-delimited decoding converted an untrusted UInt64 varint size to Int without exactness or availability checks. On platforms where the value is not representable, this could trap.

The fix enforces a single invariant across the hardened readers: every decoded or skipped byte count must be non-negative and no larger than the remaining bytes in the current logical message limit before any cursor, pointer, limit, allocation, or slice is advanced.

Impact

An attacker who can supply protobuf bytes to an application using affected Wire decoders can trigger a denial of service by causing decode to fail with unchecked runtime failures or traps rather than normal malformed-input decode errors.

Known impact:

- Availability impact only. - No known confidentiality impact. - No known integrity impact. - No known code execution.

Attack requirements:

- The application decodes attacker-controlled protobuf bytes with Wire. - No authentication is required if the decoding endpoint is reachable without authentication. - A single short malformed protobuf payload is sufficient for the Kotlin byte-array fast path.

Most directly affected Kotlin entry points:

- ProtoAdapter.decode(ByteArray) - ProtoAdapter.decode(ByteString)

Adjacent Kotlin path hardened by this fix:

- ProtoAdapter.decode(BufferedSource) - direct use of ProtoReader

Affected Swift entry points:

- Swift ProtoDecoder and ProtoReader APIs when decoding attacker-controlled Data or buffers.

Proof of concept and regression payloads

These payloads are intentionally small and should be treated as malformed protobuf input. After the fix, they must fail with normal decode errors such as IOException, EOFException, or ProtoDecoder.Error.unexpectedEndOfData, not unchecked runtime exceptions, traps, out-of-bounds reads, or large allocations.

Kotlin byte-array known length-delimited field

Hex:

text 0A FF FF FF FF 07

Meaning:

- 0A: field 1, length-delimited - FF FF FF FF 07: varint length 2147483647

Pre-fix behavior observed through Person.ADAPTER.decode(byteArray):

text java.lang.IllegalArgumentException: startIndex: 6 > endIndex: -2147483643

Expected fixed behavior:

text IOException / EOFException

Kotlin byte-array unknown length-delimited field

Hex:

text 1A FF FF FF FF 07

Meaning:

- 1A: field 3, length-delimited - FF FF FF FF 07: varint length 2147483647

Pre-fix behavior observed:

text ArrayIndexOutOfBoundsException

Expected fixed behavior:

text IOException / EOFException

Kotlin byte-array skipped group containing oversized positive length

Hex:

text 0B 0A FF FF FF FF 07 0C

Meaning:

- 0B: start group, field 1 - 0A: nested field 1, length-delimited - FF FF FF FF 07: varint length 2147483647 - 0C: end group, field 1

Expected fixed behavior:

text IOException / EOFException

Kotlin current-message-limit fixed32 boundary

Hex:

text 02 0D 05 00 00 00

Meaning:

- 02: outer length-delimited message length is 2 bytes - 0D: nested field 1, fixed32 - 05 00 00 00: enough bytes remain in the underlying source, but not inside the current logical message limit

Expected fixed behavior:

text EOFException

This covers the invariant that scalar reads must not cross the current length-delimited message boundary even when the underlying source has more bytes available.

Swift tag-only varint value

Hex:

text 08

Meaning:

- 08: field 1, varint - Missing varint value byte

Pre-fix risk:

- ReadBuffer.readVarint() could dereference pointer.pointee before verifying that a byte remained.

Expected fixed behavior:

text ProtoDecoder.Error.unexpectedEndOfData

Swift nested message with oversized positive length

Hex:

text 12 FF FF FF FF 07

Meaning:

- 12: field 2, length-delimited - FF FF FF FF 07: varint length 2147483647

Pre-fix risk:

- Nested message decoding computed an end pointer from an untrusted length before proving that the buffer contained that many bytes.

Expected fixed behavior:

text ProtoDecoder.Error.unexpectedEndOfData

Swift packed repeated field with oversized positive length

Hex:

text 0A FF FF FF FF 07

Meaning:

- 0A: field 1, length-delimited packed repeated field - FF FF FF FF 07: varint length 2147483647

Pre-fix risk:

- Packed repeated decoding could reserve capacity based on an untrusted length before proving the bytes were present.

Expected fixed behavior:

text ProtoDecoder.Error.unexpectedEndOfData

Swift size-delimited stream with unrepresentable size

Hex:

text FF FF FF FF FF FF FF FF FF 01

Meaning:

- Size-delimited message length varint UInt64.max

Pre-fix risk:

- ProtoDecoder.decodeSizeDelimited(:from:) converted the untrusted UInt64 to Int without exactness checking.

Expected fixed behavior:

text ProtoDecoder.Error.unexpectedEndOfData

Root cause

The vulnerable code mixed three operations that must remain separate:

1. Decode an untrusted protobuf length. 2. Validate that the length is non-negative and fits within the current logical message boundary. 3. Advance the cursor, pointer, limit, slice, or allocation based on that length.

In the vulnerable paths, step 3 happened before step 2 was complete. For Kotlin ByteArrayProtoReader32, this caused signed integer wraparound in pos + length. For Swift, related pointer and allocation operations could be performed before proving the requested bytes existed.

Fix

The fix centralizes checked cursor and pointer advancement.

Kotlin changes:

- ByteArrayProtoReader32 now validates constructor invariants for pos and limit. - ByteArrayProtoReader32 now uses shared helpers to: - reject negative lengths, - compute checked limits, - compute remaining bytes in the current logical limit, - validate before skip, - validate before string and bytes reads, - validate before fixed32 and fixed64 reads. - ProtoReader now mirrors the same logical-boundary model for: - length-delimited limit calculation, - skipped length-delimited fields, - varint reads, - fixed32 reads, - fixed64 reads, - current-message remaining-byte calculations.

Swift changes:

- ReadBuffer now computes checked end pointers only after confirming count >= 0 and count <= remaining. - ReadBuffer.readVarint() verifies one byte remains before each byte dereference. - ReadBuffer.readBuffer(count:), readData(count:), readFixed32(), and readFixed64() compute the checked new pointer before reading and advancing. - ProtoReader.beginMessage() validates nested message lengths before storing a message-end pointer. - Packed repeated decoding validates the packed field length before preallocation and before constructing the loop boundary. - ProtoDecoder.decodeSizeDelimited(:from:) converts sizes with Int(exactly:) and verifies that the full message bytes exist before constructing a child buffer.

Fixed in PR #3635:

- https://github.com/square/wire/pull/3635 - Fix commit 25ebcabb9ab7f12d1d77af75ecbc51726fddc015

Workarounds

The recommended remediation is to upgrade to a patched release.

Partial mitigations if an immediate upgrade is not possible:

- Reject or cap untrusted protobuf message sizes before passing bytes to Wire. - Prefer decoding from a bounded source where possible rather than decoding unbounded attacker-controlled byte arrays. - Treat unchecked runtime exceptions from protobuf decode as malformed-input failures at service trust boundaries so they cannot crash the process. - For Swift, do not pass untrusted size-delimited streams or Data directly to affected decoders without an outer size cap and exception/error boundary.

These mitigations reduce exposure but do not fully fix the parser bugs.

Detection

A crash or error may contain one of the following symptoms when processing malformed protobuf bytes:

text IllegalArgumentException: startIndex: 6 > endIndex: -2147483643 ArrayIndexOutOfBoundsException IndexOutOfBoundsException unexpected unchecked RuntimeException during ProtoAdapter.decode(ByteArray) Swift trap during Int conversion from an untrusted protobuf size Swift unexpected pointer/buffer failure while reading malformed varints or length-delimited values

The absence of these exact messages does not prove safety. Any unchecked exception, trap, or process crash while decoding malformed length-delimited protobuf input should be investigated.

Verification

Regression tests added:

- ProtoReader32Test.lengthDelimitedRejectsPositiveLengthOverflow - ProtoReader32Test.fixed32CannotReadPastLengthDelimitedLimit - ProtoReaderTest.fixed32CannotReadPastLengthDelimitedLimit - ProtoReaderTests.testReadVarintRejectsMissingValue - ProtoReaderTests.testNestedMessageRejectsOversizedLength - ProtoReaderTests.testPackedRepeatedRejectsOversizedLengthBeforePreallocation - ProtoDecoderTests.testDecodeSizeDelimitedRejectsUnrepresentableSize

Focused verification command:

bash ./gradlew :wire-runtime:jvmTest :wire-runtime-swift:test

Expected result:

text BUILD SUCCESSFUL

Relationship to related advisories

This is a distinct vulnerability from the negative-length issues. It is a different bug class — a positive, non-negative length (for example 2147483647) that passes the existing length < 0 check but still overflows the signed 32-bit cursor or crosses the current message boundary — and it has a separate fix (PR #3635, not the negative-length PRs).

- CVE-2026-45799 / GHSA-7xpr-hc2w-34m9 fixed the original Kotlin/JVM negative-length skipped-group crash (Wire 6.3.0). The non-negative overflow described here was not covered by that check and remained exploitable through 6.4.4. - GHSA-86wm-r4c5-2rc9 / CVE-2026-61695 covers the Swift negative-length skipGroup() crash (PR #3616). The Swift hardening in this advisory (PR #3635) instead addresses positive/oversized-length overflow, buffer over-read, and unrepresentable-size conversions in the Swift readers.

Affected Software

2 affected componentsFixes available
maven/com.squareup.wire:wire-runtime>=7.0.0-alpha01<7.0.0-alpha04
7.0.0-alpha04
maven/com.squareup.wire:wire-runtime<=6.4.4
6.4.5

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade maven/com.squareup.wire:wire-runtime to a version that resolves this vulnerability.

    Fixed in 7.0.0-alpha04
  2. Upgrade

    Upgrade maven/com.squareup.wire:wire-runtime to a version that resolves this vulnerability.

    Fixed in 6.4.5
  3. Upgrade

    Upgrade Wire (Kotlin/JVM) ByteArrayProtoReader32 / ProtoReader32 fast path to a version that resolves this vulnerability.

    Patch PR #3635
  4. Compensating control

    For Swift, do not pass untrusted size-delimited streams or `Data` directly to affected decoders without an outer size cap and exception/error boundary.

  5. Operational

    Treat unchecked runtime exceptions, traps, out-of-bounds reads, or large allocations while decoding malformed length-delimited protobuf input as malformed-input failures at service trust boundaries; investigate any unchecked exception (e.g., IllegalArgumentException, ArrayIndexOutOfBoundsException) that escapes decode handling and could crash request handling or the process.

Event History

Sep 17, 2026
Advisory Published
via GitHub·02:52 PM
Data Sourced
via GitHub·02:52 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which applications are exposed to this issue?

Applications using Wire's Kotlin runtime and decoding attacker-controlled protobuf data through ProtoAdapter.decode(ByteArray) or ProtoAdapter.decode(ByteString) are exposed to the described fast path. The affected component is maven/com.squareup.wire:wire-runtime.

2

What must an attacker provide to trigger the failure?

An attacker needs to supply malformed protobuf input containing a length-delimited field with a non-negative varint length that causes the signed 32-bit pos + length calculation to overflow, such as 2147483647. No privileges or user interaction are indicated by the supplied severity vector.

3

What is the practical impact during decoding?

Malformed input may cause unchecked runtime exceptions, including IllegalArgumentException and ArrayIndexOutOfBoundsException, rather than a checked IOException decode failure. This can disrupt applications that treat malformed protobuf messages as expected decoding errors but do not handle these runtime exceptions.

4

How can teams identify potentially affected code paths?

Review uses of the Wire Kotlin runtime's ProtoAdapter.decode(ByteArray) and ProtoAdapter.decode(ByteString), especially where protobuf bytes originate from network clients, external services, files, or other untrusted sources. Test those paths with malformed length-delimited fields using very large non-negative encoded lengths.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203