All vulnerabilities
GHSA-633h-mqjc-8rwg

gix-pack: reachable assertion panic on valid pack version 3 header aborts clone/fetch

gix-pack
CVSS
6.5Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed inNone listedNo source names a fixed version yet
Published25 Sep 2026Updated 25 Sep 2026GitHub

What fixes it

No source in this snapshot names a fixed version.

Description

Summary

gix-pack's pack-header decoder explicitly accepts pack version 3 (returning Ok((Version::V3, num_objects))), but the very next step asserts the version equals 2 with assert_eq! — which is compiled into release builds. A malicious remote that sends a 12-byte pack header declaring version 3 triggers a panic that aborts the clone/fetch. This is a trivial, deterministic, pre-authentication remote denial of service that fires on every clone/fetch.

Root Cause

data::header::decode (gix-pack/src/data/header.rs:12-21):

let kind = match crate::read_u32(&data[ofs..ofs + N32_SIZE]) {
    2 => data::Version::V2,
    3 => data::Version::V3,          // version 3 is ACCEPTED, not an error
    v => return Err(decode::Error::UnsupportedVersion(v)),
};

Then BytesToEntriesIter::new_from_header (gix-pack/src/data/input/bytes_to_entries.rs:58):

let (version, num_objects) = crate::data::header::decode(&header_data)?;
assert_eq!(
    version,
    crate::data::Version::V2,
    "let's stop here if we see undocumented pack formats"
);

assert_eq! (unlike debug_assert_eq!) is NOT compiled out in release. Note the same file uses debug_assert_eq! at line 133, confirming the line-58 hard assert is a distinct, deliberate release-time panic. The author's own message ("let's stop here") shows an intent to stop gracefully, but a panic was chosen instead of an error return.

Impact

The crate is built with panic = "unwind" and there is no catch_unwind in the runtime read path, so the panic unwinds to the fetch/clone caller → operation/process abort = DoS. It runs first-thing on every clone/fetch (both thin and non-thin branches), before any negotiation-specific state.

Proof of Concept

Serve a pack stream beginning 50 41 43 4B 00 00 00 03 <num_objects> ("PACK", version 3) to a victim gix clone/fetch. header::decode returns Ok((V3, ...)), then the assert_eq!(version, V2) panics.

Attack Chain

  1. Entry: attacker serves a pack beginning 50 41 43 4B 00 00 00 03 .... Guard: header::decode version check. Bypass proof: header.rs:14 returns Ok(Version::V3, ...) for byte value 3; only versions ≠ 2,3 error.
  2. Sink: new_from_header runs assert_eq!(version, V2) (bytes_to_entries.rs:58). Guard: should be a graceful UnsupportedVersion error. Bypass proof: it is assert_eq! (not debug_assert_eq!) → panics on V3 in release.
  3. Impact: panic on the fetch/clone thread → process/operation abort = remote DoS. 12 attacker bytes, no negotiation state, no thin-pack precondition.

Bypass Evidence

  • git show gix-pack-v0.74.2:gix-pack/src/data/header.rs confirms version 3 → Ok(Version::V3, ...) on the latest tag.
  • git show gix-pack-v0.74.2:gix-pack/src/data/input/bytes_to_entries.rs confirms the assert_eq!(version, Version::V2, ...) at line 58 (and debug_assert_eq! at line 133, proving the distinction is deliberate).
  • The GHSA-x494-mj8g-cj27 panic-fix commits b69f0a6a/5850141e edited only data/entry/decode.rs; this pack-header assert is untouched.

Affected Versions

gix-pack <= 0.74.2 (latest release tag gix-pack-v0.74.2; code present on HEAD). No post-tag fix.

Suggested Fix

Return a graceful decode::Error::UnsupportedVersion/dedicated error for any non-V2 version in new_from_header instead of assert_eq!.

--- Reported by zx (Jace) — GitHub: @manus-use

Text from GitHub Advisory Database

Affected versions 1

  • gix-pack, other<= 0.74.2GitHub