Skip to content

gix-pack: uncapped Tree::with_capacity from pack-header object count causes OOM/process abort on clone/fetch

Moderate
Byron published GHSA-x862-c2wj-4mwr Sep 25, 2026

Software

gix-pack

Affected versions

<= 0.74.2

Patched versions

>= 0.75.0

Description

Summary

During any clone/fetch, gix-pack sizes its delta-resolution Tree directly from the attacker-controlled 32-bit object count in the 12-byte pack header, before any object body is read. The backing allocation uses Vec::reserve_exact (not try_reserve), so a header claiming a huge object count forces a multi-hundred-gigabyte reservation that aborts the process via handle_alloc_error. This is a pre-authentication, ~12-byte remote denial of service against any application built on gix/gix-pack that clones or fetches from an untrusted remote.

Root Cause

write_data_iter_to_stream (gix-pack/src/index/write/mod.rs:123) does:

let (anticipated_num_objects, upper_bound) = entries.size_hint();
let worst_case_num_objects_after_thin_pack_resolution = upper_bound.unwrap_or(anticipated_num_objects);
let mut tree = Tree::with_capacity(worst_case_num_objects_after_thin_pack_resolution)?;

Tree::with_capacity (gix-pack/src/cache/delta/tree.rs:51) calls exact_vec(num_objects/2) twice, and exact_vec (gix-pack/src/lib.rs:100-104) is:

fn exact_vec<T>(capacity: usize) -> Vec<T> {
    let mut v = Vec::new();
    v.reserve_exact(capacity);   // aborts on failure; does NOT return Err
    v
}

reserve_exact aborts via handle_alloc_error on failure — it does not return Err — so the Result return type of with_capacity does NOT guard the allocation. The upper_bound derives from size_hint(): BytesToEntriesIter::size_hint returns Some(objects_left) where objects_left = the raw num_objects: u32 read from the pack header (gix-pack/src/data/header.rs:18), and LookupRefDeltaObjectsIter::size_hint doubles it. So worst_case = 2 * num_objects.

Impact

num_objects = 0xFFFFFFFF yields two reserve_exact(~4.29e9) allocations of Vec<Item<TreeEntry>> (~76 bytes/element ≈ 326 GB each), attempted before any entry body is decompressed. On default-overcommit Linux (vm.overcommit_memory=0, heuristic mode rejects the oversized single request) and on Windows (commit charge), the allocation fails and the process aborts (SIGABRT). No num_objects sanity check exists before the allocation.

Proof of Concept

Serve an upload-pack whose pack stream begins with a valid header 50 41 43 4B 00 00 00 02 FF FF FF FF ("PACK", version 2, num_objects=0xFFFFFFFF) to a victim running gix clone/fetch. The Tree::with_capacity call attempts ~326 GB reservations and aborts the process before reading any object body.

Attack Chain

  1. Entry: attacker-served (or MITM-redirected) upload-pack → victim gix clone/fetch. Guard: none (serving a pack needs no auth). Bypass proof: N/A.
  2. Check: data::header::decode (header.rs:12-21) accepts any u32 object count. Guard: header validation. Bypass proof: only "PACK" magic + version (2 or 3) are checked; num_objects has no upper bound.
  3. Processing: size_hint() → objects_left as usize → LookupRefDeltaObjectsIter::size_hint doubles it → worst_case = 2 * num_objects (index/write/mod.rs:122).
  4. Sink: Tree::with_capacity(2*num_objects) → exact_vec(n/2) → Vec::reserve_exact (lib.rs:102). Guard: alloc_limit_bytes (added by the GHSA-x494-mj8g-cj27 follow-up fix ace687c). Bypass proof: git show ace687ce -- gix-pack/src/index/write/mod.rs shows the param was added to the function signature and forwarded ONLY to resolve() (line 207); the Tree::with_capacity call at line 123 takes no limit and runs earlier.
  5. Impact: reserve_exact of hundreds of GB fails and calls handle_alloc_error → process abort. Pre-auth, ~12 attacker bytes.

Bypass Evidence

  • git show gix-pack-v0.74.2:gix-pack/src/lib.rs confirms exact_vec uses v.reserve_exact(capacity) with no try_reserve and no cap.
  • git show gix-pack-v0.74.2:gix-pack/src/index/write/mod.rs confirms line 123 Tree::with_capacity(worst_case...)? is unguarded by alloc_limit_bytes.
  • The published advisory GHSA-x494-mj8g-cj27 covers the DIFFERENT allocation sink bytes_to_entries.rs:109 (Vec::with_capacity(decompressed_size)); this Tree::with_capacity sink is untouched by its fix (commit ace687c added alloc_limit_bytes only to resolve()).

Affected Versions

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

Suggested Fix

Cap worst_case_num_objects_after_thin_pack_resolution against a configurable maximum (like git's transfer.maxPackSize), and change exact_vec to use try_reserve_exact and propagate the error through Tree::with_capacity's existing Result.

Caveat

On kernels with vm.overcommit_memory=1 (always-overcommit) the virtual reservation succeeds and the cheap crash degrades (the attacker must then actually stream billions of entries). Hence severity MEDIUM rather than HIGH. On CI/CD auto-clone (UI:N) severity rises to ~7.5.


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

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

CVE ID

No known CVE

Weaknesses

Allocation of Resources Without Limits or Throttling

The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. Learn more on MITRE.

Credits