https://bugs.kde.org/show_bug.cgi?id=481808
--- Comment #36 from nyanpasu64 <[email protected]> --- Created attachment 195053 --> https://bugs.kde.org/attachment.cgi?id=195053&action=edit journalctl log of a sleep-wake failed login The source code lives at https://invent.kde.org/plasma/kscreenlocker/-/tree/v6.7.4?ref_type=tags. We can enable logging via: nano ~/.config/QtProject/qtlogging.ini [Rules] kscreenlocker_greet=true How does kscreenlocker set up authentication? // auth.cpp class PamAuthenticator : public QObject { QThread m_thread; PamWorker *d; PamAuthenticator::PamAuthenticator(const QString &service, const QString &user, NoninteractiveAuthenticatorTypes types, QObject *parent) { d->moveToThread(&m_thread); ↑ This shows that PamWorker methods are invoked in a background thread, and run concurrently with UI operations. (worker) PamWorker::PamWorker() : m_conv({&PamWorker::converse, this}) (main)void PamAuthenticator::init(const QString &service, const QString &user) { QMetaObject::invokeMethod(d, [this, service, user]() { d->start(service, user); }); (worker) void PamWorker::start(const QString &service, const QString &user) { m_service = service; if (user.isEmpty()) m_result = pam_start(qPrintable(service), nullptr, &m_conv, &m_handle); else m_result = pam_start(qPrintable(service), qPrintable(user), &m_conv, &m_handle); Aug 10 14:56:55 servlet kscreenlocker_greet[3848637]: [PAM worker kde-fingerprint] start: successfully started Aug 10 14:56:55 servlet kscreenlocker_greet[3848637]: [PAM worker kde-smartcard] start: successfully started Aug 10 14:56:55 servlet kscreenlocker_greet[3848637]: [PAM worker kde] start: successfully started ↑ Each potential unlock thread calls pam_start(), passing in m_conv -> PamWorker::converse(). (main) void PamAuthenticator::tryUnlock() { m_unlocked = false; QMetaObject::invokeMethod(d, &PamWorker::authenticate); (worker) void PamWorker::authenticate() { qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Authenticate: Starting authentication", qUtf8Printable(m_service)); Aug 10 14:56:56 servlet kscreenlocker_greet[3848637]: [PAM worker kde-fingerprint] Authenticate: Starting authentication Aug 10 14:56:56 servlet kscreenlocker_greet[3848637]: [PAM worker kde] Authenticate: Starting authentication Aug 10 14:56:56 servlet kscreenlocker_greet[3848637]: [PAM worker kde-smartcard] Authenticate: Starting authentication ↓ pam_authenticate() is a blocking call, that calls back into m_conv -> PamWorker::converse(). Each call-back receives a fixed number of commands, not an updatable stream. int rc = pam_authenticate(m_handle, 0); // PAM_SILENT); qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Authenticate: Authentication done, result code: %d (%s)", qUtf8Printable(m_service), rc, pam_strerror(m_handle, rc)); Aug 10 14:56:56 servlet kscreenlocker_greet[3848637]: [PAM worker kde-smartcard] Authenticate: Authentication done, result code: 28 (Module is unknown) Aug 10 14:56:56 servlet kscreenlocker_greet[3848637]: [PAM worker kde-fingerprint] Authenticate: Authentication done, result code: 28 (Module is unknown) ↑ At this point only the "kde" authenticator (password prompt) is active. pam_authenticate() calls into PamWorker::converse(): ↓ (worker) int PamWorker::converse(int n, const struct pam_message **msg, struct pam_response **resp, void *data) { PamWorker *c = static_cast<PamWorker *>(data); *resp = (calloc pam_response[n]) auto responses = std::span<pam_response>{*resp, nSize}; auto messages = std::span<const pam_message*>{msg, nSize}; for (const auto &[pamMessage, pamResponse] : std::views::zip(messages, responses)) { bool isSecret = false; switch (pamMessage->msg_style) { There are two types of PAM prompts for user input. KDE's handling for them is... awkward. qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Message: %s: %s", qUtf8Printable(c->m_service), (isSecret ? "Echo-off prompt" : "Echo-on prompt"), qUtf8Printable(prompt)); Aug 10 14:56:56 servlet kscreenlocker_greet[3848637]: [PAM worker kde] Message: Echo-off prompt: Password: QEventLoop e; QObject::connect(c, &PamWorker::cancelled, &e, ...); qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Starting nested event loop to await response", qUtf8Printable(c->m_service)); Aug 10 14:56:56 servlet kscreenlocker_greet[3848637]: [PAM worker kde] Starting nested event loop to await response // We are in a non-gui thread. It should be mostly fine to exec() here. int rc = e.exec(); ↑ This is the point we reach with the lock screen open. There is a wedged call to pam_authenticate() -> PamWorker::converse(), that persists until we enter a password or sleep the system. In the latter case... ↓ Aug 10 14:56:58 servlet systemd-logind[734]: suspend requested from client PID 3848637 ('kscreenlocker_g') (unit [email protected])... Aug 10 14:56:58 servlet systemd-logind[734]: The system will suspend now! (main) void PamAuthenticators::cancel() { qCDebug(KSCREENLOCKER_GREET) << "PamAuthenticators: cancelling interactive authenticator"; Aug 10 14:56:58 servlet kscreenlocker_greet[3848637]: PamAuthenticators: cancelling interactive authenticator d->interactive->cancel(); [void PamAuthenticator::cancel()] { ... QMetaObject::invokeMethod(d, &PamWorker::cancelled); } e.exec() calls into [QObject::connect(&PamWorker::cancelled)] → [&]() { qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Received cancellation, exiting with PAM_CONV_ERR", qUtf8Printable(c->m_service)); Aug 10 14:56:58 servlet kscreenlocker_greet[3848637]: [PAM worker kde] Received cancellation, exiting with PAM_CONV_ERR e.exit(PAM_CONV_ERR); } return from [int rc = e.exec()] = PAM_CONV_ERR if (rc != 0) { qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Nested event loop's exit code was not zero, bailing", qUtf8Printable(c->m_service)); Aug 10 14:56:58 servlet kscreenlocker_greet[3848637]: [PAM worker kde] Nested event loop's exit code was not zero, bailing return rc; } ↑ Our pam_conv callback returns PAM_CONV_ERR. It seems that pam_authenticate() → pam_unix.so did not appreciate us returning PAM_CONV_ERR, and returns PAM_AUTHTOK_ERR=20 to the caller. Aug 10 14:56:58 servlet kscreenlocker_greet[3848637]: [PAM worker kde] Authenticate: Authentication done, result code: 20 (Authentication token manipulation error) Aug 10 14:56:58 servlet kscreenlocker_greet[3848637]: pam_unix(kde:auth): unexpected response from failed conversation function Aug 10 14:56:58 servlet kscreenlocker_greet[3848637]: PamAuthenticators: Failure from interactive authenticator kde Aug 10 14:56:58 servlet kscreenlocker_greet[3848637]: pam_unix(kde:auth): conversation failed Worse yet, when pam_unix.so receives an erroneous reply from our callback, it doesn't just abort authentication, but libpam invokes pam_faillock.so in authfail mode, registering our suspend attempt as a failed login. I've attached my full journal log of authentication errors. ---- Which authentication flow did we trigger in libpam? void PamWorker::start(const QString &service, const QString &user) { m_result = pam_start(qPrintable(service), qPrintable(user), &m_conv, &m_handle); [int pam_start(const char *service_name, ...)]; Based on the log messages, we can infer service_name is "kde". /usr/lib/pam.d/kde redirects to system-local-login, /etc/pam.d/system-local-login redirects to system-login, and /etc/pam.d/system-login specifies four modes each with a sequential list of authenticator shared libraries. README.pam claims (consistent with my wild poking-around in linux-pam/libpam): > PAM configuration files have four types of entries for each service, > however KScreenLocker only uses the "auth" entries. Other programs > using PAM may use other entries. The auth entries are: auth required pam_shells.so auth requisite pam_nologin.so auth include system-auth /etc/pam.d/system-auth contains: auth required pam_faillock.so preauth -auth [success=2 default=ignore] pam_systemd_home.so auth [success=1 default=bad] pam_unix.so try_first_pass nullok auth [default=die] pam_faillock.so authfail ... auth required pam_faillock.so authsucc So at this point we'd have to either configure pam_faillock (https://linux.die.net/man/8/pam_faillock, in the linux-pam repo) to ignore PAM_CONV_ERR as a failed unlock, or not *call* pam_authenticate() until the user has already entered a password. The first may be difficult, and we'd need to hide the stray "Unlocking failed" message another way (don't print an error upon PAM_AUTHTOK_ERR? if we've called PamWorker::cancelled()?). The second may break non-password unlock mechanisms the user installs into the kde → system-local-login → system-login → system-auth chain, since we only check for PAM authentication when the user enters a password and presses Enter (not if they plug in a security key, bring a smartphone, or anything else.). See also https://github.com/ChocolateLoverRaj/pam-any/issues/19, I experienced something similar there over cancelling password prompts. -- You are receiving this mail because: You are watching all bug changes.
