# DF-0778 — ufs_readlink OOB read / panic via i_size truncation to int

**Status: REPRODUCED (Mode A leak + Mode B panic) · FIX VALIDATED**
**Severity: Medium · CWE-197 (Integer Truncation) / CWE-125 (OOB Read) / CWE-200 (Info Leak)**

## What this proves

`ufs_readlink()` truncates the on-disk uint64 `i_size` of a symlink inode to a
signed `int` and feeds it to `uiomove()` against the 48-byte inline
`i_shortlink` buffer with no upper bound. A crafted UFS/FFS image whose
symlink inode has `di_blocks==0` and a malicious `di_size` produces, via an
**unprivileged** `readlink()` after the image is mounted:

- **Mode A** (`di_size=200`): OOB read of **152 bytes past the inode** into
  adjacent kernel slab — info leak incl. kernel KVA pointers (KASLR bypass).
- **Mode B** (`di_size=0x80000000`): `isize` becomes `INT_MIN`, sign-extends to
  ~2^63 as a `size_t`; a sufficiently large user buffer makes `uiomove`
  page-fault in kernel mode → **kernel panic** (`vm_fault: fault on stack guard`
  in `ufs_readlink+0x42`).

## Files

| File | Purpose |
|------|---------|
| `trigger_readlink.c`     | unprivileged readlink trigger, hexdumps + counts leaked KVA ptrs |
| `trigger_readlink_big.c` | Mode B variant — mmap'd large buffer to find the OOB/panic ceiling |
| `craft_image.sh`         | **root** — builds a crafted UFS1 image, patches symlink inode 3 via `fsdb` |
| `build.sh`               | compiles the triggers |
| `run.sh`                 | drives a reproduction run |
| `VERDICT.md`             | full root-cause analysis + path:line trace + fix validation |
| `fix.diff`               | the validated git-apply-able fix (`int64_t isize` + bound) |
| `run.log`/`run.2.log`/`run.3.log` | Mode A leak across 3 runs (variance/stability) |
| `leak_sample.txt`        | raw leaked bytes (Mode A) |
| `panic.txt`              | Mode B panic signature from `boot.log` |
| `fix_build.log`          | full single-fix kernel build output (rc=0) |
| `fix_run.log`            | before/after contrast on patched `#1` kernel |
| `env.txt`                | guest environment |

## How to reproduce

```sh
# 1. (root on guest) craft the malicious UFS1 image — Mode A or B
ssh dfbsd '/root/craft_image.sh 200'      # Mode A = di_size 200 (OOB leak)
ssh dfbsd '/root/craft_image.sh modeB'    # Mode B = di_size 0x80000000 (panic)

# 2. (unprivileged) trigger
ssh dfbsd-maxx '/home/maxx/trigger_readlink /mnt/test/mylink'
ssh dfbsd-maxx '/home/maxx/trigger_readlink_big /mnt/test/mylink 16777216'   # Mode B
```

### Preconditions
- An admin mounts the crafted UFS/FFS image (`vfs.usermount=0` → root mount;
  realistic: downloaded disk image, USB volume, attached storage).
- Then **any** unprivileged local user triggers via `readlink()`.

## The fix (validated)

`int isize` → `int64_t isize`; only use the inline `i_shortlink` when
`0 <= isize <= UFS1_MAXSYMLINKLEN`. See `fix.diff` and `VERDICT.md`.
