Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.
amdgpu/TTM: kernel oops in `ttm_lru_bulk_move_tail()` (corrupt LRU `list_head`) from `amdgpu_cs_ioctl`
Reported by anavarre · filed Sep 20, 2026 · open at source
At a glance
- 1 · What is claimed?
- Only the report text below — no validator has restated it as checkable claims yet.
- 2 · What does it affect?
- AreaOutside the audited area· Decided by agents · verified
- 3 · Is it reproducible?
- No validation has been run yet.
- 4 · What remains unclear?
- Not validated yet: no claims, no reproducer.
- 5 · What happens next?
- Decided by agents and verified by a second, independent agent — see the decision.
Original report
System details
AMD Strix iGPU, Radeon 880M/890M (1002:150e rev c1, GC 11.5.x class)
What's wrong?
Status: system-fatal, hard reset required. Observed once on 7.2.5, never on 7.1.9.
Subsystem: drivers/gpu/drm/ttm + drivers/gpu/drm/amd/amdgpu
Hardware: AMD Strix iGPU, Radeon 880M/890M (1002:150e rev c1, GC 11.5.x class)
1. Summary
A DRM_IOCTL_AMDGPU_CS submission oopsed inside ttm_lru_bulk_move_tail() while
splicing a bulk-move range into the resource manager LRU. The faulting store islast->lru.next->prev = first->lru.prev, where last->lru.next held the non-pointer
value 0x0000000c00000009 — a corrupted or never-initialised list_head.
Because the oops happens inside the `bdev->lru_lock` critical section, the lock is
never released (exited with irqs disabled, preempt_count 1). 24 seconds later every
other GPU client on the machine — Hyprland, Firefox, Chrome, Telegram, and a kworker —
was spinning in ttm_resource_manager_usage() → _raw_spin_lock() and tripped the
soft-lockup watchdog on 5 CPUs. The machine was unrecoverable.
Two distinct issues are worth separating:
- Root cause: a
ttm_lru_bulk_move_posholds a resource whoselrulist node is
garbage by the time ttm_lru_bulk_move_tail() runs (use-after-free / stale pos).
2. Blast radius: oopsing under lru_lock converts a single client's bad submit into
a full system hang. Worth hardening independently of (1).
2. Environment
| Item | Value |
|---|---|
| Kernel | 7.2.5-3-omarchy — Linux version 7.2.5-3-omarchy (linux-omarchy@omarchy) (gcc (GCC) 16.2.1 20260810, GNU ld (GNU Binutils) 2.47) #1 SMP PREEMPT_DYNAMIC Mon, 14 Sep 2026 19:55:01 +0000 |
| Kernel srcversion | build id 240f9ec5a8022b1a7f578b0199033bf067658fbc |
ttm.ko srcversion | af53f3d7b80864bd8ae3177e53cd701b6900fe82 |
amdgpu.ko srcversion | f0d391bee47e81c4f78dac128061d1aaf220d33f |
| Distro | Omarchy 4.0.4 (Arch-based), omarchy-settings 4.0.4-1 |
| GPU | c4:00.0 Display controller [0380]: AMD/ATI Strix [Radeon 880M / 890M] [1002:150e] (rev c1) — integrated, no dGPU |
| Mesa | 1:26.2.2-1 (upgraded 2026-09-18 18:04) |
| GPU firmware | linux-firmware-amdgpu 20260810-2 |
| Machine | AZW SER9 (Beelink SER9), board SER9, BIOS SER9T103 dated 2024-10-28 |
| RAM | 27 GiB total (~8.8 GiB in use at crash time), zswap.enabled=0, zram active |
| Compositor | Hyprland (Wayland) under uwsm, plus omarchy-shell (Quickshell/QML) |
| Preemption | CONFIG_PREEMPT=y, PREEMPT_DYNAMIC (PREEMPT(full) at runtime) |
| Debug config | CONFIG_SLUB_DEBUG=y, CONFIG_SLAB_FREELIST_HARDENED=y, CONFIG_FORTIFY_SOURCE=y. No DEBUG_LIST, no LOCKDEP/PROVE_LOCKING, no KASAN. |
/proc/cmdline:
quiet splash cryptdevice=PARTUUID=f429f303-5002-48c0-80cb-b6ee833c30d1:root root=/dev/mapper/root zswap.enabled=0 rootflags=subvol=@ rw rootfstype=btrfs initramfs_async=0 loglevel=0 systemd.show_status=false rd.udev.log_level=0 vt.global_cursor_default=0No amdgpu.* module parameters set on the command line; all /sys/module/amdgpu/parameters/*
are at defaults (gpu_recovery=-1, sched_policy=0, sched_jobs=32, sched_hw_submission=2,dcfeaturemask=1026, ppfeaturemask=0xfff7bfff, dc=-1, tmz=-1, noretry=-1).
3. Regression window
| Boot | Date | Kernel | Outcome |
|---|---|---|---|
| -5 | 2026-09-18 | 7.1.9-arch1-2 | clean |
| -4 | 2026-09-18 18:02 | 7.1.9-arch1-2 | clean |
| -3 | 2026-09-18 18:13 → 23:34 | 7.2.5-3-omarchy | clean |
| -2 | 2026-09-18 23:35 → 00:38 | 7.2.5-3-omarchy | clean |
| -1 | 2026-09-19 07:34 → 2026-09-20 10:37 | `7.2.5-3-omarchy` | crash |
linux-omarchy 7.2.5-3 was installed 2026-09-18 18:04 (pacman log), replacinglinux 7.1.9-arch1-2. The crash is on the third boot of the new kernel, after a
27-hour uptime. grep -c ttm_lru_bulk_move_tail over boots -5..-2 returns 0 in all
cases — the trace has never appeared under 7.1.9 either. This is suggestive of a 7.2.x
regression but is a single data point; it is not bisected.
Mesa 1:26.2.2-1 also landed in the same 2026-09-18 update, so a userspace-side
trigger change cannot be excluded — though nothing Mesa does should produce this.
4. Reproduction
Not reproducible on demand. Single occurrence.
Conditions at the time of the crash:
- Push-to-talk dictation via voxtype 1.0.1 (
voxtype-bin 1.0.1-1). Its GTK4 on-screen
indicator process voxtype-osd-gtk maps a small always-on-top Wayland surface each
time recording starts.
- The oops fires in that OSD process at the moment the window is mapped, on what is
effectively the first command submission for the new surface:
10:37:24.304 voxtype[176898]: INFO Recording started (external trigger)
10:37:24.306 voxtype[176898]: INFO Using audio device: default
10:37:24.??? kernel: BUG: unable to handle page fault ... Comm: voxtype-osd-gtk PID 176928
10:37:24.575 voxtype[176928]: INFO voxtype_osd_gtk4: showing (frame seq=10089, last_at=0.004s ago)- The same show/hide cycle had completed successfully **9 times earlier in the same
boot** (this was showing #10) — so mapping the OSD is not sufficient by itself. The
bug is a race.
- ~3 minutes earlier (10:34:41) the session went through an **omarchy-shell lock/unlock
cycle** (omarchy lock … session-locked=false … unlocked), which tears down and
recreates compositor surfaces. Possibly relevant to how a stale bulk-move pos
survived.
- Hyprland reloaded its keymap at 10:37:17 and 10:37:22 (xkbcomp warnings), i.e. there
was compositor churn in the seconds before the fault.
- Concurrent GPU clients: Hyprland, Firefox, Chrome, Telegram Desktop, omarchy-shell.
- No prior GPU trouble in this boot: zero GPU reset, zero ring timeouts, zero
VM_L2 / VM page faults in the whole 27-hour boot (`grep -icE "GPU reset|ring .*
timeout|VM_L2|page fault \(src_id"` → 0). The oops comes out of a clean-running GPU.
5. Primary oops — full trace
BUG: unable to handle page fault for address: 0000000c00000011
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: Oops: 0002 [#1] SMP NOPTI
CPU: 1 UID: 1000 PID: 176928 Comm: voxtype-osd-gtk Not tainted 7.2.5-3-omarchy #1 PREEMPT(full) 240f9ec5a8022b1a7f578b0199033bf067658fbc
Hardware name: AZW SER9/SER9, BIOS SER9T103 10/28/2024
RIP: 0010:ttm_lru_bulk_move_tail+0x144/0x1e0 [ttm]
Code: d2 74 51 48 8b 42 30 49 8b 48 08 48 8b 6a 48 48 8b 80 70 01 00 00 48 83 c1 40 4a 8b 74 d0 08 48 8b 01 48 89 45 00 48 8b 6a 48 <48> 89 68 08 89 f8 48 8d 6a 40 48 c1 e0 04 48 01 f0 4c 01 ce 4c 8b
RSP: 0018:ffffd16528b27910 EFLAGS: 00010282
RAX: 0000000c00000009 RBX: 0000000000000000 RCX: ffff8a708660c820
RDX: ffff8a72c47dc6c0 RSI: ffff8a709c40edb8 RDI: 0000000000000001
RBP: ffff8a7088cc53a0 R08: ffff8a745021d9a8 R09: 0000000000000078
R10: 000000000000001a R11: ffff8a745021d998 R12: ffff8a745021d000
R13: 0000000000000003 R14: 0000000000000006 R15: 00000000000008e4
FS: 00007d6ed184a040(0000) GS:ffff8a7733d71000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000c00000011 CR3: 0000000595b5a000 CR4: 0000000000f50ef0
PKRU: 55555554
Call Trace:
<TASK>
amdgpu_vm_move_to_lru_tail+0x29/0x40 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
amdgpu_cs_ioctl+0x209f/0x22f0 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
? __pfx_amdgpu_cs_ioctl+0x10/0x10 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
drm_ioctl_kernel+0xb6/0x110
drm_ioctl+0x2e9/0x550
? __pfx_amdgpu_cs_ioctl+0x10/0x10 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
amdgpu_drm_ioctl+0x4a/0x80 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
__x64_sys_ioctl+0xc1/0x110
do_syscall_64+0xaa/0x690
? amdgpu_drm_ioctl+0x6a/0x80 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
? __x64_sys_ioctl+0xdc/0x110
? do_syscall_64+0xaa/0x690
? __x64_sys_ioctl+0xdc/0x110
? do_syscall_64+0xaa/0x690
? do_syscall_64+0x5f/0x690
? exc_page_fault+0x90/0x1f0
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7d6ed471be1d
Code: 04 25 28 00 00 00 48 89 45 c8 31 c0 48 8d 45 10 c7 45 b0 10 00 00 00 48 89 45 b8 48 8d 45 d0 48 89 45 c0 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1a 48 8b 45 c8 64 48 2b 04 25 28 00 00 00
RSP: 002b:00007ffe62890140 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ffe62890358 RCX: 00007d6ed471be1d
RDX: 00007ffe628901e0 RSI: 00000000c0186444 RDI: 0000000000000010
RBP: 00007ffe62890190 R08: 00005a046bbd5ae0 R09: 00007ffe628901a0
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000010
R13: 00005a046b9865d0 R14: 00005a046bbd5a80 R15: 00005a046bb20cd0
</TASK>
CR2: 0000000c00000011
---[ end trace 0000000000000000 ]---
note: voxtype-osd-gtk[176928] exited with irqs disabled
note: voxtype-osd-gtk[176928] exited with preempt_count 1Userspace frame: ORIG_RAX = 0x10 (ioctl), RSI = 0xc0186444 = DRM_IOCTL_AMDGPU_CS,RDI = 0x10 (the render-node fd). RAX = -ENOSYS is just the pre-syscall sentinel.
Modules linked in (full list) is in the attached oops-primary.txt; the DRM-relevant
tail is:
... hid_sensor_hub amdgpu drm_buddy amdxcp i2c_algo_bit drm_ttm_helper ttm drm_exec
drm_panel_backlight_quirks nvme gpu_sched aesni_intel drm_suballoc_helper gf128mul
nvme_core serio_raw aead video drm_display_helper nvme_keyring ccp nvme_auth cec wmi thunderbolt6. Faulting instruction — disassembly and decode
Symbolised against the shipped /lib/modules/7.2.5-3-omarchy/kernel/drivers/gpu/drm/ttm/ttm.ko.zst.ttm_lru_bulk_move_tail is at 0x70f0, so +0x144 = 0x7234:
7205: 49 8b 10 mov (%r8),%rdx ; rdx = pos->first
7208: 48 85 d2 test %rdx,%rdx
720b: 74 51 je 725e ; if (!pos->first) continue;
720d: 48 8b 42 30 mov 0x30(%rdx),%rax ; rax = first->bo
7211: 49 8b 48 08 mov 0x8(%r8),%rcx ; rcx = pos->last
7215: 48 8b 6a 48 mov 0x48(%rdx),%rbp ; rbp = first->lru.prev
7219: 48 8b 80 70 01 00 00 mov 0x170(%rax),%rax ; rax = bo->bdev
7220: 48 83 c1 40 add $0x40,%rcx ; rcx = &last->lru
7224: 4a 8b 74 d0 08 mov 0x8(%rax,%r10,8),%rsi ; rsi = &man->lru[j] (ttm_manager_type)
7229: 48 8b 01 mov (%rcx),%rax ; rax = last->lru.next <-- POISON READ
722c: 48 89 45 00 mov %rax,0x0(%rbp) ; first->lru.prev->next = last->lru.next
7230: 48 8b 6a 48 mov 0x48(%rdx),%rbp ; rbp = first->lru.prev
>>> 7234: 48 89 68 08 mov %rbp,0x8(%rax) ; last->lru.next->prev = ... <-- FAULTS
7238: 89 f8 mov %edi,%eax
723a: 48 8d 6a 40 lea 0x40(%rdx),%rbp
...
724c: 49 89 2e mov %rbp,(%r14)
7253: 4c 89 72 48 mov %r14,0x48(%rdx)
7257: 48 89 31 mov %rsi,(%rcx)
725a: 48 89 48 70 mov %rcx,0x70(%rax)The bytes at 0x7229..0x7237 (48 8b 01 | 48 89 45 00 | 48 8b 6a 48 | 48 89 68 08)
match the oops Code: line exactly, with <48 89 68 08> marked as the faulting
instruction. Confirmed decode:
- This is the inlined
list_bulk_move_tail(&man->lru[j], &pos->first->lru, &pos->last->lru)
in the second loop nest of ttm_lru_bulk_move_tail() (outer loop cmp $0x9,%ebx
= TTM_NUM_MEM_TYPES, inner loop cmp $0x4,%edi = TTM_MAX_BO_PRIORITY).
- RAX = 0x0000000c00000009 is pos->last->lru.next. CR2 = RAX + 8 = the prev
field it tried to write.
- RDX = ffff8a72c47dc6c0 (pos->first) and RCX = ffff8a708660c820 (&pos->last->lru,
i.e. pos->last = ffff8a708660c7e0) are both plausible slab addresses — the
structures are mapped; only the embedded list_head contents are junk.
- R13 = 3 and R14 = 6 are the loop indices at fault time; R10 = 0x1a is the
ttm_manager_type index expression. RBX = 0 is the outer counter, so this is
bulk->pos[0][...].
Interpretation of the poison value. 0x0000000c00000009 is not a kernel pointer,
not LIST_POISON1/2, and not a SLUB free poison — it reads as two packed 32-bit words,12 and 9. That is the signature of reading a list_head out of memory that has been
reallocated and is now holding a different struct's small integer fields (e.g. au32/u32 pair). Combined with SLAB_FREELIST_HARDENED being on and noDEBUG_LIST/KASAN to catch it earlier, the most likely mechanism is a
use-after-free / stale `ttm_lru_bulk_move_pos`: the ttm_resource recorded inbulk->pos[i][j].last was freed and its slab object reused, while the bulk-move
structure still referenced it.
Note on partial damage: the store at 0x722c (first->lru.prev->next = <poison>)
executed successfully before the fault. So even at the moment of the oops the real
LRU list has already had a garbage next pointer written into it. Any recovery attempt
after this point would walk a corrupt list.
7. Secondary failure — soft lockups from the leaked lru_lock
Five CPUs wedged, all with the same stack, all waiting on the spinlock the oopsed task
never released:
watchdog: BUG: soft lockup - CPU#5 stuck for 26s! [Hyprland:cs0:1535]
CPU#5 Utilization every 4000ms during lockup:
#1: 100% system, 0% softirq, 0% hardirq, 0% idle
... (same for #2-#5)
CPU: 5 UID: 1000 PID: 1535 Comm: Hyprland:cs0 Tainted: G D 7.2.5-3-omarchy #1 PREEMPT(full)
Tainted: [D]=DIE
RIP: 0010:native_queued_spin_lock_slowpath+0xa2/0x330
Call Trace:
<TASK>
_raw_spin_lock+0x3f/0x60
ttm_resource_manager_usage+0x2e/0x80 [ttm]
amdgpu_cs_ioctl+0x1343/0x22f0 [amdgpu]
drm_ioctl_kernel+0xb6/0x110
drm_ioctl+0x2e9/0x550
amdgpu_drm_ioctl+0x4a/0x80 [amdgpu]
__x64_sys_ioctl+0xc1/0x110
do_syscall_64+0xaa/0x690
entry_SYSCALL_64_after_hwframe+0x76/0x7e
</TASK>All affected tasks (identical stacks, full text in softlockups.txt):
| Time | CPU | Stuck | Task | PID |
|---|---|---|---|---|
| 10:37:48 | 5 | 26s | Hyprland:cs0 | 1535 |
| 10:37:48 | 12 | 26s | kworker/u97:4 | 390815 |
| 10:37:48 | 14 | 23s | chrome:cs0 | 3897 |
| 10:37:50 | 0 | 27s | telegram-d:cs0 | 2506 |
| 10:37:52 | 2 | 27s | firefox:cs0 | 5925 |
Collateral at 10:37:43, once the machine was already wedged:
thunderbolt 0000:c6:00.5: 0: timeout reading config space 1 from 0xd3
thunderbolt 0000:c6:00.6: 0: timeout reading config space 1 from 0xd3
thunderbolt 0000:c6:00.5: 0: timeout reading config space 2 from 0x5
thunderbolt 0000:c6:00.6: 0: timeout reading config space 2 from 0x5Last log line is 10:37:52; the box was power-cycled and came back at 10:38:50.
8. Timeline
| Time (CEST, 2026-09-20) | Event |
|---|---|
| 07:34:06 (Sep 19) | Boot 2feb479f8a6d4e748b8ba23661b5bb90, kernel 7.2.5-3-omarchy |
| … | 27 h uptime, no GPU resets, no ring timeouts, no VM faults |
| 10:34:41 | omarchy-shell session lock → unlock cycle |
| 10:37:17, 10:37:22 | Hyprland keymap recompiles (xkbcomp warnings) |
| 10:37:24.304 | voxtype: "Recording started (external trigger)" |
| 10:37:24 | Oops in `voxtype-osd-gtk` (PID 176928); exits with irqs disabled, preempt_count 1 → `lru_lock` leaked |
| 10:37:24.575 | voxtype OSD logs "showing (frame seq=10089)" |
| 10:37:43 | thunderbolt config-space read timeouts |
| 10:37:48–10:37:52 | soft lockups on CPUs 5, 12, 14, 0, 2 |
| 10:37:52 | last log line; machine dead |
| 10:38:50 | next boot after power cycle |
9. Expected vs actual
Expected: an unprivileged DRM_IOCTL_AMDGPU_CS from any client either succeeds or
returns an error to userspace.
Actual: kernel oops on a corrupt LRU list_head, one garbage pointer already written
into the live LRU before the fault, lru_lock leaked, all GPU clients soft-locked,
unrecoverable system hang.
10. Suggested directions
- Audit the lifetime of
ttm_lru_bulk_move_pos.first/lastagainstttm_resourcefrees —
specifically whether a resource can be destroyed while still recorded in a
ttm_lru_bulk_move reachable from amdgpu_vm_move_to_lru_tail(). The window here
followed compositor surface teardown/recreate (session unlock), which is exactly when
BOs churn.
- Consider a cheap sanity check (or list_bulk_move_tail under CONFIG_DEBUG_LIST
semantics) before the splice, so this fails loudly and locally instead of corrupting
the list.
- Independently: ensure an oops on this path cannot leak lru_lock, so one client's
fault does not take every GPU consumer down with it.
11. Things I can do on request
- Rebuild/boot with
CONFIG_DEBUG_LIST,CONFIG_PROVE_LOCKINGandCONFIG_KASANto get
a first-fault report rather than the downstream page fault. Current kernel has none of
these, which is why the corruption only surfaced at dereference time.
- Boot linux 7.1.9-arch1-2 (still installed) to sanity-check the regression window.
- Attach drm.debug output if a reproducer can be found (not currently reproducible).
12. Attachments (in ~/bug-reports/)
| File | Contents |
|---|---|
dmesg-boot-crash-full.txt | full journalctl -b -1 -k (3022 lines) |
journal-boot-crash-full.txt | full journalctl -b -1, incl. userspace (26080 lines) |
oops-primary.txt | the oops block verbatim, incl. complete Modules linked in (66 lines) |
softlockups.txt | all five soft-lockup traces verbatim (226 lines) |
dmesg-boot-crash-full.txt
journal-boot-crash-full.txt
oops-primary.txt
softlockups.txt
View raw text
### System details
AMD Strix iGPU, Radeon 880M/890M (1002:150e rev c1, GC 11.5.x class)
### What's wrong?
**Status:** system-fatal, hard reset required. Observed once on 7.2.5, never on 7.1.9.
**Subsystem:** `drivers/gpu/drm/ttm` + `drivers/gpu/drm/amd/amdgpu`
**Hardware:** AMD Strix iGPU, Radeon 880M/890M (`1002:150e` rev c1, GC 11.5.x class)
---
## 1. Summary
A `DRM_IOCTL_AMDGPU_CS` submission oopsed inside `ttm_lru_bulk_move_tail()` while
splicing a bulk-move range into the resource manager LRU. The faulting store is
`last->lru.next->prev = first->lru.prev`, where `last->lru.next` held the non-pointer
value `0x0000000c00000009` — a corrupted or never-initialised `list_head`.
Because the oops happens **inside the `bdev->lru_lock` critical section**, the lock is
never released (`exited with irqs disabled`, `preempt_count 1`). 24 seconds later every
other GPU client on the machine — Hyprland, Firefox, Chrome, Telegram, and a kworker —
was spinning in `ttm_resource_manager_usage()` → `_raw_spin_lock()` and tripped the
soft-lockup watchdog on 5 CPUs. The machine was unrecoverable.
Two distinct issues are worth separating:
1. **Root cause:** a `ttm_lru_bulk_move_pos` holds a resource whose `lru` list node is
garbage by the time `ttm_lru_bulk_move_tail()` runs (use-after-free / stale `pos`).
2. **Blast radius:** oopsing under `lru_lock` converts a single client's bad submit into
a full system hang. Worth hardening independently of (1).
## 2. Environment
| Item | Value |
|---|---|
| Kernel | `7.2.5-3-omarchy` — `Linux version 7.2.5-3-omarchy (linux-omarchy@omarchy) (gcc (GCC) 16.2.1 20260810, GNU ld (GNU Binutils) 2.47) #1 SMP PREEMPT_DYNAMIC Mon, 14 Sep 2026 19:55:01 +0000` |
| Kernel srcversion | build id `240f9ec5a8022b1a7f578b0199033bf067658fbc` |
| `ttm.ko` srcversion | `af53f3d7b80864bd8ae3177e53cd701b6900fe82` |
| `amdgpu.ko` srcversion | `f0d391bee47e81c4f78dac128061d1aaf220d33f` |
| Distro | Omarchy 4.0.4 (Arch-based), `omarchy-settings 4.0.4-1` |
| GPU | `c4:00.0 Display controller [0380]: AMD/ATI Strix [Radeon 880M / 890M] [1002:150e] (rev c1)` — integrated, no dGPU |
| Mesa | `1:26.2.2-1` (upgraded 2026-09-18 18:04) |
| GPU firmware | `linux-firmware-amdgpu 20260810-2` |
| Machine | AZW SER9 (Beelink SER9), board SER9, BIOS `SER9T103` dated 2024-10-28 |
| RAM | 27 GiB total (~8.8 GiB in use at crash time), `zswap.enabled=0`, zram active |
| Compositor | Hyprland (Wayland) under `uwsm`, plus `omarchy-shell` (Quickshell/QML) |
| Preemption | `CONFIG_PREEMPT=y`, PREEMPT_DYNAMIC (`PREEMPT(full)` at runtime) |
| Debug config | `CONFIG_SLUB_DEBUG=y`, `CONFIG_SLAB_FREELIST_HARDENED=y`, `CONFIG_FORTIFY_SOURCE=y`. **No** `DEBUG_LIST`, **no** `LOCKDEP`/`PROVE_LOCKING`, **no** `KASAN`. |
`/proc/cmdline`:
```
quiet splash cryptdevice=PARTUUID=f429f303-5002-48c0-80cb-b6ee833c30d1:root root=/dev/mapper/root zswap.enabled=0 rootflags=subvol=@ rw rootfstype=btrfs initramfs_async=0 loglevel=0 systemd.show_status=false rd.udev.log_level=0 vt.global_cursor_default=0
```
No `amdgpu.*` module parameters set on the command line; all `/sys/module/amdgpu/parameters/*`
are at defaults (`gpu_recovery=-1`, `sched_policy=0`, `sched_jobs=32`, `sched_hw_submission=2`,
`dcfeaturemask=1026`, `ppfeaturemask=0xfff7bfff`, `dc=-1`, `tmz=-1`, `noretry=-1`).
## 3. Regression window
| Boot | Date | Kernel | Outcome |
|---|---|---|---|
| -5 | 2026-09-18 | `7.1.9-arch1-2` | clean |
| -4 | 2026-09-18 18:02 | `7.1.9-arch1-2` | clean |
| -3 | 2026-09-18 18:13 → 23:34 | `7.2.5-3-omarchy` | clean |
| -2 | 2026-09-18 23:35 → 00:38 | `7.2.5-3-omarchy` | clean |
| **-1** | **2026-09-19 07:34 → 2026-09-20 10:37** | **`7.2.5-3-omarchy`** | **crash** |
`linux-omarchy 7.2.5-3` was installed 2026-09-18 18:04 (pacman log), replacing
`linux 7.1.9-arch1-2`. The crash is on the third boot of the new kernel, after a
27-hour uptime. `grep -c ttm_lru_bulk_move_tail` over boots -5..-2 returns 0 in all
cases — the trace has never appeared under 7.1.9 either. This is suggestive of a 7.2.x
regression but is a single data point; it is not bisected.
Mesa `1:26.2.2-1` also landed in the same 2026-09-18 update, so a userspace-side
trigger change cannot be excluded — though nothing Mesa does should produce this.
## 4. Reproduction
Not reproducible on demand. Single occurrence.
Conditions at the time of the crash:
- Push-to-talk dictation via **voxtype 1.0.1** (`voxtype-bin 1.0.1-1`). Its GTK4 on-screen
indicator process `voxtype-osd-gtk` maps a small always-on-top Wayland surface each
time recording starts.
- The oops fires in that OSD process **at the moment the window is mapped**, on what is
effectively the first command submission for the new surface:
```
10:37:24.304 voxtype[176898]: INFO Recording started (external trigger)
10:37:24.306 voxtype[176898]: INFO Using audio device: default
10:37:24.??? kernel: BUG: unable to handle page fault ... Comm: voxtype-osd-gtk PID 176928
10:37:24.575 voxtype[176928]: INFO voxtype_osd_gtk4: showing (frame seq=10089, last_at=0.004s ago)
```
- The same show/hide cycle had completed successfully **9 times earlier in the same
boot** (this was `showing` #10) — so mapping the OSD is not sufficient by itself. The
bug is a race.
- ~3 minutes earlier (10:34:41) the session went through an **omarchy-shell lock/unlock
cycle** (`omarchy lock … session-locked=false … unlocked`), which tears down and
recreates compositor surfaces. Possibly relevant to how a stale bulk-move `pos`
survived.
- Hyprland reloaded its keymap at 10:37:17 and 10:37:22 (xkbcomp warnings), i.e. there
was compositor churn in the seconds before the fault.
- Concurrent GPU clients: Hyprland, Firefox, Chrome, Telegram Desktop, `omarchy-shell`.
- **No** prior GPU trouble in this boot: zero `GPU reset`, zero ring timeouts, zero
`VM_L2` / VM page faults in the whole 27-hour boot (`grep -icE "GPU reset|ring .*
timeout|VM_L2|page fault \(src_id"` → 0). The oops comes out of a clean-running GPU.
## 5. Primary oops — full trace
```
BUG: unable to handle page fault for address: 0000000c00000011
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: Oops: 0002 [#1] SMP NOPTI
CPU: 1 UID: 1000 PID: 176928 Comm: voxtype-osd-gtk Not tainted 7.2.5-3-omarchy #1 PREEMPT(full) 240f9ec5a8022b1a7f578b0199033bf067658fbc
Hardware name: AZW SER9/SER9, BIOS SER9T103 10/28/2024
RIP: 0010:ttm_lru_bulk_move_tail+0x144/0x1e0 [ttm]
Code: d2 74 51 48 8b 42 30 49 8b 48 08 48 8b 6a 48 48 8b 80 70 01 00 00 48 83 c1 40 4a 8b 74 d0 08 48 8b 01 48 89 45 00 48 8b 6a 48 <48> 89 68 08 89 f8 48 8d 6a 40 48 c1 e0 04 48 01 f0 4c 01 ce 4c 8b
RSP: 0018:ffffd16528b27910 EFLAGS: 00010282
RAX: 0000000c00000009 RBX: 0000000000000000 RCX: ffff8a708660c820
RDX: ffff8a72c47dc6c0 RSI: ffff8a709c40edb8 RDI: 0000000000000001
RBP: ffff8a7088cc53a0 R08: ffff8a745021d9a8 R09: 0000000000000078
R10: 000000000000001a R11: ffff8a745021d998 R12: ffff8a745021d000
R13: 0000000000000003 R14: 0000000000000006 R15: 00000000000008e4
FS: 00007d6ed184a040(0000) GS:ffff8a7733d71000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000c00000011 CR3: 0000000595b5a000 CR4: 0000000000f50ef0
PKRU: 55555554
Call Trace:
<TASK>
amdgpu_vm_move_to_lru_tail+0x29/0x40 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
amdgpu_cs_ioctl+0x209f/0x22f0 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
? __pfx_amdgpu_cs_ioctl+0x10/0x10 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
drm_ioctl_kernel+0xb6/0x110
drm_ioctl+0x2e9/0x550
? __pfx_amdgpu_cs_ioctl+0x10/0x10 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
amdgpu_drm_ioctl+0x4a/0x80 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
__x64_sys_ioctl+0xc1/0x110
do_syscall_64+0xaa/0x690
? amdgpu_drm_ioctl+0x6a/0x80 [amdgpu f0d391bee47e81c4f78dac128061d1aaf220d33f]
? __x64_sys_ioctl+0xdc/0x110
? do_syscall_64+0xaa/0x690
? __x64_sys_ioctl+0xdc/0x110
? do_syscall_64+0xaa/0x690
? do_syscall_64+0x5f/0x690
? exc_page_fault+0x90/0x1f0
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7d6ed471be1d
Code: 04 25 28 00 00 00 48 89 45 c8 31 c0 48 8d 45 10 c7 45 b0 10 00 00 00 48 89 45 b8 48 8d 45 d0 48 89 45 c0 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1a 48 8b 45 c8 64 48 2b 04 25 28 00 00 00
RSP: 002b:00007ffe62890140 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ffe62890358 RCX: 00007d6ed471be1d
RDX: 00007ffe628901e0 RSI: 00000000c0186444 RDI: 0000000000000010
RBP: 00007ffe62890190 R08: 00005a046bbd5ae0 R09: 00007ffe628901a0
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000010
R13: 00005a046b9865d0 R14: 00005a046bbd5a80 R15: 00005a046bb20cd0
</TASK>
CR2: 0000000c00000011
---[ end trace 0000000000000000 ]---
note: voxtype-osd-gtk[176928] exited with irqs disabled
note: voxtype-osd-gtk[176928] exited with preempt_count 1
```
Userspace frame: `ORIG_RAX = 0x10` (`ioctl`), `RSI = 0xc0186444` = `DRM_IOCTL_AMDGPU_CS`,
`RDI = 0x10` (the render-node fd). `RAX = -ENOSYS` is just the pre-syscall sentinel.
`Modules linked in` (full list) is in the attached `oops-primary.txt`; the DRM-relevant
tail is:
```
... hid_sensor_hub amdgpu drm_buddy amdxcp i2c_algo_bit drm_ttm_helper ttm drm_exec
drm_panel_backlight_quirks nvme gpu_sched aesni_intel drm_suballoc_helper gf128mul
nvme_core serio_raw aead video drm_display_helper nvme_keyring ccp nvme_auth cec wmi thunderbolt
```
## 6. Faulting instruction — disassembly and decode
Symbolised against the shipped `/lib/modules/7.2.5-3-omarchy/kernel/drivers/gpu/drm/ttm/ttm.ko.zst`.
`ttm_lru_bulk_move_tail` is at `0x70f0`, so `+0x144` = `0x7234`:
```
7205: 49 8b 10 mov (%r8),%rdx ; rdx = pos->first
7208: 48 85 d2 test %rdx,%rdx
720b: 74 51 je 725e ; if (!pos->first) continue;
720d: 48 8b 42 30 mov 0x30(%rdx),%rax ; rax = first->bo
7211: 49 8b 48 08 mov 0x8(%r8),%rcx ; rcx = pos->last
7215: 48 8b 6a 48 mov 0x48(%rdx),%rbp ; rbp = first->lru.prev
7219: 48 8b 80 70 01 00 00 mov 0x170(%rax),%rax ; rax = bo->bdev
7220: 48 83 c1 40 add $0x40,%rcx ; rcx = &last->lru
7224: 4a 8b 74 d0 08 mov 0x8(%rax,%r10,8),%rsi ; rsi = &man->lru[j] (ttm_manager_type)
7229: 48 8b 01 mov (%rcx),%rax ; rax = last->lru.next <-- POISON READ
722c: 48 89 45 00 mov %rax,0x0(%rbp) ; first->lru.prev->next = last->lru.next
7230: 48 8b 6a 48 mov 0x48(%rdx),%rbp ; rbp = first->lru.prev
>>> 7234: 48 89 68 08 mov %rbp,0x8(%rax) ; last->lru.next->prev = ... <-- FAULTS
7238: 89 f8 mov %edi,%eax
723a: 48 8d 6a 40 lea 0x40(%rdx),%rbp
...
724c: 49 89 2e mov %rbp,(%r14)
7253: 4c 89 72 48 mov %r14,0x48(%rdx)
7257: 48 89 31 mov %rsi,(%rcx)
725a: 48 89 48 70 mov %rcx,0x70(%rax)
```
The bytes at `0x7229..0x7237` (`48 8b 01 | 48 89 45 00 | 48 8b 6a 48 | 48 89 68 08`)
match the oops `Code:` line exactly, with `<48 89 68 08>` marked as the faulting
instruction. Confirmed decode:
- This is the inlined `list_bulk_move_tail(&man->lru[j], &pos->first->lru, &pos->last->lru)`
in the second loop nest of `ttm_lru_bulk_move_tail()` (outer loop `cmp $0x9,%ebx`
= `TTM_NUM_MEM_TYPES`, inner loop `cmp $0x4,%edi` = `TTM_MAX_BO_PRIORITY`).
- `RAX = 0x0000000c00000009` is `pos->last->lru.next`. `CR2 = RAX + 8` = the `prev`
field it tried to write.
- `RDX = ffff8a72c47dc6c0` (`pos->first`) and `RCX = ffff8a708660c820` (`&pos->last->lru`,
i.e. `pos->last` = `ffff8a708660c7e0`) are both plausible slab addresses — the
*structures* are mapped; only the embedded `list_head` contents are junk.
- `R13 = 3` and `R14 = 6` are the loop indices at fault time; `R10 = 0x1a` is the
`ttm_manager_type` index expression. `RBX = 0` is the outer counter, so this is
`bulk->pos[0][...]`.
**Interpretation of the poison value.** `0x0000000c00000009` is not a kernel pointer,
not `LIST_POISON1/2`, and not a SLUB free poison — it reads as two packed 32-bit words,
`12` and `9`. That is the signature of reading a `list_head` out of memory that has been
reallocated and is now holding a different struct's small integer fields (e.g. a
`u32`/`u32` pair). Combined with `SLAB_FREELIST_HARDENED` being on and no
`DEBUG_LIST`/`KASAN` to catch it earlier, the most likely mechanism is a
**use-after-free / stale `ttm_lru_bulk_move_pos`**: the `ttm_resource` recorded in
`bulk->pos[i][j].last` was freed and its slab object reused, while the bulk-move
structure still referenced it.
**Note on partial damage:** the store at `0x722c` (`first->lru.prev->next = <poison>`)
**executed successfully before the fault**. So even at the moment of the oops the real
LRU list has already had a garbage `next` pointer written into it. Any recovery attempt
after this point would walk a corrupt list.
## 7. Secondary failure — soft lockups from the leaked `lru_lock`
Five CPUs wedged, all with the same stack, all waiting on the spinlock the oopsed task
never released:
```
watchdog: BUG: soft lockup - CPU#5 stuck for 26s! [Hyprland:cs0:1535]
CPU#5 Utilization every 4000ms during lockup:
#1: 100% system, 0% softirq, 0% hardirq, 0% idle
... (same for #2-#5)
CPU: 5 UID: 1000 PID: 1535 Comm: Hyprland:cs0 Tainted: G D 7.2.5-3-omarchy #1 PREEMPT(full)
Tainted: [D]=DIE
RIP: 0010:native_queued_spin_lock_slowpath+0xa2/0x330
Call Trace:
<TASK>
_raw_spin_lock+0x3f/0x60
ttm_resource_manager_usage+0x2e/0x80 [ttm]
amdgpu_cs_ioctl+0x1343/0x22f0 [amdgpu]
drm_ioctl_kernel+0xb6/0x110
drm_ioctl+0x2e9/0x550
amdgpu_drm_ioctl+0x4a/0x80 [amdgpu]
__x64_sys_ioctl+0xc1/0x110
do_syscall_64+0xaa/0x690
entry_SYSCALL_64_after_hwframe+0x76/0x7e
</TASK>
```
All affected tasks (identical stacks, full text in `softlockups.txt`):
| Time | CPU | Stuck | Task | PID |
|---|---|---|---|---|
| 10:37:48 | 5 | 26s | `Hyprland:cs0` | 1535 |
| 10:37:48 | 12 | 26s | `kworker/u97:4` | 390815 |
| 10:37:48 | 14 | 23s | `chrome:cs0` | 3897 |
| 10:37:50 | 0 | 27s | `telegram-d:cs0` | 2506 |
| 10:37:52 | 2 | 27s | `firefox:cs0` | 5925 |
Collateral at 10:37:43, once the machine was already wedged:
```
thunderbolt 0000:c6:00.5: 0: timeout reading config space 1 from 0xd3
thunderbolt 0000:c6:00.6: 0: timeout reading config space 1 from 0xd3
thunderbolt 0000:c6:00.5: 0: timeout reading config space 2 from 0x5
thunderbolt 0000:c6:00.6: 0: timeout reading config space 2 from 0x5
```
Last log line is 10:37:52; the box was power-cycled and came back at 10:38:50.
## 8. Timeline
| Time (CEST, 2026-09-20) | Event |
|---|---|
| 07:34:06 (Sep 19) | Boot `2feb479f8a6d4e748b8ba23661b5bb90`, kernel 7.2.5-3-omarchy |
| … | 27 h uptime, no GPU resets, no ring timeouts, no VM faults |
| 10:34:41 | omarchy-shell session lock → unlock cycle |
| 10:37:17, 10:37:22 | Hyprland keymap recompiles (xkbcomp warnings) |
| 10:37:24.304 | voxtype: "Recording started (external trigger)" |
| **10:37:24** | **Oops in `voxtype-osd-gtk` (PID 176928); exits with irqs disabled, preempt_count 1 → `lru_lock` leaked** |
| 10:37:24.575 | voxtype OSD logs "showing (frame seq=10089)" |
| 10:37:43 | thunderbolt config-space read timeouts |
| 10:37:48–10:37:52 | soft lockups on CPUs 5, 12, 14, 0, 2 |
| 10:37:52 | last log line; machine dead |
| 10:38:50 | next boot after power cycle |
## 9. Expected vs actual
**Expected:** an unprivileged `DRM_IOCTL_AMDGPU_CS` from any client either succeeds or
returns an error to userspace.
**Actual:** kernel oops on a corrupt LRU `list_head`, one garbage pointer already written
into the live LRU before the fault, `lru_lock` leaked, all GPU clients soft-locked,
unrecoverable system hang.
## 10. Suggested directions
- Audit the lifetime of `ttm_lru_bulk_move_pos.first/last` against `ttm_resource` frees —
specifically whether a resource can be destroyed while still recorded in a
`ttm_lru_bulk_move` reachable from `amdgpu_vm_move_to_lru_tail()`. The window here
followed compositor surface teardown/recreate (session unlock), which is exactly when
BOs churn.
- Consider a cheap sanity check (or `list_bulk_move_tail` under `CONFIG_DEBUG_LIST`
semantics) before the splice, so this fails loudly and locally instead of corrupting
the list.
- Independently: ensure an oops on this path cannot leak `lru_lock`, so one client's
fault does not take every GPU consumer down with it.
## 11. Things I can do on request
- Rebuild/boot with `CONFIG_DEBUG_LIST`, `CONFIG_PROVE_LOCKING` and `CONFIG_KASAN` to get
a first-fault report rather than the downstream page fault. Current kernel has none of
these, which is why the corruption only surfaced at dereference time.
- Boot `linux 7.1.9-arch1-2` (still installed) to sanity-check the regression window.
- Attach `drm.debug` output if a reproducer can be found (not currently reproducible).
## 12. Attachments (in `~/bug-reports/`)
| File | Contents |
|---|---|
| `dmesg-boot-crash-full.txt` | full `journalctl -b -1 -k` (3022 lines) |
| `journal-boot-crash-full.txt` | full `journalctl -b -1`, incl. userspace (26080 lines) |
| `oops-primary.txt` | the oops block verbatim, incl. complete `Modules linked in` (66 lines) |
| `softlockups.txt` | all five soft-lockup traces verbatim (226 lines) |
[dmesg-boot-crash-full.txt](https://github.com/user-attachments/files/32431707/dmesg-boot-crash-full.txt)
[journal-boot-crash-full.txt](https://github.com/user-attachments/files/32431706/journal-boot-crash-full.txt)
[oops-primary.txt](https://github.com/user-attachments/files/32431705/oops-primary.txt)
[softlockups.txt](https://github.com/user-attachments/files/32431708/softlockups.txt)
- dmesg-boot-crash-full.txt
- journal-boot-crash-full.txt
- oops-primary.txt
- softlockups.txt
Validation
Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.
Linked
Not linked to any known issue, other report or change yet.
Decision
Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.
- Out of scopeDecided by agents · verified · 2026-10-09 16:30 UTC