# DF-1093 — NULL pointer dereference in ppb_pnp_detect

## Verdict

**NOT REPRODUCED at runtime (hardware-gated) — STATIC VERIFICATION + HARNESS CONFIRMED.**

The bug exists verbatim in `sys/bus/ppbus/ppbconf.c:213-288`. Six call
sites in `ppb_pnp_detect` compute `search_token(token, UNKNOWN_LENGTH, ":") + 1`
**unconditionally** without checking whether `search_token` returned
NULL. `search_token` (`:177-206`) returns NULL when the inner token is
not found in the scan window, which a malicious IEEE 1284 peripheral can
arrange by sending a PnP ID string with a keyword (`MFG`, `MDL`, `VER`,
`REV`, `CLS`, `CMD` etc.) but no `:` separator before the next NUL.
`NULL + 1 = (char *)0x1` is then dereferenced by `kprintf("%s", ...)`,
which on the kernel heap/stack-less low addresses page-faults — kernel
panic at every `ppbus` attach (boot or `kldload ppbus`).

The audit QEMU guest has **no parallel-port hardware** (`pciconf -l`
shows no ISA/parallel bridge; `dmesg | grep -c pnp` = 0), so the path is
not exercised at runtime here. The trigger requires attacker-controlled
IEEE 1284 hardware (`AV:P`/`AC:L`) — same hardware-gating class as the
DF-1071 PnP finding.

The `df1093_harness` userspace C program installs a SIGSEGV handler,
mirrors `search_token` and the MFG-handling block, then feeds it the
malicious string `"MFG"` (keyword, no `:`). The **unpatched** harness
traps the SIGSEGV at address 1 — the kernel equivalent of which is a
page-fault panic. The **patched** harness (NULL check before the `+ 1`)
returns normally.

## Mechanism (confirmed by source trace)

`search_token` (`ppbconf.c:177-206`):

```c
static char *
search_token(char *str, int slen, char *token)
{
    ...
    if (slen == UNKNOWN_LENGTH)
        for (slen = 0, p = str; *p != '\0'; p++)
            slen++;                          /* :187  scan to next NUL */
    ...
    for (i = 0; i <= slen-tlen; i++) {       /* :197 */
        for (j = 0; j < tlen; j++)
            if (str[i+j] != token[j])
                break;
        if (j == tlen)
            return (&str[i]);                /* :202 match */
    }
    return (NULL);                           /* :205 miss */
}
```

In `ppb_pnp_detect` the `;` characters of the PnP string are first
replaced by `\0` (`:236-237`), so each keyword lives in its own
NUL-terminated slice. A malicious peripheral supplies `MFG\0` (no `:`),
and the inner `search_token(token, UNKNOWN_LENGTH, ":")` scans from
`MFG` to the next NUL (which is the `\0` immediately after `MFG`),
finds no `:`, returns NULL. The six affected sites at `:242, :249,
:255, :259, :264, :271` all do `NULL + 1` and pass the result to
`kprintf("%s", ...)`, which dereferences `(char *)0x1` — kernel page
fault, panic.

This is a particularly reliable crash because (a) it happens at attach
time (boot or module load), not at first use; (b) it requires no
particular privilege — merely plugging the peripheral in is enough;
(c) `DONTPROBE_1284` is NOT defined by default, so the probe runs
unconditionally when a ppbus is present.

## Reproduction

```
$ sh verify.sh        # 7/7 static checks; 6 unguarded +1 sites counted
$ cc -O0 -o df1093_harness df1093_harness.c
$ cc -O0 -DFIX -o df1093_harness_fix df1093_harness.c
$ ./df1093_harness        # SIGSEGV at addr 1 (kernel: panic at boot)
$ ./df1093_harness_fix    # safe skip
```

## Fix

`fix.diff` adds an explicit `if (val != NULL)` guard at each of the six
sites. The pattern is mechanical:

```c
char *val = search_token(token, UNKNOWN_LENGTH, ":");
if (val != NULL)
    kprintf("...", val + 1);
```

For the MFG and MDL sites (where the empty-string fallback is harmless),
the ternary `val != NULL ? val + 1 : ""` is used. For the CLS site, the
NULL check additionally prevents the later `search_token(class, len, ...)`
call at `:278` from receiving a garbage `class` pointer. The
`nativekernel` build of the patched file succeeds; the harness validates
the algorithm-level correctness.
