Skip to content

sinfl.h: Stack buffer overflow in dynamic Huffman code length parsing + input OOB reads (CWE-121/CWE-125) #66

@ByamB4

Description

@ByamB4

Summary

sinfl_decompress() has a stack buffer overflow in the dynamic Huffman block parsing where repeat codes (symbols 16/17/18) can write past the 320-byte lens stack buffer. Additionally, the input bitstream is never bounds-checked, and back-reference distances are not validated.

Vulnerability Details

Bug 1: Stack buffer overflow via repeat code lengths (Critical, CWE-121)

In the dynamic Huffman code length parsing (lines 455-463):

unsigned char lens[288+32];  // 320 bytes on stack

for (n = 0; n < nlit + ndist;) {
    sym = sinfl_decode(&s, hlens, 7);
    switch (sym) {
    default: lens[n++] = (unsigned char)sym; break;
    case 16: for (i=3+sinfl_get(&s,2);i;i--,n++) lens[n]=lens[n-1]; break;
    case 17: for (i=3+sinfl_get(&s,3);i;i--,n++) lens[n]=0; break;
    case 18: for (i=11+sinfl_get(&s,7);i;i--,n++) lens[n]=0; break;
    }
}

The outer loop checks n < nlit + ndist (max 320) only at the top. The inner repeat loops (cases 16/17/18) increment n without checking the bound:

  • Case 18 can repeat up to 11 + 127 = 138 times
  • If n = 300 and case 18 fires with max repeat: n goes to 438, writing lens[300] through lens[437]
  • This overflows 117 bytes past the 320-byte lens stack buffer

A crafted DEFLATE stream with a dynamic Huffman block specifying excessive repeat codes triggers this attacker-controlled stack buffer overflow. Case 16 (lens[n]=lens[n-1]) also causes OOB reads of the already-overflowed data.

Bug 2: Input buffer over-read in sinfl_refill (CWE-125)

static void sinfl_refill(struct sinfl *s) {
  s->bitbuf |= sinfl_read64(s->bitptr) << s->bitcnt;  // reads 8 bytes unconditionally
  s->bitptr += (63 - s->bitcnt) >> 3;
  s->bitcnt |= 56;
}

sinfl_read64 does memcpy(&n, p, 8) — always reads 8 bytes from bitptr. The input end e = in + size is computed at line 385 but is never checked against bitptr in the compressed block code paths. For any input shorter than ~8 bytes of remaining data after the last refill, subsequent refills read past the input buffer into adjacent heap memory.

Bug 3: Back-reference distance not validated (CWE-125)

int offs = sinfl__get(&s, dbits[dsym]) + dbase[dsym];
unsigned char *src = out - offs;  // no check that offs <= (out - output_start)

A crafted stream can specify a distance larger than the number of bytes already output. src = out - offs then points before the output buffer, causing an OOB read. The output bounds (out > oe) are checked, but there's no validation that the match source is within the already-written output.

Impact

  • Bug 1: Attacker-controlled stack buffer overflow up to ~138 bytes. With a crafted DEFLATE stream, this can overwrite the return address and achieve code execution.
  • Bug 2: Heap information disclosure — up to 7 bytes of heap data read past the input allocation.
  • Bug 3: OOB read from before the output buffer, potential info leak.

Suggested Fix

For Bug 1, clamp the inner repeat loops:

case 16: for (i=3+sinfl_get(&s,2); i && n < nlit+ndist; i--,n++) lens[n]=lens[n-1]; break;
case 17: for (i=3+sinfl_get(&s,3); i && n < nlit+ndist; i--,n++) lens[n]=0; break;
case 18: for (i=11+sinfl_get(&s,7); i && n < nlit+ndist; i--,n++) lens[n]=0; break;

For Bug 2, check bitptr against input end before refilling, or document the 8-byte overread requirement.

For Bug 3, add: if (offs > (int)(out - o)) return (int)(out-o);

Found via manual code audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions