# DF-0898 — NULL pointer deref when DEVFS_RULE_LINK is used without DEVFS_RULE_NAME

## Summary

`devfs_rule_create_link()` in `sys/vfs/devfs/devfs_rules.c:243` unconditionally
dereferences `rule->name[rule->namlen-1]`. However, `rule->name` is only set by
`devfs_rule_alloc()` when the `DEVFS_RULE_NAME` flag is present in
`rule_type` (line 99). A rule that sets `rule_cmd = DEVFS_RULE_LINK` **without**
`rule_type = DEVFS_RULE_NAME` is accepted by `devfs_rule_alloc()` (because
`linkname` is valid on its own), and is dispatched to `devfs_rule_create_link()`
by `devfs_rule_check_apply()` (line 331) whenever `rule_cmd & DEVFS_RULE_LINK`,
regardless of `rule_type`. The result is a NULL-pointer (+wrap) dereference:

```
rule->name[rule->namlen-1]   ==   NULL[(u_char)0 - 1]
                              ==   *((char*)NULL + (ptrdiff_t)-1)
                              ==   *(char*)0xFFFFFFFFFFFFFFFF
                              ==>  page fault  ==>  kernel panic
```

Furthermore, because `rule_type == 0`, the rule has **no JAIL / TYPE / NAME
filter** — it matches every node on the matched mount, so the very first node
encountered during `DEVFS_RULE_APPLY` triggers the panic.

## Impact

`panic` (kernel NULL-deref → DoS) on the default GENERIC kernel. The fault
address (`0xffffffffffffffff`) is fixed and uncontrolled, so this is **pure
DoS**, not an escalation primitive. No memory corruption, no info leak.

## Reachability / privilege boundary

`/dev/devfs` is `0600 root:wheel`, so the direct trigger requires root. On a
default non-jailed GENERIC this is a **root→kernel hardening gap** (root already
has kldload / `/dev/mem`, so no privilege boundary is crossed). The realistic
escalation-relevant angle is **jail escape**: if a host admin exposes
`/dev/devfs` inside a jail's devfs ruleset (non-default but plausible), jailed
root (`cr_uid=0`) can trigger this and panic the **host** kernel from inside the
jail. That crosses the jail isolation boundary.

## Build / Run

```
./build.sh        # cc -O0 -o trigger trigger.c
sudo ./run.sh     # must be root (opens /dev/devfs)
```

**Expected on unpatched (`6.5-DEVELOPMENT #0`):**
- `DEVFS_RULE_ADD` returns 0 (rule accepted, name left NULL).
- `DEVFS_RULE_APPLY` triggers `Fatal trap 12` (page fault) at
  `devfs_rule_create_link+0x..` with `fault virtual address = 0xffffffffffffffff`.
- Guest panics, SSH dies.

**Expected on patched (`#1`):**
- `DEVFS_RULE_ADD` returns `EINVAL` (alloc rejects LINK-without-NAME).
- `DEVFS_RULE_APPLY` never runs (no rule to apply).
- Guest stays up; program exits cleanly.

## Fix

`fix.diff` — two-part minimal fix:
1. `devfs_rule_alloc()`: reject at input boundary any LINK rule that lacks NAME
   (root-cause fix — closes the bug for new rules).
2. `devfs_rule_create_link()`: defensive NULL guard before the dereference
   (defense in depth — protects against any pre-existing rule or future
   code path that bypasses alloc).

Supersedes the finding proposal ("require NAME flag for LINK or defensive NULL
check") — implements BOTH.
