# DF-4536 — lptopen() sc_state check-then-set TOCTOU (latent: blocked by the mplock in the current configuration)

File: `sys/dev/misc/lpt/lpt.c:458-462` (pass 2)

```
if (sc->sc_state) {
        return(EBUSY);
} else
        sc->sc_state |= LPTINIT;
```

No lock, no atomic: two concurrent lptopen() calls that both read
`sc_state == 0` both proceed — double `kmalloc(BUFSIZE)` +
`kmalloc(BUFSTATSIZE)` (the first pair leaks), and the two later
closes `kfree()` the same (second) buffer pair twice → heap
double-free in the private M_LPT zone.

## Runtime status on this tree: BLOCKED (why the PoC cannot hit)

lpt's `dev_ops` head flags are 0 (lpt.c:191-198), so
`dev_needmplock()` (sys/kern/kern_device.c:119-122) is true and
`dev_dopen()` wraps `d_open` in `get_mplock()`/`rel_mplock()`
(sys/kern/kern_device.c:142-156): **all lpt cdev operations execute
under Giant**, fully serializing the check-then-set.  (On top of
that, same-vnode opens also pass through devfs's exclusive vnode
section, sys/vfs/devfs/devfs_vnops.c devfs_spec_open.)

PoC `lpt_openrace` raced:

* `/dev/lpt0` vs `/dev/lpt0` — 300 rounds (full open path, fds held
  300 ms so sequential double-success is impossible): 0 hits;
* `/dev/lpt0` vs `/dev/lpt0.ctl` (different cdevs/vnodes, shared
  softc): 300 rounds, 0 hits;
* `/dev/lpt0.ctl` vs `/mnt/df2/lpt0.ctl` (two devfs mounts → two
  vnodes, no shared vnode lock): ~3,600 effective rounds, 0 hits —
  the mplock is the remaining serializer, and it is airtight for
  same-driver d_open entries.

(An early ".ctl vs .ctl same vnode" run printed many double-successes;
those were **sequential** pairs — sibling's open after the holder's
close — not races; the fd-hold fix in the PoC removes that false
signal, after which all variants score 0.)

## Why it is still filed (Info)

The driver-level TOCTOU is real source code; the serialization is
*incidental* (a global side effect of not being D_MPSAFE).  Any future
conversion of lpt to `D_MPSAFE` (the direction DFly pushes all
drivers) makes the window live with a double-free consequence.
`fix.diff` adds the missing per-unit serialization now.

## Reproduce (attempts)

```
cc -O -Wall -o lpt_openrace lpt_openrace.c
mount -t devfs devfs /mnt/df2
./lpt_openrace /dev/lpt0 /dev/lpt0 300 350          # full path
./lpt_openrace /dev/lpt0 /dev/lpt0.ctl 300 450      # mixed
./lpt_openrace /dev/lpt0.ctl /mnt/df2/lpt0.ctl 30000 3 25000
```
Expected on a hypothetical MPSAFE build: `[+] HIT ... BOTH opens
returned 0` followed (full path) by the double-free on the two closes
and eventual heap corruption in the M_LPT zone.  On this tree: zero
hits everywhere (mplock).
