From 25d277f84837537446e6b42363a53ef20b32795e Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Sat, 28 Mar 2026 21:14:59 +0800 Subject: [PATCH] fix(auth): prevent Qt atexit deadlock in PAM fork children PAM modules (notably pam_gnome_keyring) may fork() internally and call exit() instead of _exit() in the fork child, e.g. when starting a daemon or communicating with an existing one. Since the DDM session leader inherits Qt's atexit handlers, exit() triggers libQt6DBus cleanup which tries to join its dispatcher thread. After fork(), only the calling thread survives, so the join blocks forever on a futex. Fix this by registering a pthread_atfork child handler in the session leader, right before pam_open_session(). The handler pushes an atexit entry that calls _exit(0); since atexit handlers run in LIFO order, this runs first and terminates the grandchild without executing Qt's broken post-fork cleanup. This fixes the immediate stuck after clicking login on Arch Linux. --- src/daemon/Auth.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/daemon/Auth.cpp b/src/daemon/Auth.cpp index 9aa6654..5d614a7 100644 --- a/src/daemon/Auth.cpp +++ b/src/daemon/Auth.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -266,6 +267,19 @@ namespace DDM { env.insert(QStringLiteral("LOGNAME"), QString::fromLocal8Bit(pw->pw_name)); } + // PAM modules (e.g. pam_gnome_keyring) may fork() internally and + // call exit() instead of _exit() in the fork child, which triggers + // Qt's atexit cleanup (including libQt6DBus joining its dispatcher + // thread). After fork(), that thread doesn't exist, so the join + // blocks forever on a futex/semaphore. + // + // Register a pthread_atfork child handler here so that any + // grandchild processes spawned by PAM will have an atexit handler + // that runs _exit() first (LIFO), bypassing Qt's broken cleanup. + pthread_atfork(nullptr, nullptr, []() { + atexit([]() { _exit(0); }); + }); + // Open session auto sessionEnv = openSessionInternal(env); if (!sessionEnv.has_value()) {