-
Notifications
You must be signed in to change notification settings - Fork 22
fix(auth): prevent Qt atexit deadlock in PAM fork children #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,11 +11,12 @@ | |
| #include "SignalHandler.h" | ||
| #include "VirtualTerminal.h" | ||
|
|
||
| #include <pwd.h> | ||
| #include <security/pam_appl.h> | ||
| #include <pthread.h> | ||
| #include <signal.h> | ||
| #include <unistd.h> | ||
| #include <utmp.h> | ||
| #include <utmpx.h> | ||
|
|
||
| namespace DDM { | ||
|
|
@@ -266,6 +267,19 @@ | |
| 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, []() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Consider handling the pthread_atfork return code and avoiding repeated registration.
Suggested implementation: // 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.
//
// Use std::call_once to avoid registering multiple handlers in the
// same process, and check the pthread_atfork return value.
static std::once_flag pamAtforkOnce;
std::call_once(pamAtforkOnce, [] {
const int rc = pthread_atfork(
nullptr,
nullptr,
[]() {
atexit([]() { _exit(0); });
}
);
if (rc != 0) {
// Best-effort logging; failure means the workaround is not installed.
qWarning() << "Failed to register pthread_atfork handler for PAM:"
<< strerror(rc);
}
});
// Open session
auto sessionEnv = openSessionInternal(env);
To compile successfully, you will also need to:
|
||
| atexit([]() { _exit(0); }); | ||
| }); | ||
|
Comment on lines
+279
to
+281
|
||
|
|
||
| // Open session | ||
| auto sessionEnv = openSessionInternal(env); | ||
| if (!sessionEnv.has_value()) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question (bug_risk): Scope and lifetime of the atfork-based workaround may be broader than intended.
Since
pthread_atforkis process-wide, this handler will run on every futurefork(), not just those triggered indirectly by PAM, and will change termination behavior for all child processes by installing the_exit(0)atexit hook. If that broader effect isn’t intended, consider scoping the workaround more narrowly (e.g., via a helper process or only around the specific PAM operations known to fork) so other fork-using paths aren’t unintentionally altered.