{"schema":"threatline.record/2","record":{"id":"GHSA-x862-c2wj-4mwr","aliases":["GHSA-x862-c2wj-4mwr"],"summary":"gix-pack: uncapped Tree::with_capacity from pack-header object count causes OOM/process abort on clone/fetch","details":"## Summary\r\nDuring 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.\r\n\r\n## Root Cause\r\n`write_data_iter_to_stream` (gix-pack/src/index/write/mod.rs:123) does:\r\n```rust\r\nlet (anticipated_num_objects, upper_bound) = entries.size_hint();\r\nlet worst_case_num_objects_after_thin_pack_resolution = upper_bound.unwrap_or(anticipated_num_objects);\r\nlet mut tree = Tree::with_capacity(worst_case_num_objects_after_thin_pack_resolution)?;\r\n```\r\n`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:\r\n```rust\r\nfn exact_vec<T>(capacity: usize) -> Vec<T> {\r\n    let mut v = Vec::new();\r\n    v.reserve_exact(capacity);   // aborts on failure; does NOT return Err\r\n    v\r\n}\r\n```\r\n`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`.\r\n\r\n## Impact\r\n`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.\r\n\r\n## Proof of Concept\r\nServe 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.\r\n\r\n## Attack Chain\r\n1. Entry: attacker-served (or MITM-redirected) upload-pack → victim `gix clone`/`fetch`. Guard: none (serving a pack needs no auth). Bypass proof: N/A.\r\n2. 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.\r\n3. Processing: `size_hint()` → `objects_left as usize` → `LookupRefDeltaObjectsIter::size_hint` doubles it → `worst_case = 2 * num_objects` (index/write/mod.rs:122).\r\n4. 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 ace687ce). 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.\r\n5. Impact: `reserve_exact` of hundreds of GB fails and calls `handle_alloc_error` → process abort. Pre-auth, ~12 attacker bytes.\r\n\r\n## Bypass Evidence\r\n- `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.\r\n- `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`.\r\n- 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 ace687ce added `alloc_limit_bytes` only to `resolve()`).\r\n\r\n## Affected Versions\r\n`gix-pack <= 0.74.2` (latest release tag gix-pack-v0.74.2; code present on HEAD e3a6fa15). No post-tag fix.\r\n\r\n## Suggested Fix\r\nCap `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`.\r\n\r\n## Caveat\r\nOn 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.\r\n\r\n---\r\nReported by **zx (Jace)** — GitHub: @manus-use","categories":["Unclassified"],"product":"gix-pack","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-x862-c2wj-4mwr","source_modified_at":"2026-09-25T08:30:42Z","fetched_at":"2026-10-23T19:00:29.328130Z","package":{"ecosystem":"other","name":"gix-pack"},"range":"<= 0.74.2"}],"fixed_versions":[],"cvss":6.5,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-25T08:30:42Z","modified_at":"2026-09-25T08:30:42Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-x862-c2wj-4mwr","fetched_at":"2026-10-23T19:00:29.328130Z","source_modified_at":"2026-09-25T08:30:42Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-25T08:30:42Z","content_hash":"6b31a6ff03093429436356cb5b2fbf5a4b9a4263da88e1226d011203a7392847"}}
