Skip to content

output: skip lock surface creation when the session lock is gone - #1080

Open
Karen-Abgaryan wants to merge 1 commit into
hyprwm:mainfrom
Karen-Abgaryan:fix-lock-surface-after-unlock
Open

Karen-Abgaryan wants to merge 1 commit into
hyprwm:mainfrom
Karen-Abgaryan:fix-lock-surface-after-unlock

Conversation

@Karen-Abgaryan

Copy link
Copy Markdown

Problem

hyprlock segfaults if a new output's wl_output.done is dispatched after the session lock object has been released. Hyprland then shows the "lockscreen app died" screen and the session stays locked.

I hit this after resuming a laptop with an external monitor. Right after wake the monitor disconnected and reconnected several times while hyprlock was unlocking.

releaseSessionLock() and onLockFinished() set m_sLockState.lock to null but never reset m_lockAquired, so isLockAquired() keeps returning true. If a done event for a newly bound output arrives after that, the setDone handler calls createSessionLockSurface(), and the CSessionLockSurface constructor calls g_pHyprlock->getSessionLock()->sendGetLockSurface(...) on the null lock. In my case the event was dispatched by the wl_display_roundtrip() at the end of releaseSessionLock().

Trace from the core dump:

#0  Hyprutils::Memory::sc<CCWlSurface*, void* const&> at protocols/ext-session-lock-v1.cpp:124
#3  CSessionLockSurface::CSessionLockSurface at src/core/LockSurface.cpp:50
#5  COutput::createSessionLockSurface at src/core/Output.cpp:59
#7  _CWlOutputDone at protocols/wayland.cpp:2151
#18 wl_display_roundtrip_queue
#19 CHyprlock::releaseSessionLock at src/core/hyprlock.cpp:819
#21 CRenderer::startFadeOut(bool)::<lambda> at src/renderer/Renderer.cpp:613
#25 Hyprutils::Animation::CBaseAnimatedVariable::onAnimationEnd()
#29 CHyprlockAnimationManager::tick at src/core/AnimationManager.cpp:106
#30 CSessionLockSurface::render at src/core/LockSurface.cpp:141

Fix

createSessionLockSurface() returns early when getSessionLock() is null. The check sits next to the existing guards, at the point that dereferences the lock. So it covers every caller (the done handler and acquireSessionLock()) and both paths that drop the lock object.

An alternative would be resetting m_lockAquired in releaseSessionLock() / onLockFinished(). I went with the guard at where the null dereference happens, so it protects any caller, including future ones. Happy to switch if you prefer that.

Related: #1025 touched the same function for outputs appearing during resume.

Testing

I ran everything in a nested Hyprland. Real hotplug rarely hits the window after releaseSessionLock() drops the lock, so forced it with gdb.

  1. Build Debug binaries of main and of this branch. With -O3 the breakpoint line gets inlined away.
  2. Start a nested Hyprland and lock it with hyprlock under gdb. Put a breakpoint at Output.cpp:40 (the setGeometry registration in COutput::create) that calls releaseSessionLock() and then continues.
  3. Add an output with hyprctl output create headless FORCED.
  4. Expected:
    • main: SIGSEGV after output N creating a new lock surface.
    • this branch: logs output N has no session lock to create a lock surface for and exits normally.

AI usage disclosure

I used Claude throughout. It symbolized the core dump with gdb/debuginfod, traced the crash path and found the stale m_lockAquired flag. It proposed and wrote the 7-line fix and the commit message. It also built and ran every test listed above, in the nested Hyprland.

Lock surface creation was skipped once the session lock object was released, preventing a null dereference when wl_output.done arrived during unlock.
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.

1 participant