GHSA-X494-MJ8G-CJ27

Vulnerability from github – Published: 2026-05-05 19:24 – Updated: 2026-05-05 19:24
VLAI
Summary
gix-pack has multiple DoS vectors: unchecked indexing panics and uncapped OOM allocations from crafted pack data
Details

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.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.68.0"
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "gix-pack"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.69.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-248",
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-05T19:24:15Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nMultiple 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.\n\n### Details\n\n**Bug 1: Unchecked array indexing in delta application (CWE-248)**\n\nThe `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\u0027s bits indicate how many additional bytes follow, but if the delta data is truncated, the index panics:\n\n```rust\npub(crate) fn apply(base: \u0026[u8], mut target: \u0026mut [u8], data: \u0026[u8]) -\u003e Result\u003c(), apply::Error\u003e {\n    let mut i = 0;\n    while let Some(cmd) = data.get(i) {  // first byte: safely checked\n        i += 1;\n        match cmd {\n            cmd if cmd \u0026 0b1000_0000 != 0 =\u003e {\n                let (mut ofs, mut size): (u32, u32) = (0, 0);\n                if cmd \u0026 0b0000_0001 != 0 {\n                    ofs = u32::from(data[i]);     // PANIC: no bounds check\n                    i += 1;\n                }\n                // ... 6 more unchecked data[i] at lines 45, 49, 53, 57, 61, 65\n```\n\nLines 83-84 use `assert_eq!` (not `debug_assert_eq!`) that panics in both debug and release builds:\n\n```rust\n    assert_eq!(i, data.len());\n    assert_eq!(target.len(), 0);\n```\n\nA 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]`.\n\nNote: 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.\n\n**Bug 2: Uncapped allocation from attacker-controlled size headers (CWE-770)**\n\nPack 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:\n\n```\nbytes_to_entries.rs:109  Vec::with_capacity(entry.decompressed_size as usize)  // UNCAPPED\nresolve.rs:461           out.resize(decompressed_len, 0)                       // UNCAPPED\nresolve.rs:190           fully_resolved_delta_bytes.resize(result_size as usize, 0)  // UNCAPPED\n```\n\nA 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.\n\nThe 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.\n\n### PoC\n\nCompiled and executed in Rust 1.94.1 `--release` mode. All 5 panics confirmed:\n\n```\n[1] delta apply: cmd=0x81, truncated -\u003e PANIC: index out of bounds: len is 1 but index is 1\n[2] delta apply: cmd=0xFF, only 3 extra bytes -\u003e PANIC: index out of bounds: len is 4 but index is 4\n[3] parse_header_info: empty data -\u003e PANIC: index out of bounds: len is 0 but index is 0\n[4] parse_header_info: byte=0x80, truncated -\u003e PANIC: index out of bounds: len is 1 but index is 1\n[5] delta apply: assert_eq!(i, data.len()) -\u003e PANIC: assertion failed\n```\n\nFor the OOM vector: the allocation path is `parse_header_info()` -\u003e `entry.decompressed_size` (u64) -\u003e `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.\n\n### Impact\n\nAny application built on gitoxide that clones or fetches from an untrusted remote can be crashed by a malicious server:\n\n- **Panic DoS**: 1-2 bytes of crafted delta data causes an immediate process abort\n- **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\n\nThis 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).\n\n### Suggested fix\n\nFor panics: replace unchecked `data[i]` with `data.get(i).ok_or(Error::...)` and replace `assert_eq!` with proper error returns.\n\nFor OOM: add a configurable maximum object size (similar to git\u0027s `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.\n\n### Severity\n\nHigh. Network vector, no privileges required, user interaction required (clone/fetch). The OOM vector is a single-packet process kill with no recovery.",
  "id": "GHSA-x494-mj8g-cj27",
  "modified": "2026-05-05T19:24:15Z",
  "published": "2026-05-05T19:24:15Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-x494-mj8g-cj27"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/GitoxideLabs/gitoxide"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "gix-pack has multiple DoS vectors: unchecked indexing panics and uncapped OOM allocations from crafted pack data"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…