4.6 KiB
Phase 0: B-ZSTD-001 Remediation
Status: FIXED_STATICALLY; runtime corruption-tail verification remains
NOT_TESTED in this remediation pass.
Problem
Strict audit item B-ZSTD-001 / BUG-ZSTD-035 found that ordinary ZSTD extents
advertised subextent support. A prefix or middle read could therefore decode
only the requested output and accept z_erofs_zstd_finish() without checking
the remaining compressed stream. Corruption after the requested range could be
skipped.
P15-086 already authorizes partial ordinary reads only for LZ4, LZMA, and
Deflate. Its ZSTD result is FULL_FALLBACK because the partial candidate used
more temporary memory than full decode.
Call Graph
For an ordinary mapped compressed read:
z_erofs_do_read()callsz_erofs_decode_length()withmapoffandwant.z_erofs_decode_length()setspartial=trueanddecoded_len=mapoff+wantwhen the descriptor reportssupports_subextentand the request ends before the extent.z_erofs_decode_extent()passes that length and mode toz_erofs_decompress().z_erofs_zstd_decompress()fills the shortened output and callsz_erofs_zstd_finish().z_erofs_zstd_finish()intentionally permits partial decoding without requiring stream end. That behavior remains necessary for the separately definedEROFS_MAP_PARTIAL_REFbounded-prefix path.
The smallest correct policy fix is to stop ordinary ZSTD reads at step 2:
ZSTD now advertises .supports_subextent = 0. z_erofs_decode_length() then
selects the full extent for ordinary prefix and middle reads. The explicit
EROFS_MAP_PARTIAL_REF branch still sets partial=true and remains unchanged.
Changes
- Set the ZSTD descriptor capability to false in
src/decompressor_zstd.c; no finish validation was weakened or bypassed. - Updated B27 assertions to preserve ZSTD partial-reference completion while requiring ordinary ZSTD full fallback.
- Updated B28 source and extracted-policy assertions, host report, and QEMU
corruption decision so ZSTD is
FULL_FALLBACK; LZ4/LZMA/Deflate policy is unchanged.
Verification
Commands and actual results:
git diff --check:PASS.sh -n repo-pre-15/tests/pre15/cases/B27-stream-tail.shandsh -n repo-pre-15/tests/pre15/cases/B28-partial.sh:PASS.PRE15_EVIDENCE_ROOT=/tmp/erofs-phase0-b28-20260818-r2 timeout -k 10 600 repo-pre-15/tests/pre15/run-host.sh B28-partial:PASS; cleanupPASS, target reached. Evidence:/tmp/erofs-phase0-b28-20260818-r2/20260818T070848Z-host-B28-partial-1780769-0/. The source audit reportszstd=false, ZSTD incurrent_full_fallback,current_partial=[deflate,lz4,lzma], and partial-reference preserved.PRE15_EVIDENCE_ROOT=/tmp/erofs-phase0-b27-20260818 timeout -k 10 600 repo-pre-15/tests/pre15/run-host.sh B27-stream-tail:RUNNER_FAIL, cleanupPASS; the pre-existing B29 Deflate context-pool delta fails B27's frozen exact-transform audit before the completion harness. This is not claimed as ZSTD runtime evidence.- P15-086 frozen gate result and policy were inspected, not rerun; its existing
recorded decision remains ZSTD
FULL_FALLBACK.
The B28 host case generated and compared real EROFS fixtures and ran the descriptor/decode-length source audit, but did not load a FreeBSD KLD or read an image through the kernel. No full feature suite was run.
Not Tested
NOT_TESTED: a real FreeBSD runtime read of an EROFS ZSTD image whose stream
is corrupted after an ordinary prefix or middle request. The required runtime
threshold is that the short ordinary read returns positive EINTEGRITY (97),
matching full fallback, while a valid ordinary prefix/middle/tail read matches
the source bytes.
NOT_TESTED: runtime confirmation that EROFS_MAP_PARTIAL_REF retains its
bounded-prefix semantics on a real image after this policy change.
Risk
Ordinary ZSTD prefix and middle reads now decode the complete extent. This may increase CPU or temporary output work relative to the previously incorrect partial path, but it restores corruption visibility and matches P15-086 policy. The changed tests do not alter test fixtures, image bytes, test environment, or finish-error behavior.
Runtime Gate
Before claiming Phase 0 runtime closure, run the existing focused B28 runtime
case with ZSTD enabled and verify valid prefix, middle, and tail reads, an
after-request-range corruption case returning EINTEGRITY, full corruption
returning EINTEGRITY, and the real partial-reference case. Record separate
PASS, INFRA_BLOCKED, or NOT_TESTED results; do not infer runtime behavior
from the extracted C harness.