β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2174

__hrtimer_task epilogue unconditionally clears timer->active, defeating callback re-arm via hrtimer_start + NORESTART β€” un-cancellable timer leading to UAF

Summary

__hrtimer_task epilogue at line 60 executes timer->active=false unconditionally after callback returns even when callback itself called hrtimer_start() (which set active=true at line 113 and armed a new systimer) and returned HRTIMER_NORESTART. Makes timer un-cancellable: hrtimer_cancel (line 127) sees active==false skips all teardown. Systimer remains armed fires indefinitely on freed hardware state leading to UAF when driver frees timers containing object. amdgpu dce_virtual uses exactly this pattern: dce_virtual_vblank_timer_handle (dce_virtual.c:700-713) calls hrtimer_start at line 709 returns HRTIMER_NORESTART at 712. After callback fires active=false but systimer armed. hrtimer_cancel at dce_virtual.c:736 returns 0 without stopping systimer. Timer fires indefinitely on disabled CRTC causing GPU state corruption/hang. When device removed/module unloaded hrtimer struct freed systimer fires __hrtimer_function dereferences info->data (freed hrtimer) enqueues timer->task (freed) __hrtimer_task calls timer->function (freed function pointer) = arbitrary code execution or panic.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2174 Β· 6 files
FileTypeDescriptionSize
VERDICT.md verdict source-level analysis with path:line citations 2.4 KB ↓ raw
reachability.txt environment guest PCI/device survey proving no required HW 1.6 KB view raw
fix.diff suggested-fix git-apply-able fix (validated: applies clean) 701 B view raw
build.sh build-log documents HW requirement 550 B view raw
run.sh run-log documents HW requirement 272 B view raw
env.txt environment guest uname and environment 491 B view raw
VERDICT.md verdict source-level analysis with path:line citations
↓ download raw

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:

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):

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

git apply --check clean + drm.ko compiles

git apply --check clean + drm.ko compiles

Confirmed kernel references

β€”

Detail

Exploit chain

none (HW-gated)

Evidence (decisive lines)

HW-GATED (no GPU). Source-confirmed: hrtimer epilogue clears active=false unconditionally even if callback called hrtimer_start -> uncancellable systimer -> UAF after free.

Verified recommended fix

HW-GATED (no GPU). Source-confirmed: hrtimer epilogue clears active=false unconditionally even if callback called hrtimer_start -> uncancellable systimer -> UAF after free.

Verdict

HW-GATED (no GPU). Source-confirmed: hrtimer epilogue clears active=false unconditionally even if callback called hrtimer_start -> uncancellable systimer -> UAF after free.