# Varint length overflow bypasses bounds

Unsigned varint length cast to signed/narrow int, then checked only against an upper bound

_Area: Memory corruption and native code. Seen in 2 disclosed reports; latest 2026-10-20. Updated 2026-11-02._

## How it works
A length or repeat count is decoded from a varint (unsigned) and then stored in a signed `int` or a narrower type like `Int32`. The validation that follows compares it against an upper bound only — `d.repeat > len(dict)`, `count > buffer.count`, or an allocation sized from the field. A value above the signed maximum wraps negative, and a negative number trivially passes any `> positive` check. The tainted negative then flows into index arithmetic, pointer offsets, or an allocation size, producing an out-of-bounds access or a trap [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md)[E3](https://bugbountylibrary.com/r/h1%3A134880.md).

## Where it shows up
- Varint/uvarint decode immediately followed by a cast to `int`, `Int32`, or similar [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md)
- Bounds checks written as a single upper-bound comparison with no `length < 0` guard [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md)
- Length fields used to size an allocation before the input is confirmed to contain that many bytes [E3](https://bugbountylibrary.com/r/h1%3A134880.md)
- Decoders that cannot know total input length upfront (BIO/stream style) [E3](https://bugbountylibrary.com/r/h1%3A134880.md)
- Negative values reaching `unsafe.Pointer` arithmetic, `Data(bytes:count:)`, or `make([]byte, n)` [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md)

## How to review
For every parsed length, ask: what is the type after the cast, and does the check reject negatives? The invariant: a decoded length must be validated as `0 <= n <= remaining_input` before any allocation, index, or pointer use. Check the comparison operator and the sign of the variable — a check against a positive length does not constrain a negative. Trace the value to its sink: index math, pointer offset, allocation size. For stream decoders, confirm lengths are validated incrementally against actual bytes read, not trusted upfront [E3](https://bugbountylibrary.com/r/h1%3A134880.md).

## How finders spotted it
- Read the uvarint-to-int conversion and the following `> len(dict)` check, noticing a negative int always passes a positive-length comparison; then traced the negative into index math and unchecked pointer arithmetic [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md)
- Noticed a sibling runtime (Kotlin/JVM) got a `length < 0` fix, then checked the functionally similar Swift path and found it unpatched, tracing the signed length into an upper-bound-only guard [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md)
- Crafted minimal encodings with oversized length fields and watched CPU, memory and swap consumption spike [E3](https://bugbountylibrary.com/r/h1%3A134880.md)

## Variants seen
- Signed overflow in a dictionary repeat count wrapping negative, then OOB pointer read [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md)
- Varint truncated to Int32 in a skip path, negative count trapping Foundation [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md)
- ASN.1 length field trusted for allocation, huge buffer from a 7-byte header [E3](https://bugbountylibrary.com/r/h1%3A134880.md)

## Impact and severity
Crash (SIGSEGV, trap) of any process decoding untrusted bytes — denial of service against services and libraries [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md). In E3 the oversized allocation consumed CPU, memory and swap, degrading the host [E3](https://bugbountylibrary.com/r/h1%3A134880.md). Severity rises with reach: a shared runtime used by many services, or a decoder on a network/parse boundary, multiplies the blast radius [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md)[E3](https://bugbountylibrary.com/r/h1%3A134880.md).

## Seen in real software
Open-source packages and projects, and products, where this pattern was found. If the target runs one, compare the version it runs with the fix.
- **github.com/klauspost/compress (go)** <= 1.18.6, fixed in 1.18.7: Compress/s2: Integer Overflow or Wraparound and Out-of-bounds Write in compress (CVE-2026-63209). Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md. Fix: https://github.com/klauspost/compress/commit/539243b8823ee8f03e49969823d57c348c917536
- **github.com/square/wire (swift)** <= 6.4.0, fixed in 6.4.1: Wire Swift runtime: negative LENGTH_DELIMITED length in skipGroup() crashes any protobuf-decoding service (CVE-2026-61695). Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md. Fix: https://github.com/square/wire/commit/24043b6b3a5e5974a978f2745b76d50b31407c1c

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-259r-337f-4rfw.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-86wm-r4c5-2rc9.md
- E3: https://bugbountylibrary.com/r/h1%3A134880.md
