# 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.
