# DF-0020 — PoC

`elf_note_oob.c` (C; the seed was `elf_note_oob.py` — rewritten in C
because the DragonFly guest has no `python3`) crafts a minimal ELF64
whose truncated `.note.ABI-tag` triggers a kernel OOB read in
`bsd_trans_osrel()`.

## The bug

`note_overflow()` (`sys/kern/imgact_elf.c:1700-1707`) validates that a
note's `n_namesz` fits in the remaining PT_NOTE segment but never
validates `n_descsz`. A crafted PT_NOTE that matches the DragonFly
brandnote (`n_namesz=10`, `n_descsz=4`, `n_type=1`, vendor `"DragonFly"`)
but is truncated (`p_filesz=22`, no descriptor) passes `note_overflow`
(`12<=22`, `10<=10`), matches, and `bsd_trans_osrel()` reads the 4-byte
descriptor at `note + sizeof(Elf_Note) + roundup2(n_namesz, 4) == note+24`
— past the 22-byte segment, and with `noteloc=4072`, past the 4096-byte
`image_header` page entirely. OOB read of adjacent kernel memory.

**Impact:** kernel OOB read of ≤4 bytes; value goes to `p_osrel` (not
exposed to userspace) → no info leak, no escalation. Realistic ceiling
is a silent OOB read on every local execve (robustness /
defense-in-depth) or, if the OOB straddles an unmapped page, a local
DoS via page-fault panic.

## Why EI_OSABI = 200

The PoC sets `e_ident[EI_OSABI] = 200` so the brand can ONLY be
selected via the PT_NOTE match path (loop 1 in `get_brandinfo`,
`sys/kern/imgact_elf.c:550-561`). With `EI_OSABI=0` (the seed value),
loop 2's `hdr->e_ident[EI_OSABI] == bi->brand` match would select the
DragonFly brand regardless of whether the note check passed, masking
the bug. With `EI_OSABI=200` and `kern.elf64.fallback_brand=-1`, the
unfixed kernel accepts the binary (note match + OOB), while the fixed
kernel rejects it with `ENOEXEC` — a clean before/after contrast.

## Why noteloc = 4072 (not 4074)

The seed Python PoC used `noteloc = 4096 - 22 = 4074`, but `4074 % 4 = 2`,
and the note walk breaks immediately on `!aligned(note, Elf32_Addr)`
(`sys/kern/imgact_elf.c:1778`). With `noteloc=4072` (4-aligned), the
walk proceeds past the alignment check, the truncated note matches, and
`bsd_trans_osrel` performs the OOB read.

## Build (unprivileged)

```
cc -O2 -Wall -o elf_note_oob elf_note_oob.c
```
(or just `./build.sh`)

## Run (unprivileged)

```
./run.sh
# equivalent to:
./elf_note_oob /tmp/df0020_oob_elf
chmod +x /tmp/df0020_oob_elf
/tmp/df0020_oob_elf
```

## Expected output

### Bug present (unpatched `#0` kernel)

`execve` **succeeds**: the crafted binary is loaded via the OOB brand
match. Since the binary has no `PT_LOAD`, control jumps to `e_entry=0`
and the new process dies with SIGSEGV:

```
--- attempting execve ---
Segmentation fault (core dumped)            # execve succeeded
```

The shell does NOT print "Exec format error". (5/5 reproducible; OOB
read happens silently each time — adjacent lwbuf-pool page is mapped.)

### Fixed kernel (this `fix.diff` applied)

`execve` **fails** with `ENOEXEC`. The shell prints:

```
--- attempting execve ---
./run.sh: /tmp/df0020_oob_elf: Exec format error
```

No SIGSEGV, no OOB read. (3/3 reproducible.)

## Files

| File              | Purpose                                              |
|-------------------|------------------------------------------------------|
| `elf_note_oob.c`  | minimal C generator for the crafted ELF              |
| `elf_note_oob.py` | original Python seed (kept for reference)            |
| `build.sh`        | exact build command (`cc -O2 -Wall ...`)             |
| `run.sh`          | exact run invocation (craft + chmod + exec)          |
| `VERDICT.md`      | full narrative (mechanism, fix, validation)          |
| `fix.diff`        | `git apply`-able fix to `sys/kern/imgact_elf.c`      |
| `build.log`       | full final build output                              |
| `run.log`         | baseline (#0) decisive run, full output              |
| `run.2.log`       | baseline 5-run stress (OOB silent each time)         |
| `fix_build.log`   | full single-fix kernel build output                  |
| `fix_run.log`     | patched (#1) decisive run, full output               |
| `env.txt`         | guest environment (uname, cc, sysctl)                |
| `manifest.json`   | machine-readable catalog                             |
