# DF-2174: __hrtimer_task epilogue unconditionally clears timer->active — UAF

## Verdict: NOT REPRODUCED (HW-gated) — source-confirmed real bug

## Reachability
**NOT reachable on this QEMU guest.** `__hrtimer_task()` is in `sys/dev/drm/linux_hrtimer.c`,
part of `drm.ko`. HRTimers are used by DRM GPU drivers for periodic operations (radeon/amdgpu
display vblank, hardware polling). Without GPU hardware, no hrtimers are started.

## Mechanism (source-confirmed)
`__hrtimer_task()` at `linux_hrtimer.c:42-69`:
```c
static void __hrtimer_task(void *arg, int pending) {
    struct hrtimer *timer = arg;
    enum hrtimer_restart restart;

    lwkt_gettoken(&timer->timer_token);
    timer->running = true;
    if (timer->cancel) { ... goto done; }
    lwkt_reltoken(&timer->timer_token);
    restart = timer->function(timer);      // callback may call hrtimer_start()
    lwkt_gettoken(&timer->timer_token);
    timer->running = false;
    timer->active = false;                 // ← BUG: unconditional clear (line 60)
    if (!timer->cancel && restart == HRTIMER_RESTART) {
        timer->active = true;              // re-set only for RESTART
        systimer_init_oneshot(...);
    }
done:
    lwkt_reltoken(&timer->timer_token);
}
```

The bug: if the callback calls `hrtimer_start()` (which sets `timer->active = true` at line
113 and arms a new systimer) and then returns `HRTIMER_NORESTART`:
1. `hrtimer_start()` armed a new systimer with `active=true`
2. Epilogue line 60: `timer->active = false` — **clears the active flag**
3. `restart == HRTIMER_NORESTART` → re-arm at lines 63-64 is skipped
4. Result: **systimer is armed but `active=false`**

When `hrtimer_cancel()` is later called (line 122-150):
```c
if (timer->active) {   // FALSE — doesn't enter cancel path
    ...
}
```
It returns immediately without canceling the armed systimer. When the systimer fires, it
enqueues the task again. If the `struct hrtimer` has been freed → **use-after-free** via
`timer->function(timer)`.

## Primitive
- Class: UAF (timer callback from freed memory)
- The un-cancellable systimer fires after the hrtimer is freed
- On no-SMEP guest: hijacked function pointer → shellcode → `uid=0`

## Fix
`fix.diff`: Set `timer->active = false` **before** the callback (the oneshot that triggered
us has fired and is no longer armed), not after. The callback's `hrtimer_start()` correctly
sets `active=true` and arms a new timer; the epilogue no longer clobbers it.
