{"id":"GHSA-9rm7-3qhh-h2mc","summary":"Wire: Unauthenticated decoder crash via 32-bit length integer overflow in ByteArrayProtoReader32 (incomplete fix of CVE-2026-45799)","details":"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.\n\nIn 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 \u003e pushedLimit)` guard did not catch this because the overflowed value was negative.\n\nThat 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.\n\nThe 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.\n\nWhile auditing for the same bug class, related boundary flaws were also found and fixed:\n\n- 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.\n- 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.\n- 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.)\n- Swift nested-message decoding and packed-repeated decoding computed end pointers from untrusted lengths before validating that the bytes were present.\n- Swift packed-repeated decoding reserved array capacity from an untrusted length before validating that the length existed in the current buffer.\n- 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.\n\nThe 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.\n\n### Impact\n\nAn 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.\n\nKnown impact:\n\n- Availability impact only.\n- No known confidentiality impact.\n- No known integrity impact.\n- No known code execution.\n\nAttack requirements:\n\n- The application decodes attacker-controlled protobuf bytes with Wire.\n- No authentication is required if the decoding endpoint is reachable without authentication.\n- A single short malformed protobuf payload is sufficient for the Kotlin byte-array fast path.\n\nMost directly affected Kotlin entry points:\n\n- `ProtoAdapter.decode(ByteArray)`\n- `ProtoAdapter.decode(ByteString)`\n\nAdjacent Kotlin path hardened by this fix:\n\n- `ProtoAdapter.decode(BufferedSource)`\n- direct use of `ProtoReader`\n\nAffected Swift entry points:\n\n- Swift `ProtoDecoder` and `ProtoReader` APIs when decoding attacker-controlled `Data` or buffers.\n\n### Proof of concept and regression payloads\n\nThese 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.\n\n#### Kotlin byte-array known length-delimited field\n\nHex:\n\n```text\n0A FF FF FF FF 07\n```\n\nMeaning:\n\n- `0A`: field 1, length-delimited\n- `FF FF FF FF 07`: varint length `2147483647`\n\nPre-fix behavior observed through `Person.ADAPTER.decode(byteArray)`:\n\n```text\njava.lang.IllegalArgumentException: startIndex: 6 \u003e endIndex: -2147483643\n```\n\nExpected fixed behavior:\n\n```text\nIOException / EOFException\n```\n\n#### Kotlin byte-array unknown length-delimited field\n\nHex:\n\n```text\n1A FF FF FF FF 07\n```\n\nMeaning:\n\n- `1A`: field 3, length-delimited\n- `FF FF FF FF 07`: varint length `2147483647`\n\nPre-fix behavior observed:\n\n```text\nArrayIndexOutOfBoundsException\n```\n\nExpected fixed behavior:\n\n```text\nIOException / EOFException\n```\n\n#### Kotlin byte-array skipped group containing oversized positive length\n\nHex:\n\n```text\n0B 0A FF FF FF FF 07 0C\n```\n\nMeaning:\n\n- `0B`: start group, field 1\n- `0A`: nested field 1, length-delimited\n- `FF FF FF FF 07`: varint length `2147483647`\n- `0C`: end group, field 1\n\nExpected fixed behavior:\n\n```text\nIOException / EOFException\n```\n\n#### Kotlin current-message-limit fixed32 boundary\n\nHex:\n\n```text\n02 0D 05 00 00 00\n```\n\nMeaning:\n\n- `02`: outer length-delimited message length is 2 bytes\n- `0D`: nested field 1, fixed32\n- `05 00 00 00`: enough bytes remain in the underlying source, but not inside the current logical message limit\n\nExpected fixed behavior:\n\n```text\nEOFException\n```\n\nThis covers the invariant that scalar reads must not cross the current length-delimited message boundary even when the underlying source has more bytes available.\n\n#### Swift tag-only varint value\n\nHex:\n\n```text\n08\n```\n\nMeaning:\n\n- `08`: field 1, varint\n- Missing varint value byte\n\nPre-fix risk:\n\n- `ReadBuffer.readVarint()` could dereference `pointer.pointee` before verifying that a byte remained.\n\nExpected fixed behavior:\n\n```text\nProtoDecoder.Error.unexpectedEndOfData\n```\n\n#### Swift nested message with oversized positive length\n\nHex:\n\n```text\n12 FF FF FF FF 07\n```\n\nMeaning:\n\n- `12`: field 2, length-delimited\n- `FF FF FF FF 07`: varint length `2147483647`\n\nPre-fix risk:\n\n- Nested message decoding computed an end pointer from an untrusted length before proving that the buffer contained that many bytes.\n\nExpected fixed behavior:\n\n```text\nProtoDecoder.Error.unexpectedEndOfData\n```\n\n#### Swift packed repeated field with oversized positive length\n\nHex:\n\n```text\n0A FF FF FF FF 07\n```\n\nMeaning:\n\n- `0A`: field 1, length-delimited packed repeated field\n- `FF FF FF FF 07`: varint length `2147483647`\n\nPre-fix risk:\n\n- Packed repeated decoding could reserve capacity based on an untrusted length before proving the bytes were present.\n\nExpected fixed behavior:\n\n```text\nProtoDecoder.Error.unexpectedEndOfData\n```\n\n#### Swift size-delimited stream with unrepresentable size\n\nHex:\n\n```text\nFF FF FF FF FF FF FF FF FF 01\n```\n\nMeaning:\n\n- Size-delimited message length varint `UInt64.max`\n\nPre-fix risk:\n\n- `ProtoDecoder.decodeSizeDelimited(_:from:)` converted the untrusted `UInt64` to `Int` without exactness checking.\n\nExpected fixed behavior:\n\n```text\nProtoDecoder.Error.unexpectedEndOfData\n```\n\n### Root cause\n\nThe vulnerable code mixed three operations that must remain separate:\n\n1. Decode an untrusted protobuf length.\n2. Validate that the length is non-negative and fits within the current logical message boundary.\n3. Advance the cursor, pointer, limit, slice, or allocation based on that length.\n\nIn 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.\n\n### Fix\n\nThe fix centralizes checked cursor and pointer advancement.\n\nKotlin changes:\n\n- `ByteArrayProtoReader32` now validates constructor invariants for `pos` and `limit`.\n- `ByteArrayProtoReader32` now uses shared helpers to:\n  - reject negative lengths,\n  - compute checked limits,\n  - compute remaining bytes in the current logical limit,\n  - validate before `skip`,\n  - validate before string and bytes reads,\n  - validate before fixed32 and fixed64 reads.\n- `ProtoReader` now mirrors the same logical-boundary model for:\n  - length-delimited limit calculation,\n  - skipped length-delimited fields,\n  - varint reads,\n  - fixed32 reads,\n  - fixed64 reads,\n  - current-message remaining-byte calculations.\n\nSwift changes:\n\n- `ReadBuffer` now computes checked end pointers only after confirming `count \u003e= 0` and `count \u003c= remaining`.\n- `ReadBuffer.readVarint()` verifies one byte remains before each byte dereference.\n- `ReadBuffer.readBuffer(count:)`, `readData(count:)`, `readFixed32()`, and `readFixed64()` compute the checked new pointer before reading and advancing.\n- `ProtoReader.beginMessage()` validates nested message lengths before storing a message-end pointer.\n- Packed repeated decoding validates the packed field length before preallocation and before constructing the loop boundary.\n- `ProtoDecoder.decodeSizeDelimited(_:from:)` converts sizes with `Int(exactly:)` and verifies that the full message bytes exist before constructing a child buffer.\n\nFixed in PR #3635:\n\n- https://github.com/square/wire/pull/3635\n- Fix commit `25ebcabb9ab7f12d1d77af75ecbc51726fddc015`\n\n### Workarounds\n\nThe recommended remediation is to upgrade to a patched release.\n\nPartial mitigations if an immediate upgrade is not possible:\n\n- Reject or cap untrusted protobuf message sizes before passing bytes to Wire.\n- Prefer decoding from a bounded source where possible rather than decoding unbounded attacker-controlled byte arrays.\n- Treat unchecked runtime exceptions from protobuf decode as malformed-input failures at service trust boundaries so they cannot crash the process.\n- For Swift, do not pass untrusted size-delimited streams or `Data` directly to affected decoders without an outer size cap and exception/error boundary.\n\nThese mitigations reduce exposure but do not fully fix the parser bugs.\n\n### Detection\n\nA crash or error may contain one of the following symptoms when processing malformed protobuf bytes:\n\n```text\nIllegalArgumentException: startIndex: 6 \u003e endIndex: -2147483643\nArrayIndexOutOfBoundsException\nIndexOutOfBoundsException\nunexpected unchecked RuntimeException during ProtoAdapter.decode(ByteArray)\nSwift trap during Int conversion from an untrusted protobuf size\nSwift unexpected pointer/buffer failure while reading malformed varints or length-delimited values\n```\n\nThe 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.\n\n### Verification\n\nRegression tests added:\n\n- `ProtoReader32Test.lengthDelimitedRejectsPositiveLengthOverflow`\n- `ProtoReader32Test.fixed32CannotReadPastLengthDelimitedLimit`\n- `ProtoReaderTest.fixed32CannotReadPastLengthDelimitedLimit`\n- `ProtoReaderTests.testReadVarintRejectsMissingValue`\n- `ProtoReaderTests.testNestedMessageRejectsOversizedLength`\n- `ProtoReaderTests.testPackedRepeatedRejectsOversizedLengthBeforePreallocation`\n- `ProtoDecoderTests.testDecodeSizeDelimitedRejectsUnrepresentableSize`\n\nFocused verification command:\n\n```bash\n./gradlew :wire-runtime:jvmTest :wire-runtime-swift:test\n```\n\nExpected result:\n\n```text\nBUILD SUCCESSFUL\n```\n\n### Relationship to related advisories\n\nThis is a distinct vulnerability from the negative-length issues. It is a\ndifferent bug class — a positive, non-negative length (for example\n`2147483647`) that passes the existing `length \u003c 0` check but still overflows\nthe signed 32-bit cursor or crosses the current message boundary — and it has\na separate fix (PR #3635, not the negative-length PRs).\n\n- `CVE-2026-45799` / `GHSA-7xpr-hc2w-34m9` fixed the original Kotlin/JVM\n  negative-length skipped-group crash (Wire `6.3.0`). The non-negative\n  overflow described here was not covered by that check and remained\n  exploitable through `6.4.4`.\n- `GHSA-86wm-r4c5-2rc9` / `CVE-2026-61695` covers the Swift negative-length\n  `skipGroup()` crash (PR #3616). The Swift hardening in this advisory\n  (PR #3635) instead addresses positive/oversized-length overflow, buffer\n  over-read, and unrepresentable-size conversions in the Swift readers.","aliases":["CVE-2026-63126"],"modified":"2026-09-17T15:00:06.855019559Z","published":"2026-09-17T14:52:52Z","database_specific":{"github_reviewed_at":"2026-09-17T14:52:52Z","nvd_published_at":"2026-09-16T19:17:24Z","cwe_ids":["CWE-190"],"severity":"HIGH","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/square/wire/security/advisories/GHSA-9rm7-3qhh-h2mc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-63126"},{"type":"WEB","url":"https://github.com/square/wire/pull/3635"},{"type":"WEB","url":"https://github.com/square/wire/commit/082d5d83cec57ef68f1dd7d3e3d1d641c1fb670f"},{"type":"WEB","url":"https://github.com/square/wire/commit/25ebcabb9ab7f12d1d77af75ecbc51726fddc015"},{"type":"PACKAGE","url":"https://github.com/square/wire"},{"type":"WEB","url":"https://github.com/square/wire/releases/tag/6.4.5"},{"type":"WEB","url":"https://github.com/square/wire/releases/tag/7.0.0-alpha04"}],"affected":[{"package":{"name":"com.squareup.wire:wire-runtime","ecosystem":"Maven","purl":"pkg:maven/com.squareup.wire/wire-runtime"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.4.5"}]}],"versions":["1.0.0","1.0.1","1.1.0","1.1.1","1.2.0","1.3.0","1.3.1","1.3.2","1.3.3","1.4.0","1.5.0","1.5.1","1.5.2","1.6.0","1.6.1","1.7.0","1.8.0","2.0.0","2.0.0-BETA1","2.0.0-BETA10","2.0.0-BETA2","2.0.0-BETA3","2.0.0-BETA4","2.0.0-BETA5","2.0.0-BETA6","2.0.0-BETA7","2.0.0-BETA8","2.0.0-BETA9","2.0.1","2.0.2","2.0.3","2.1.0","2.1.1","2.1.2","2.2.0","2.3.0-RC1","3.0.0","3.0.0-alpha01","3.0.0-alpha02","3.0.0-alpha03","3.0.0-rc01","3.0.0-rc02","3.0.0-rc03","3.0.1","3.0.2","3.0.3","3.1.0","3.2.0","3.2.1","3.2.2","3.3.0","3.3.0-alpha1","3.4.0","3.5.0","3.6.0","3.6.1","3.7.0","3.7.1","4.0.0","4.0.0-alpha.1","4.0.0-alpha.10","4.0.0-alpha.11","4.0.0-alpha.12","4.0.0-alpha.15","4.0.0-alpha.16","4.0.0-alpha.17","4.0.0-alpha.18","4.0.0-alpha.19","4.0.0-alpha.2","4.0.0-alpha.20","4.0.0-alpha.3","4.0.0-alpha.4","4.0.0-alpha.5","4.0.0-alpha.6","4.0.0-alpha.7","4.0.0-alpha.8","4.0.0-alpha.9","4.0.1","4.1.0","4.1.1","4.2.0","4.3.0","4.4.0","4.4.1","4.4.2","4.4.3","4.5.0","4.5.1","4.5.2","4.5.3","4.5.4","4.5.5","4.5.6","4.6.0","4.6.1","4.6.2","4.7.0","4.7.1","4.7.2","4.8.0","4.8.1","4.9.0","4.9.1","4.9.11","4.9.2","4.9.3","4.9.4","4.9.5","4.9.6","4.9.7","4.9.8","4.9.9","5.0.0","5.0.0-alpha01","5.0.0-alpha02","5.0.0-alpha03","5.0.0-alpha04","5.1.0","5.2.0","5.2.1","5.3.0","5.3.1","5.3.10","5.3.11","5.3.2","5.3.3","5.3.4","5.3.5","5.3.6","5.3.7","5.3.8","5.3.9","5.4.0","5.5.0","5.5.1","6.0.0","6.0.0-alpha01","6.0.0-alpha02","6.0.0-alpha03","6.1.0","6.2.0","6.3.0","6.4.0","6.4.1"],"database_specific":{"last_known_affected_version_range":"\u003c= 6.4.4","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-9rm7-3qhh-h2mc/GHSA-9rm7-3qhh-h2mc.json"}},{"package":{"name":"com.squareup.wire:wire-runtime","ecosystem":"Maven","purl":"pkg:maven/com.squareup.wire/wire-runtime"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"7.0.0-alpha01"},{"fixed":"7.0.0-alpha04"}]}],"versions":["7.0.0-alpha01","7.0.0-alpha02","7.0.0-alpha03"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-9rm7-3qhh-h2mc/GHSA-9rm7-3qhh-h2mc.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}]}