# DF-5099 — joyopen/joyclose exclusive-access protocol is an unlocked TOCTOU

## What
`joyopen()` implements device exclusivity as check-then-set on the shared
`joy->timeout[i]` flag with no serialization at all
(`sys/dev/misc/joy/joy.c:159-162`):

```c
if (joy->timeout[i])
    return EBUSY;
joy->x_off[i] = joy->y_off[i] = 0;
joy->timeout[i] = JOY_TIMEOUT;
```

`joyclose()` clears the same flag (`joy.c:173`). The file contains no lock,
token, or atomic — the entire open/close/read/ioctl state machine
(`timeout[2]`, `x_off[2]`, `y_off[2]`) is unsynchronized.

## Defect mechanics
* Two CPUs racing `open("/dev/joy0")` can both read `timeout[0] == 0`,
  both skip `EBUSY`, and both store `JOY_TIMEOUT` — the exclusive-open
  guarantee is bypassed.
* A `close()` by one holder zeroes the flag while another holder still has
  the fd open, permanently dropping the claim for the remaining holder.
* `joyioctl()` mutates the same ints concurrently with an in-flight
  `joyread()` (harmless on its own — plain int fields, no tearing on the
  supported arches, and the read loop snapshots `timeout` once at
  `joy.c:195`).

## Security impact
None beyond the broken exclusivity semantics: `/dev/joy*` is created
`0600 root` (`make_dev(..., 0, 0, 0600, ...)` at `joy.c:133`), so only
root reaches any of this, and the raced state is plain ints in a
devclass-allocated softc that is never freed (no detach path) — there is
no lifetime or memory-safety consequence. Filed as Info hardening /
correctness.

## Build / run (as executed on the audit guest)
```
cc -O2 -Wall -o joy_open_race joy_open_race.c     # -> BUILD_OK, no warnings
./joy_open_race
```
Expected on hardware with `/dev/joy0`: exit 0 with
`iterations=2000 EBUSY-bypassing iterations=N stray opens=M`, N>0.
Observed on this guest: exit 2, `open /dev/joy0: No such file or directory
(errno=2)` — joy(4) never attaches (no ISA gameport; `kldload joy` alone
creates no node), so the race is not runtime-exercisable here. See
`run.log`, `env.txt`.

## Fix
One-word claim via `atomic_cmpset_int()` (see `fix.diff`).
