fix: reset faillock after a face unlock of the lock screen

This commit is contained in:
Felitendo committed 2026-09-22 20:52:15 +02:00
1 parent 2ff27160e3
commit 7c87adb733
5 files changed
+39

No files matched your search

+5
View File
@@ -181,6 +181,11 @@ After a scan that did not get anybody in, the next one waits for the person to
be still for two seconds and then touch something again, so typing the password be still for two seconds and then touch something again, so typing the password
does not start a scan with every key. does not start a scan with every key.
Unlocked through logind, the lock screen cuts off its own password prompt and
counts that as a wrong password. After a face unlock the daemon resets the
failed logins of *pam_faillock*(8), as a correct password does, so face unlocks
never lock the account.
The bubble is a layer-shell surface that KWin keeps above the lock screen The bubble is a layer-shell surface that KWin keeps above the lock screen
(kde_lockscreen_overlay_v1). KWin only allows that for a program whose desktop (kde_lockscreen_overlay_v1). KWin only allows that for a program whose desktop
file asks for it, which is file asks for it, which is
+2
View File
@@ -19,6 +19,8 @@ CapabilityBoundingSet=CAP_DAC_READ_SEARCH
# runtime directory, to tell the bubble about a sudo scan. # runtime directory, to tell the bubble about a sudo scan.
NoNewPrivileges=yes NoNewPrivileges=yes
ProtectSystem=strict ProtectSystem=strict
# To take back the failed login the lock screen counts for a face unlock.
ReadWritePaths=-/run/faillock
ProtectHome=read-only ProtectHome=read-only
PrivateTmp=yes PrivateTmp=yes
PrivateNetwork=yes PrivateNetwork=yes
+13
View File
@@ -69,6 +69,10 @@ QJsonObject result(bool ok, const QString &reason = {})
return o; return o;
} }
// A face unlock and the lock screen going away this soon after belong
// together.
constexpr qint64 UnlockPairSeconds = 30;
bool isFailureThatCounts(const QString &reason) bool isFailureThatCounts(const QString &reason)
{ {
// A face that did not match, a fake, or a match that never showed a sign // A face that did not match, a fake, or a match that never showed a sign
@@ -403,6 +407,15 @@ void Server::handleWatch(Client *client)
void Server::handleUnlocked(Client *client) void Server::handleUnlocked(Client *client)
{ {
UserState state = UserState::load(m_options.stateDir, client->uid()); UserState state = UserState::load(m_options.stateDir, client->uid());
// Our unlock goes through logind. The lock screen's password prompt is
// cut off mid-question by it and counts that as a wrong password, so a
// few face unlocks would lock the account (pam_faillock). Taken back here.
const qint64 now = QDateTime::currentSecsSinceEpoch();
if (geteuid() == 0 && state.lastPurpose == u"unlock" && now - state.lastUnlock <= UnlockPairSeconds) {
System::resetFailedLogins(client->uid());
}
if (state.failures || state.lockedUntil) { if (state.failures || state.lockedUntil) {
state.failures = 0; state.failures = 0;
state.lockedUntil = 0; state.lockedUntil = 0;
+15
View File
@@ -6,6 +6,8 @@
#include <QDBusInterface> #include <QDBusInterface>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QProcess>
#include <QStandardPaths>
#include <pwd.h> #include <pwd.h>
#include <unistd.h> #include <unistd.h>
@@ -79,4 +81,17 @@ quint64 processStartTime(pid_t pid)
const QList<QByteArray> fields = line.mid(close + 2).split(' '); const QList<QByteArray> fields = line.mid(close + 2).split(' ');
return fields.size() > 19 ? fields.at(19).toULongLong() : 0; return fields.size() > 19 ? fields.at(19).toULongLong() : 0;
} }
bool resetFailedLogins(uid_t uid)
{
// Not from PATH: this runs as root.
const QString faillock = QStandardPaths::findExecutable(QStringLiteral("faillock"),
{QStringLiteral("/usr/sbin"), QStringLiteral("/usr/bin"), QStringLiteral("/sbin")});
if (faillock.isEmpty()) {
return false;
}
QProcess p;
p.start(faillock, {QStringLiteral("--user"), nameOf(uid), QStringLiteral("--reset")});
return p.waitForFinished(3000) && p.exitStatus() == QProcess::NormalExit && p.exitCode() == 0;
}
} // namespace System } // namespace System
+4
View File
@@ -20,4 +20,8 @@ bool lidClosed();
// When a process started, in clock ticks since boot, as polkit wants it to // When a process started, in clock ticks since boot, as polkit wants it to
// tell a process from a later one that got the same pid. // tell a process from a later one that got the same pid.
quint64 processStartTime(pid_t pid); quint64 processStartTime(pid_t pid);
// Forget the failed logins pam_faillock counted for somebody, as a correct
// password does. False without faillock.
bool resetFailedLogins(uid_t uid);
} // namespace System } // namespace System