# DF-2769 — FILM SCRIPT (disclosure-video demo run)

**One continuous take, two actors, stock kernel.**
Deliverables: `run_film.log` (untrimmed driver transcript, the authoritative
record), `serial_console_film.log` (what the kernel's own serial console
recorded of the decisive arc), `film_run.sh` (the take driver), `film_victim.c`
/ `film_attack.c` / `film_churn.c` (the actors).

* Stock kernel: `DragonFly v6.5.0.1712.g89e6a-DEVELOPMENT #1` (clean-install
  snapshot, no custom build, INVARIANTS off).
* Root appears ONLY as the victim. The attacker is `nobody` (uid 65534) the
  whole way through; `id` is re-checked after the takeover.
* Wall time of the take: 11:40:40 → 11:40:57 UTC (see timestamps in
  `run_film.log`).

## Cast / terminals (for a human re-recording)

| Terminal | Who | Content |
|---|---|---|
| VICTIM window | root admin session inside a modeled terminal (xterm / `ssh -tt` / script(1)) | live `root@dfbsd:~ #` shell doing admin things — then the terminal dies |
| ATTACKER window | `nobody` | discovers the stale master, takes the session over, talks to the root shell |
| NARRATOR (optional) | the demo driver console | beat banners + proofs; on camera this is the voiceover |

The automated take drives all three from one script (`film_run.sh`), teeing
everything into `run_film.log` **and** `/dev/console` (serial line). For a
two-terminal re-recording, run the beats below manually in the same order —
every command is in the log.

## Beat sheet (timestamps from run_film.log)

**BEAT 0 — SETUP [11:40:40]** — stage: kernel line, narrator uid=0, attacker
will be `nobody` (uid shown up front so nobody can accuse a swap), proof
files pre-checked absent, the two actors compiled on camera.

**BEAT 1 — VICTIM LIVE [11:40:40–:45]** — a root admin session opens on
`/dev/pts/0` (pid 867) inside its terminal. On the VICTIM screen the shell is
*alive and interactive*: `whoami` → `root`, `hostname` → `dfbsd`, `id` →
`uid=0(root)…`, `echo ROOT-SESSION-ALIVE`. This is the session we will own.

**BEAT 2 — TERMINAL CRASH [11:40:45]** — `*** TERMINAL CRASH ***` (xterm
killed / ssh connection dropped): the master closes. Kernel fact box on
screen: `ptcclose()` disarms every `ptcopen()` gate, leaves `/dev/ptm/N`
registered `0666 root:root`, tty goes ZOMBIE, SIGHUP is ignored by the
nohup-style session leader. `ps` proof: pid 867, **UID 0**, still running;
slave device `/dev/pts/0` still present. A root shell now waits silently on a
dead pty whose master seat is empty.

**BEAT 3 — ATTACKER PROBE, DEFAULT DEVFS [11:40:45]** — ATTACKER window:
`su -m nobody -c "/tmp/film/film_attack /dev/pts/0"` →
`open("/dev/ptm/0") -> No such file or directory`, `RESULT=FENCED`.
Honesty beat: on a stock devfs ruleset `/dev/ptm` is DEVFS_HIDDEN; `ls
/dev/ptm*` shows only `/dev/ptmx`. The bug needs a config precondition
(next beat).

**BEAT 4 — CONFIG PRECONDITION [11:40:45]** — the system churns (any login
allocates a pty — `film_churn`), then the host's admin rules are (re)applied:
`show ptm` + `show ptm/*` via `devfsctl -a` — exactly the rule class jail
devfs rulesets and expose-/dev recipes install. ATTACKER view flips:
`ls -l /dev/ptm*` now shows `/dev/ptm/0` **crw-rw-rw- root root** — the dead
terminal's seat, world-openable.

**BEAT 5 — TAKEOVER, INJECTION, LIVE CONTROL [11:40:47–:57]** — same
unprivileged command as BEAT 3, different world now:
1. `open("/dev/ptm/0", O_RDWR)` → **master fd opened** — nobody is the new
   master of a live root session;
2. `TIOCSETAW(c_cflag |= CLOCAL)` → **TS_ZOMBIE cleared** (the kernel's last
   fence, cleared by the attacker itself, sys/kern/tty.c:1065-1077);
3. INJECT `id > /tmp/df2769_pwned; cat /etc/master.passwd > /tmp/df2769_secret`
   — the ATTACKER screen shows the root prompt accepting the payload;
4. INJECT `whoami; hostname` — the ATTACKER screen shows the root shell
   answering **`root` / `dfbsd`** live (the money shot);
5. INJECT `echo DF-2769-PWNED > /tmp/df2769_marker` — a 0:0-owned marker;
`RESULT=HIJACKED`.

**BEAT 8 — PROOF [11:40:57]** — attacker still `nobody`; the three files
created *by the root session* for the attacker: all `root:wheel`-owned;
`/tmp/df2769_pwned` contains `uid=0(root)`; `/tmp/df2769_secret` is
`/etc/master.passwd` (root's password hash on screen, truncated); marker
`owner=root:wheel`.

**BEAT 9 — CLEANUP [11:40:57]** — victim session terminated, proof files
removed, devfs rules cleared. `FILM END`.

## Re-recording gotchas (learned the hard way — keep this order)

1. **Never run `devfsctl -r` (node reset)** on a live system in this demo:
   it breaks later `show` rule application for the boot. Clear rules with
   `devfsctl -c` only; nodes re-hide at next boot.
2. **A failed `open("/dev/ptm/N")` caches a negative namecache entry** that
   survives the subsequent `devfsctl -a` (rule apply doesn't bump devfs's
   `mnt_namecache_gen`; only node *creation* does, sys/vfs/devfs/devfs_core.c:277).
   Between the fenced probe (BEAT 3) and the rule application (BEAT 4) you
   must let the system churn once — any pty allocation/free (`film_churn`,
   i.e. what any login does) — otherwise `ls`/`open` of `/dev/ptm` keep
   returning ENOENT for up to the life of that entry. This is why the driver
   runs `film_churn` *before* `devfsctl -a`.
3. Churn *after* the rules would re-hide `/dev/ptm` (creation-time ptm magic,
   devfs_core.c:2125-2133) — always churn **then** apply rules.
4. The victim session leader must ignore SIGHUP (nohup semantics) and tolerate
   tty EOF to survive the terminal death — that is the surviving-session
   class this finding is about (crashed xterm / dropped ssh with a nohup'd
   leader).
5. Serial console (`/dev/console`) drops lines under burst; it caught the
   whole decisive arc (BEAT 5→END) in `serial_console_film.log`, but the
   authoritative untrimmed transcript is `run_film.log`.

## Fix-contrast (not in this take)

The stock clean-install snapshot ships no `/usr/src`; producing the patched
kernel means a full source + kernel build, which the mission says to skip.
Prior fix validation stands unchanged: on the fix.diff kernel (VERDICT.md
"Fix validation"), with identical setup and devfs exposure, the stale master
open fails with **EIO** (`PF_SOPEN` fence), no injection, no proof file.
