Skip to content

Comments

core: fix use-after-free segfault in async resource widget callbacks#961

Merged
PointerDilemma merged 1 commit intohyprwm:mainfrom
kiivihal:fix/segfault-async-widget-uaf
Feb 20, 2026
Merged

core: fix use-after-free segfault in async resource widget callbacks#961
PointerDilemma merged 1 commit intohyprwm:mainfrom
kiivihal:fix/segfault-async-widget-uaf

Conversation

@kiivihal
Copy link
Contributor

Summary

Fixes a segfault (SIGSEGV at address 0xc) caused by use-after-free when AWP<IWidget> weak pointers are dereferenced without proper locking in AsyncResourceManager. This crash occurs frequently during lock/unlock cycles, especially on suspend/resume where outputs are removed while async resources are still in-flight.

Relates to #942.

Root Cause

onResourceFinished() and request() check if (widget) before calling widget->onAssetUpdate(), but AWP::operator bool() only checks if the raw pointer is non-null — it does not verify the underlying object is still alive. When widgets are destroyed during shutdown or output removal, the weak pointer still appears valid but the object is freed, causing a vtable dispatch on a destroyed object (crash at offset 0xc).

Additionally, the exit path destroyed global objects (g_asyncResourceManager, g_pRenderer) while timer and poll threads were still running, allowing callbacks to reference freed state.

Changes

  • Properly lock weak pointers before calling virtual methods: widget.lock() instead of if (widget) in onResourceFinished() and request(). The lock() method correctly checks dataNonNull(), destroying(), and lockable().
  • Skip needless renders for expired widget requesters — only queue renderAllOutputs() when widget.lock() succeeds.
  • Guard timer callback against null g_asyncResourceManager during shutdown.
  • Guard finished listener against null g_pHyprlock during destruction of the async resource manager.
  • Fix destruction order: join poll and timer threads before resetting globals to prevent use-after-free in pending callbacks.

Crash Backtrace (before fix)

Thread 1 (crashing):
  #0  0x0000559097685fdd  (/usr/bin/hyprlock + 0x78fdd)   // onAssetUpdate vtable dispatch on freed widget
  #1  libstdc++.so.6
  #2  libc.so.6

Thread 2 (main):
  #0  libc.so.6
  #4  std::thread::join()   // main thread waiting in exit()

segfault at c = null pointer + small offset, consistent with vtable dispatch on a destroyed object.

Testing

  • Before fix: 10 crashes in 5 days (every lock/unlock or suspend/resume cycle)
  • After fix: 0 crashes over 2 days of continuous use with frequent lock/unlock and suspend/resume cycles

Properly lock AWP<IWidget> weak pointers before calling onAssetUpdate()
to prevent use-after-free when widgets are destroyed during shutdown or
output removal. Guard timer callback against null g_asyncResourceManager.
Fix destruction order to join threads before resetting globals.
Copy link
Collaborator

@PointerDilemma PointerDilemma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks!

@PointerDilemma PointerDilemma merged commit b3a1076 into hyprwm:main Feb 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants