Skip to content

Heap double-free on many small chunks + wrong result on tiny inputs (1/2/5 bytes) #2

Description

@inikep

Summary

Two related bugs in aceapex 1.0, both triggered by small inputs:

  1. Heap corruption / abort. Compressing many small buffers in sequence aborts with free(): double free detected in tcache 2 (SIGABRT).
  2. Incorrect result on tiny inputs. For some very small sizes, the decode round-trip fails — aceapex_decompress returns -1 and the output does not match the input.

Found while benchmarking aceapex in lzbench.

Environment

  • aceapex 1.0
  • lzbench 2.2.1
  • GCC 13.3.0, x86-64 Linux (AMD EPYC 9554)

Reproduce — double-free

Run, from a checked-out lzbench tree (which contains thousands of files of varied/tiny sizes), aceapex over every file:

./lzbench -eaceapex -v10 -t0,0 -jr .

It aborts mid-compression after a couple thousand small chunks:

ENC chunk=2154/5915 part=7605 clen=7867 out=37019509->33317524
ENC chunk=2155/5915 part=356 free(): double free detected in tcache 2
Aborted (exit 134)

part=NNN is the per-buffer input size in bytes; the abort happens while compressing a small (~356-byte) buffer after ~2154 prior compress/decompress calls. It's not a single bad file — it's the repeated-call path corrupting the heap. (Running under valgrind/ASan should pinpoint the offending free.)

Reproduce — minimal, wrong result on tiny input

printf x > one              # 1 byte
./lzbench -eaceapex -t0,0 one
ERROR in aceapex: decompressed size mismatch in_bytes[1] != out_bytes[-1]

i.e. aceapex_decompress(...) returns -1 for the 1-byte input (compress produced 175 bytes, decode then fails). Scanning sizes 0–10 bytes, the failing ones are 1, 2, and 5 bytes; 0, 3, 4, 6–10 round-trip fine. Larger single buffers (tested up to 128 MB) are also fine. So the bug is specific to certain very small input sizes / block edge cases.

Likely area

The block/stream handling for inputs smaller than (or near) a single block looks like the culprit — both the -1 decode failures and the eventual double-free point at small-buffer edge cases in encode_file / the stream alloc-free path. Thanks for taking a look!

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