Skip to content

Harden PHF container, add CRC-32, byte-level spec, tests and CI - #1

Merged
libphf merged 2 commits into
mainfrom
fix/review-findings
Oct 28, 2026
Merged

libphf merged 2 commits into
mainfrom
fix/review-findings

Conversation

@libphf

@libphf libphf commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the issues found in a full review of libphf: two container bugs (one of them a possible out-of-bounds read on 32-bit/WASM), silent acceptance of corrupted files, OpenMP data races, CLI/web robustness gaps, and documentation that didn't match the code. Adds a byte-level spec, a test suite, a reproducible benchmark and CI.

Bug fixes

# Issue Fix
1 The MED flag bit was never set: (2 << 1) instead of (1 << 1), so RGB files got flags 0x04 Flags are now 0x0E (RGB) / 0x0F (RGBA)
2 phf_size < 20 + payload_size wraps on 32-bit targets (WASM), letting the decoder read past the input buffer Rewritten as a subtraction. phf_read_file also validates the untrusted header before sizing its allocation, and the PHF header must agree with the inner NHF3 packet
3 No integrity check: in fuzzing, ~15% of corrupted files decoded "successfully" into wrong pixels Optional CRC-32 trailer (flag bit 3) verified before decoding. With it, 0 of ~20k corrupted inputs get through
4 Data races on shared error flags inside omp parallel for Each thread writes only its own slot; results are checked after the loop
5 Web viewer didn't check _malloc results Checks allocations, validates dimensions/channels, shows readable errors
6 CLI was Windows-only, crashed on grayscale input, silently corrupted 16-bit input, and info printed a hardcoded "100% Bit-Exact" Loads the library on any OS (or from PHF_LIB), converts grayscale to RGB, rejects 16-bit input, info actually decodes and verifies the CRC, returns proper exit codes

Compatibility

  • Files without the CRC flag (including all files written so far) still decode.
  • Older decoders, including the currently deployed phf.wasm, read new files fine and just ignore the 4-byte trailer.
  • Error codes are now named PHF_ERR_* constants in phf.h and no longer collide with internal NHF3 codes. -1…-6 keep their documented meaning (-5 truncated, -6 stride), and new codes were added (-7 corrupt, -8 checksum, -9 memory, -10 I/O). Callers that compared the old overlapping raw numbers should switch to the constants.

Documentation

  • SPEC.md rewritten byte by byte: NHF3 packet header, real plane order (R−G, G, B−G, A), prediction border rules, canonical code tables, the RLE mode, CRC and size limits. To validate it, an independent Python decoder was written only from the spec text; it matches libphf byte for byte (synthetic RGB/RGBA/gray images and a crop of the sample photo).
  • README / MANIFESTO no longer claim features the code doesn't have (Package-Merge, AVX2, ESP32, P-frames in the .phf container). Streaming latencies are recomputed from the measured throughputs (1080p at 290 MB/s ≈ 21 ms, not 8 ms). A note says the benchmark conditions weren't recorded and points to bench/bench.py.
  • Stale code comments are fixed (fast-table width, table format, the ignored delta parameter).

Tooling

  • CMakeLists.txt for every platform (shared/static, optional OpenMP, PHF_SANITIZE).
  • tests/test_phf.c with 470 checks: round-trips across sizes and image types (stride 3/4/0), header/limit/error paths, the overflow case, CRC detection, a corruption fuzz, file I/O, and decoding sample.phf.
  • bench/bench.py: PHF vs WebP Lossless vs PNG-6/PNG-1, with a bit-exact check on every decode. While building it we found that WebP Lossless is not bit-exact on RGBA by default: it drops the RGB of fully transparent pixels. The script reports that case separately.
  • GitHub Actions CI: Linux (OpenMP, and ASan+UBSan), macOS, Windows/MSVC.
  • build.bat finds MSVC through vswhere (no hardcoded VS path). The WASM build writes straight to apps/web/, and there is now a build_wasm.sh.
  • Generated binaries are no longer tracked in libphf/. sample.phf was re-encoded with correct flags and a CRC, with identical pixels.

Test plan

  • ctest passes in Release, Debug + ASan/UBSan, and with OpenMP (macOS arm64, clang): 470/470, zero warnings (including -std=c99 -pedantic)
  • CLI round-trip on RGB / RGBA / grayscale PNGs is bit-exact; 16-bit input is rejected; info detects a flipped payload byte
  • Independent spec-only decoder matches libphf
  • phf-view.js passes node --check
  • CI on Linux / Windows (first run on this PR)
  • Rebuild apps/web/phf.wasm with libphf/build_wasm.sh (Emscripten wasn't available while preparing this PR). Until then, the deployed viewer keeps the old binary without the overflow fix or the CRC check
  • Windows: libphf/build.bat (untested here)

🤖 Generated with Claude Code

ygorvnt and others added 2 commits October 28, 2026 09:25
Bug fixes
- Header flags: the MED bit was never set ((2 << 1) instead of (1 << 1));
  RGB files now carry 0x0E and RGBA 0x0F.
- phf_decode_memory: `phf_size < 20 + payload_size` could wrap on 32-bit
  targets (WASM) and let the decoder read past the buffer; rewritten as a
  subtraction. phf_read_file validates the untrusted header before sizing
  its allocation, and the PHF header must agree with the NHF3 packet.
- Add an optional CRC-32 trailer (flag bit 3) verified before decoding.
  Corrupted files previously decoded "successfully" ~15% of the time in
  fuzzing; with the CRC none do. Files without the flag still decode, and
  older decoders ignore the trailer.
- Remove OpenMP data races on shared error flags.
- Web viewer: check _malloc results, validate dimensions/channels, map error
  codes to messages, bump cache-busting version to 1.0.2.
- CLI: load libphf on Windows/macOS/Linux (or PHF_LIB), support grayscale,
  reject 16-bit input instead of corrupting it, `info` now decodes and
  verifies the CRC, proper exit codes.

API
- Named PHF_FLAG_*, PHF_ERR_* and limit constants in phf.h; error codes no
  longer overlap with the internal NHF3 codes.

Docs
- SPEC.md rewritten byte by byte (NHF3 packet, plane order, prediction
  borders, code tables, RLE, CRC, limits); validated by an independent
  decoder written only from the spec.
- README/MANIFESTO: drop claims the code does not implement (Package-Merge,
  AVX2, ESP32), recompute streaming latencies from the measured
  throughputs, note benchmark conditions, document build/test/bench.
- Fix stale comments (fast-table width, table format, ignored `delta`).

Tooling
- CMakeLists.txt (shared/static, optional OpenMP, sanitizers), tests/test_phf.c
  (470 checks), bench/bench.py (PHF vs PNG vs WebP-LL, bit-exact checks),
  GitHub Actions CI for Linux/macOS/Windows.
- build.bat locates MSVC via vswhere; WASM builds write to apps/web/;
  add build_wasm.sh. Stop tracking generated binaries in libphf/.
- sample.phf re-encoded with correct flags and CRC (pixels identical).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@libphf
libphf merged commit 8086543 into main Oct 28, 2026
4 checks passed
@libphf
libphf deleted the fix/review-findings branch October 28, 2026 12:39
libphf added a commit that referenced this pull request Oct 28, 2026
Harden PHF container, add CRC-32, byte-level spec, tests and CI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants