PT-2026-38897 · Crates.Io · Gix-Pack
Published
2026-05-05
·
Updated
2026-05-05
CVSS v4.0
8.7
High
| Vector | AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N |
Summary
Multiple denial-of-service vectors in
gix-pack: unchecked array indexing causes panics on crafted delta data, and uncapped attacker-controlled size headers enable OOM process kills. Both are triggered by malicious pack data received during clone/fetch.Details
Bug 1: Unchecked array indexing in delta application (CWE-248)
The
apply() function in gix-pack/src/data/delta.rs (lines 33-87) reads delta instructions using unchecked data[i] indexing at 7 locations (lines 41, 45, 49, 53, 57, 61, 65). The command byte's bits indicate how many additional bytes follow, but if the delta data is truncated, the index panics:pub(crate) fn apply(base: &[u8], mut target: &mut [u8], data: &[u8]) -> Result<(), apply::Error> {
let mut i = 0;
while let Some(cmd) = data.get(i) { // first byte: safely checked
i += 1;
match cmd {
cmd if cmd & 0b1000 0000 != 0 => {
let (mut ofs, mut size): (u32, u32) = (0, 0);
if cmd & 0b0000 0001 != 0 {
ofs = u32::from(data[i]); // PANIC: no bounds check
i += 1;
}
// ... 6 more unchecked data[i] at lines 45, 49, 53, 57, 61, 65
Lines 83-84 use
assert eq! (not debug assert eq!) that panics in both debug and release builds: assert eq!(i, data.len());
assert eq!(target.len(), 0);
A second location in
parse header info() (gix-pack/src/data/entry/decode.rs:116-129) also panics on truncated input via unchecked data[0] and data[i].Note: PR #2059 (merged 2025-06-25) fixed the explicit
panic!() for command code 0. The unchecked array indexing is a distinct class that remains unfixed.Bug 2: Uncapped allocation from attacker-controlled size headers (CWE-770)
Pack entry headers and delta headers encode object sizes as LEB128-encoded u64 values. These sizes are used to allocate buffers before validating the actual data, with no upper bound:
bytes to entries.rs:109 Vec::with capacity(entry.decompressed size as usize) // UNCAPPED
resolve.rs:461 out.resize(decompressed len, 0) // UNCAPPED
resolve.rs:190 fully resolved delta bytes.resize(result size as usize, 0) // UNCAPPED
A 10-byte crafted pack entry can claim
decompressed size = 0xFFFFFFFFFFFF (281 TB). At bytes to entries.rs:109, gitoxide calls Vec::with capacity(281TB) before any decompression occurs. The OS immediately OOM-kills the process. No MAX SIZE, max object size, or equivalent limit exists anywhere in gix-pack.The allocation at
resolve.rs:461 is equally dangerous: decompressed size from the pack header is cast to usize and passed to Vec::resize(), which allocates and zeroes the full claimed size before the zlib decompressor runs.PoC
Compiled and executed in Rust 1.94.1
--release mode. All 5 panics confirmed:[1] delta apply: cmd=0x81, truncated -> PANIC: index out of bounds: len is 1 but index is 1
[2] delta apply: cmd=0xFF, only 3 extra bytes -> PANIC: index out of bounds: len is 4 but index is 4
[3] parse header info: empty data -> PANIC: index out of bounds: len is 0 but index is 0
[4] parse header info: byte=0x80, truncated -> PANIC: index out of bounds: len is 1 but index is 1
[5] delta apply: assert eq!(i, data.len()) -> PANIC: assertion failed
For the OOM vector: the allocation path is
parse header info() -> entry.decompressed size (u64) -> Vec::with capacity(size as usize) with no intermediate validation. A minimal pack with a single entry claiming a multi-terabyte size triggers immediate process kill.Impact
Any application built on gitoxide that clones or fetches from an untrusted remote can be crashed by a malicious server:
- Panic DoS: 1-2 bytes of crafted delta data causes an immediate process abort
- OOM DoS: A single crafted pack entry header causes the process to attempt a multi-terabyte allocation, triggering an immediate OOM kill by the OS
This affects the
gix CLI, any application using the gix crate, and CI/CD systems that clone repositories using gitoxide. No fuzz targets exist for gix-pack (issue #703 tracks oss-fuzz integration).Suggested fix
For panics: replace unchecked
data[i] with data.get(i).ok or(Error::...) and replace assert eq! with proper error returns.For OOM: add a configurable maximum object size (similar to git's
transfer.maxPackSize) and validate claimed sizes against it before allocating. At minimum, cap allocations to a reasonable default (e.g., 4 GB) and use try reserve() consistently.Severity
High. Network vector, no privileges required, user interaction required (clone/fetch). The OOM vector is a single-packet process kill with no recovery.
Fix
Allocation of Resources Without Limits
Found an issue in the description? Have something to add? Feel free to write us 👾
Related Identifiers
Affected Products
Gix-Pack