# DF-4197 VERDICT

**Status: inconclusive (kernel manifestation hardware-gated: no DRM device in
guest) — the shim protocol violation itself is proven exactly and
deterministically on the DragonFly guest, including under 2-thread stress
(4057 torn snapshots accepted under DF semantics, 0 under fixed semantics).**
Impact: corrupted vblank counter/timestamp pairs delivered through the DRM
UAPI (logic corruption; no memory unsafety, no info leak of kernel memory —
both torn values are legitimate vblank values). uid=0 chain N/A.

## Root cause (path:line)

* `sys/dev/drm/include/linux/seqlock.h:77-83` — `write_sequnlock()` does
  `sl->sequence--`, returning the seqcount to its pre-write value
  (0→1→0 per write section). Linux's `write_seqcount_end()` increments
  (0→1→2), making the count change visible forever after.
* `sys/dev/drm/include/linux/seqlock.h:90-94` — `read_seqbegin()` is a plain
  load; it neither waits for the count to become even nor otherwise prevents
  beginning a read against a count that a completed writer has "restored".

Consequence for the audited file:

* `sys/dev/drm/drm_vblank.c:784-788` — reader of {count,time}; a writer
  (`store_vblank`, drm_vblank.c:102-105) completing entirely inside the
  reader's two field loads leaves `sequence == start` → no retry → torn pair
  accepted.
* Consumers: drm_vblank.c:1518 (`drm_wait_vblank_reply`), :1801-1802
  (`drm_crtc_get_sequence_ioctl`), :1861/:1899 (queue_sequence),
  drm_crtc_send_vblank_event (:913), drm_crtc_arm_vblank_event (:888).
* On i386 the u64 `count` itself can tear (two 32-bit halves); on amd64 each
  field is atomic but the pair is not.

Also affected (same shim, out-of-file consumer, mentioned for blast radius):
i915 engine stats write_seqlock (sys/dev/drm/i915/intel_engine_cs.c:1623).

## Proof

Deterministic demo (verbatim shim algorithm; writer completes between the
reader's two loads — the canonical seqcount race):

```
$ ./seqlock_aba demo
[demo] semantics=DF-shim
[demo] read_seqbegin -> 0; reader loaded count=100 (preempted here...)
[demo] writer ran a FULL cycle: count=101 time=200000, sequence=0
[demo] reader resumed: loaded time=200000, read_seqretry=0
[demo] RESULT: TORN PAIR ACCEPTED {count=100(old), time=200000(new)} — reader believes it is consistent
rc=1
$ ./seqlock_aba demo_fixed
[demo] writer ran a FULL cycle: count=101 time=200000, sequence=2
[demo] RESULT: retry=1 — torn pair REJECTED, reader retries
rc=0
```

Stress (2 threads, 4M writer cycles, invariant `time == count*1000`):

```
[stress] semantics=DF-shim  consistent=8910351 torn_accepted=4189
[stress] semantics=FIXED(Linux) consistent=6953440 torn_accepted=0
```

(earlier independent run: 7767673/4057 and 6980918/0 — see run.log)

Both semantics variants live in the same binary; `*_fixed` compiles the
corrected write_sequnlock (`sequence++`) and read_seqbegin (wait-even), i.e.
exactly what `fix.diff` applies to the shim header.

## Why Low severity

The torn values are still real vblank counts/timestamps (no kernel-memory
disclosure), and the practical effect is limited to wrong sequence numbers /
timestamps / event-target computations on the vblank UAPI — visible as
compositor glitches or early/missed vblank waits. No memory-safety
consequence found on any consumer of count/time.

## Fix validation

`fix.diff` (sys/dev/drm/include/linux/seqlock.h): `write_sequnlock` advances
the count (`sequence++`) and `read_seqbegin` spins until the count is even —
restoring Linux read_seqcount_begin/write_seqcount_end semantics.
Harness-level validation: `demo_fixed`/`stress_fixed` reject every torn
snapshot (0 accepted over ~7M reads). A full DRM kernel build/boot is not
possible on this guest (no GPU), so kernel-level validation is
`fix_status: inconclusive`. All in-tree users (drm_vblank store/read,
i915 engine stats) use the API in the Linux-expected way, so the semantic
restoration is safe for them.

## Killed negatives (adjacent code audited)

* `store_vblank` writer discipline: writers are serialized by the embedded
  real spinlock AND by vblank_time_lock (assert_spin_locked, :98) — writer vs
  writer is fine; only the reader protocol is broken.
* seqcount_t section of the same header (__read_seqcount_begin waits for
  even) is correct — only the seqlock_t half is broken.
* drm_vblank_count (:277-285) unlocked read: upstream-identical; on amd64
  practically atomic; noted here as an i386-only aggravator of this same
  finding rather than a separate one.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
