# Wire Swift runtime: negative LENGTH_DELIMITED length in skipGroup() crashes any protobuf-decoding service

Wire's Swift ProtoReader.skipGroup() passed a negative LENGTH_DELIMITED varint length to ReadBuffer.readData(count:), trapping Foundation's Data(bytes:count:) and aborting any protobuf-decoding process.

- Payout: Research
- Severity: high
- Program: Wire
- Found by: tonghuaroot
- Disclosed: 2026-09-23
- CVE: CVE-2026-61695
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-86wm-r4c5-2rc9
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9
- Tags: area:mobile domain:mobile domain:oss impact:dos kind:research platform:ghsa sev:high tech:ios technique:incomplete-fix technique:source-review vuln:dos vuln:memory-corruption

**Target:** Swift protobuf runtime (ProtoReader/ReadBuffer) decoding untrusted bytes
**Root cause:** skipGroup() truncated a varint to Int32 without rejecting negative values, and ReadBuffer.verifyAdditional() only checked the upper bound, so a negative count reached Foundation's Data(bytes:count:) which traps.
**Steps:**
1. Build a Swift package depending on square/wire exact 6.4.0 with an EmptyMessage ProtoDecodable that treats all fields as unknown.
2. Craft payload `9b060a80ffffff0f9c06`: field 99 START_GROUP, inner field 1 LENGTH_DELIMITED with 5-byte varint `80 FF FF FF 0F` = -128, then END_GROUP.
3. Call `ProtoDecoder().decode(EmptyMessage.self, from: attackerPayload)`.
4. Observe SIGTRAP in Data.init(bytes:count:) via skipGroup -> readData -> ReadBuffer.readData(count:); negative control with length 0 decodes fine.
**How found:** Noticed the Kotlin/JVM negative-length skipGroup fix (CVE-2026-45799) added a `length < 0` check, then checked the functionally similar Swift ProtoReader.skipGroup() path and found it unpatched, tracing the unvalidated signed length into ReadBuffer's upper-bound-only guard.
**Exploitation:** Send the 10-byte payload as any attacker-controlled protobuf input (request body, websocket frame, queue payload, file) to a service decoding with Wire's Swift runtime; no auth, schema knowledge or valid field number needed since unknown START_GROUP fields are skipped schema-agnostically.
**Insight:** When a fix lands for one language runtime of a multi-platform library, audit the sibling implementations for the same sink; here the bounds check only validated the upper bound, so negative counts slipped past pointer arithmetic into Foundation's trapping Data initializer.

## Proof of concept (copied exactly from the original report)

**10-byte protobuf payload with negative length**

```
let attackerPayload = Data([0x9B, 0x06, 0x0A, 0x80, 0xFF, 0xFF, 0xFF, 0x0F, 0x9C, 0x06])

// Negative control: same group, inner length-delimited field has valid length 0.
let benignPayload = Data([0x9B, 0x06, 0x0A, 0x00, 0x9C, 0x06])
```

**PoC Package.swift pinning Wire 6.4.0**

```
// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "poc",
    platforms: [.macOS(.v12)],
    dependencies: [
        .package(url: "https://github.com/square/wire.git", exact: "6.4.0")
    ],
    targets: [
        .executableTarget(
            name: "poc",
            dependencies: [.product(name: "Wire", package: "wire")],
            path: "Sources/poc"
```

