zupt/FUZZING.md
2026-03-21 22:06:03 -03:00

2.6 KiB

Fuzzing Zupt with AFL++

Setup

# Install AFL++
apt install afl++ afl++-clang

# Build instrumented binary
export CC=afl-clang-fast
make clean
make CFLAGS="-Wall -Wextra -O2 -std=c11 -Iinclude -Isrc -fsanitize=address"

# Or build a harness that reads from stdin
cat > fuzz_decompress.c << 'EOF'
#include "zupt.h"
#include <stdlib.h>
#include <unistd.h>
int main(void) {
    /* Read archive from stdin, attempt to extract */
    char tmpfile[] = "/tmp/zupt_fuzz_XXXXXX";
    int fd = mkstemp(tmpfile);
    if (fd < 0) return 1;
    char buf[4096];
    ssize_t n;
    while ((n = read(0, buf, sizeof(buf))) > 0) write(fd, buf, n);
    close(fd);
    zupt_options_t opts;
    zupt_default_options(&opts);
    opts.quiet = 1;
    zupt_extract_archive(tmpfile, "/tmp/zupt_fuzz_out", &opts);
    unlink(tmpfile);
    return 0;
}
EOF
afl-clang-fast -Wall -O2 -std=c11 -Iinclude -Isrc -fsanitize=address \
    fuzz_decompress.c src/zupt_format.c src/zupt_lz.c src/zupt_lzh.c \
    src/zupt_xxh.c src/zupt_sha256.c src/zupt_aes256.c src/zupt_crypto.c \
    src/zupt_predict.c src/zupt_parallel.c src/zupt_keccak.c \
    src/zupt_x25519.c src/zupt_mlkem.c -lm -lpthread -o fuzz_zupt

Corpus

mkdir -p corpus
# Generate seed archives
echo "test" > /tmp/t.txt
./zupt compress corpus/normal.zupt /tmp/t.txt
./zupt compress -p "pw" corpus/encrypted.zupt /tmp/t.txt
./zupt compress --solid corpus/solid.zupt /tmp/t.txt
./zupt compress -s corpus/store.zupt /tmp/t.txt
./zupt compress -f corpus/fast.zupt /tmp/t.txt
# PQ mode
./zupt keygen -o /tmp/k.key
./zupt keygen --pub -o /tmp/pub.key -k /tmp/k.key
./zupt compress --pq /tmp/pub.key corpus/pq.zupt /tmp/t.txt
# Truncated/corrupt
head -c 64 corpus/normal.zupt > corpus/truncated.zupt
dd if=/dev/urandom bs=200 count=1 of=corpus/random.zupt 2>/dev/null

Run

mkdir -p findings
afl-fuzz -i corpus -o findings -m none -- ./fuzz_zupt

Expected Coverage

The decompress harness exercises:

  • Archive header parsing (magic, version, flags)
  • Block header parsing (magic, codec, flags, varint sizes)
  • LZ decompression (match/literal parsing, bounds checks)
  • LZH decompression (Huffman table decode, code-length parsing)
  • LZHP decompression (prediction decode + LZH)
  • Index parsing (varint, path, sizes)
  • Encryption header parsing (enc_type dispatch, PBKDF2 vs PQ)
  • Encrypted block handling (HMAC verify, AES-CTR decrypt)

Target: 72 hours, expect ~10K executions/sec

Known hard-to-reach paths:

  • PQ decryption requires a valid ML-KEM ciphertext (unlikely from random fuzzing)
  • Password decryption requires correct HMAC (rejected before any decompression)
  • Solid mode decompression (requires valid solid flag + index)