# 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: 1. `z_erofs_do_read()` calls `z_erofs_decode_length()` with `mapoff` and `want`. 2. `z_erofs_decode_length()` sets `partial=true` and `decoded_len=mapoff+want` when the descriptor reports `supports_subextent` and the request ends before the extent. 3. `z_erofs_decode_extent()` passes that length and mode to `z_erofs_decompress()`. 4. `z_erofs_zstd_decompress()` fills the shortened output and calls `z_erofs_zstd_finish()`. 5. `z_erofs_zstd_finish()` intentionally permits partial decoding without requiring stream end. That behavior remains necessary for the separately defined `EROFS_MAP_PARTIAL_REF` bounded-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.sh` and `sh -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`; cleanup `PASS`, target reached. Evidence: `/tmp/erofs-phase0-b28-20260818-r2/20260818T070848Z-host-B28-partial-1780769-0/`. The source audit reports `zstd=false`, ZSTD in `current_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`, cleanup `PASS`; 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.