Add opt-in Plasma fingerprint workaround
This commit is contained in:
@@ -17,6 +17,7 @@ sudo dnf config-manager addrepo \
|
||||
sudo dnf install touchpad-hold-tap
|
||||
sudo dnf install plasma-always-show-unlock
|
||||
sudo dnf install plasma-task-group-shortcuts
|
||||
sudo dnf install plasma-fingerprint-workaround
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
@@ -82,3 +83,20 @@ removing it, restore them with:
|
||||
```bash
|
||||
plasma-task-group-shortcuts --restore-shortcuts
|
||||
```
|
||||
|
||||
### Plasma fingerprint workaround
|
||||
|
||||
Experimental and unaudited. The `plasma-fingerprint-workaround` package is an
|
||||
inert controller for installing a patched Fedora KScreenLocker build that
|
||||
recovers fingerprint authentication after suspend. It makes no lock-screen
|
||||
changes when installed and must be enabled explicitly:
|
||||
|
||||
```bash
|
||||
sudo plasma-fingerprint-workaround enable
|
||||
```
|
||||
|
||||
The patched build is not present in the DNF repository and therefore cannot be
|
||||
selected by a normal system update. The controller can also install a locally
|
||||
built RPM and restore Fedora's original package. See the tool's
|
||||
[`README`](plasma-fingerprint-workaround/README.md) for the security warning,
|
||||
build instructions, version checks, and force-downgrade option.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Plasma fingerprint workaround
|
||||
|
||||
This is an experimental and unaudited replacement for Fedora's KScreenLocker
|
||||
package. It changes security-sensitive session-locking and authentication code.
|
||||
Review the patches and build the package yourself if you do not trust the
|
||||
provided binary.
|
||||
|
||||
Installing `plasma-fingerprint-workaround` does not enable the workaround or
|
||||
modify KScreenLocker. The controller only acts when explicitly asked:
|
||||
|
||||
```bash
|
||||
sudo plasma-fingerprint-workaround enable
|
||||
plasma-fingerprint-workaround status
|
||||
sudo plasma-fingerprint-workaround disable
|
||||
```
|
||||
|
||||
The default payload is kept outside the DNF repository. The controller checks
|
||||
its pinned SHA-256 digest, RPM integrity, package name, architecture, and
|
||||
workaround metadata before asking DNF to install it. Disabling the workaround
|
||||
restores the current KScreenLocker package from Fedora's `fedora` or `updates`
|
||||
repository.
|
||||
|
||||
The provided build currently supports Fedora 44 on x86_64 and is based on
|
||||
`kscreenlocker-6.7.4-1.fc44`. A different base version is rejected by default.
|
||||
To intentionally install the older provided build after a KScreenLocker update:
|
||||
|
||||
```bash
|
||||
sudo plasma-fingerprint-workaround enable --force
|
||||
```
|
||||
|
||||
## Locally built package
|
||||
|
||||
Build dependencies can be installed and the package built with:
|
||||
|
||||
```bash
|
||||
sudo dnf5 builddep plasma-fingerprint-workaround/kscreenlocker/kscreenlocker.spec
|
||||
./scripts/build-patched-kscreenlocker-rpm
|
||||
```
|
||||
|
||||
Install the resulting package directly through the controller:
|
||||
|
||||
```bash
|
||||
sudo plasma-fingerprint-workaround enable \
|
||||
--rpm rpmbuild/RPMS/x86_64/kscreenlocker-6.7.4-1.fc44.ajp5.x86_64.rpm
|
||||
```
|
||||
|
||||
For another upstream version, supply its version, Fedora base release, and
|
||||
source archive checksum. The patches must still apply and compile cleanly.
|
||||
|
||||
```bash
|
||||
KSCREENLOCKER_VERSION=6.7.5 \
|
||||
KSCREENLOCKER_BASE_RELEASE=1 \
|
||||
KSCREENLOCKER_SOURCE_SHA256=SHA256 \
|
||||
./scripts/build-patched-kscreenlocker-rpm
|
||||
```
|
||||
|
||||
A locally built package based on a different installed version requires
|
||||
`--force`. This flag permits the version mismatch and allows DNF to downgrade;
|
||||
it does not bypass the package identity, architecture, integrity, or workaround
|
||||
metadata checks.
|
||||
@@ -0,0 +1,108 @@
|
||||
%{!?upstream_version:%global upstream_version 6.7.4}
|
||||
%{!?base_release:%global base_release 1}
|
||||
%{!?workaround_release:%global workaround_release 5}
|
||||
|
||||
Name: kscreenlocker
|
||||
Version: %{upstream_version}
|
||||
Release: %{base_release}%{?dist}.ajp%{workaround_release}
|
||||
Summary: Library and components for secure lock screen architecture
|
||||
|
||||
License: BSD-3-Clause AND CC0-1.0 AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-only AND LGPL-2.1-only AND LGPL-3.0-only AND (GPL-2.0-only OR GPL-3.0-only) AND (LGPL-2.1-only OR LGPL-3.0-only)
|
||||
URL: https://invent.kde.org/plasma/%{name}
|
||||
Source0: https://download.kde.org/stable/plasma/%{version}/%{name}-%{version}.tar.xz
|
||||
Source1: restart-fprintd-after-resume
|
||||
|
||||
Patch0: 0001-dont-cancel-authentication-on-suspend.patch
|
||||
Patch1: 0002-allow-retrying-noninteractive-authentication.patch
|
||||
Patch2: 0003-allow-retry-after-transient-auth-unavailability.patch
|
||||
Patch3: 0004-retry-transient-unavailability-for-fingerprint-only.patch
|
||||
|
||||
# Marks packages that the controller may install and records the Fedora build
|
||||
# on which the patches were based.
|
||||
Provides: plasma-fingerprint-workaround-payload = 1
|
||||
Provides: plasma-fingerprint-workaround-base = %{version}-%{base_release}%{?dist}
|
||||
Provides: kscreenlocker = %{version}-%{base_release}%{?dist}
|
||||
|
||||
# Help upgrades; kscreenlocker was split from plasma-workspace in Plasma 5.5.
|
||||
Conflicts: plasma-workspace < 5.5
|
||||
|
||||
BuildRequires: cmake(LayerShellQt)
|
||||
BuildRequires: perl-generators
|
||||
BuildRequires: qt6-qtbase-devel
|
||||
BuildRequires: qt6-qtbase-private-devel
|
||||
BuildRequires: cmake(Qt6Quick)
|
||||
BuildRequires: kf6-rpm-macros
|
||||
BuildRequires: extra-cmake-modules
|
||||
BuildRequires: cmake(KF6Crash)
|
||||
BuildRequires: cmake(KF6GlobalAccel)
|
||||
BuildRequires: cmake(KF6I18n)
|
||||
BuildRequires: cmake(KF6IdleTime)
|
||||
BuildRequires: cmake(KF6KCMUtils)
|
||||
BuildRequires: cmake(KF6KIO)
|
||||
BuildRequires: cmake(KF6Notifications)
|
||||
BuildRequires: cmake(KF6Screen)
|
||||
BuildRequires: cmake(KF6Solid)
|
||||
BuildRequires: cmake(KF6Svg)
|
||||
BuildRequires: cmake(KF6XmlGui)
|
||||
BuildRequires: cmake(KF6GuiAddons)
|
||||
BuildRequires: cmake(KF6KirigamiPlatform)
|
||||
BuildRequires: libX11-devel
|
||||
BuildRequires: xcb-util-keysyms-devel
|
||||
BuildRequires: wayland-devel
|
||||
BuildRequires: pkgconfig(xi)
|
||||
BuildRequires: pkgconfig(xcb-event)
|
||||
BuildRequires: cmake(PlasmaQuick)
|
||||
BuildRequires: libXcursor-devel
|
||||
BuildRequires: pam-devel
|
||||
|
||||
%description
|
||||
%{summary}.
|
||||
|
||||
This experimental Fedora 44 build contains local fingerprint authentication
|
||||
workarounds. It has not received the security review of Fedora's package.
|
||||
|
||||
%package devel
|
||||
Summary: Development files for %{name}
|
||||
Requires: %{name}%{?_isa} = %{version}-%{release}
|
||||
|
||||
%description devel
|
||||
The %{name}-devel package contains libraries and header files for developing
|
||||
applications that use %{name}.
|
||||
|
||||
%prep
|
||||
%autosetup -p1
|
||||
|
||||
%build
|
||||
%cmake_kf6
|
||||
%cmake_build
|
||||
|
||||
%install
|
||||
%cmake_install
|
||||
install -D -m 0755 %{SOURCE1} \
|
||||
%{buildroot}%{_prefix}/lib/systemd/system-sleep/restart-fprintd-after-resume
|
||||
|
||||
%find_lang %{name} --with-qt --all-name
|
||||
|
||||
%files -f %{name}.lang
|
||||
%license COPYING
|
||||
%{_kf6_libdir}/libKScreenLocker.so.*
|
||||
%{_kf6_datadir}/knotifications6/*.notifyrc
|
||||
%{_libexecdir}/kscreenlocker_greet
|
||||
%dir %{_kf6_datadir}/ksmserver/
|
||||
%{_kf6_datadir}/ksmserver/screenlocker/
|
||||
%{_kf6_datadir}/applications/kcm_screenlocker.desktop
|
||||
%{_kf6_qtplugindir}/plasma/kcms/systemsettings/kcm_screenlocker.so
|
||||
%{_kf6_datadir}/qlogging-categories6/kscreenlocker.categories
|
||||
%{_prefix}/lib/systemd/system-sleep/restart-fprintd-after-resume
|
||||
|
||||
%files devel
|
||||
%{_kf6_libdir}/libKScreenLocker.so
|
||||
%{_kf6_libdir}/cmake/ScreenSaverDBusInterface/
|
||||
%{_kf6_libdir}/cmake/KScreenLocker/
|
||||
%{_includedir}/KScreenLocker/
|
||||
%{_datadir}/dbus-1/interfaces/*.xml
|
||||
|
||||
%changelog
|
||||
* Fri Sep 04 2026 Anton - 6.7.4-1.fc44.ajp5
|
||||
- Package the fprintd resume hook and compatibility metadata
|
||||
- Apply the tested fingerprint retry patch set
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
From: Alex Sanchez-Stern <alex@dmodel.ai>
|
||||
Subject: [PATCH] Don't cancel in-progress authentication on suspend
|
||||
|
||||
Aborting an in-progress PAM conversation when the system suspends makes
|
||||
pam_authenticate() report a failure. The greeter then displays a failed
|
||||
login on resume, and authentication backends such as fingerprint can be
|
||||
left unusable for the remainder of that lock-screen session.
|
||||
|
||||
Leave the conversation parked across suspend and resume instead. This is
|
||||
the change proposed upstream in KScreenLocker merge request 340, adapted
|
||||
to the Plasma 6.7.4 source shipped by Fedora 44.
|
||||
---
|
||||
greeter/greeterapp.cpp | 6 ------
|
||||
greeter/greeterapp.h | 2 --
|
||||
2 files changed, 8 deletions(-)
|
||||
|
||||
diff --git a/greeter/greeterapp.cpp b/greeter/greeterapp.cpp
|
||||
index b1de459..08d8134 100644
|
||||
--- a/greeter/greeterapp.cpp
|
||||
+++ b/greeter/greeterapp.cpp
|
||||
@@ -10,8 +10,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "shell_integration.h"
|
||||
#include "wallpaper_integration.h"
|
||||
|
||||
-#include "../logind.h"
|
||||
-
|
||||
#include <config-kscreenlocker.h>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
@@ -133,7 +131,6 @@ UnlockApp::UnlockApp(int &argc, char **argv)
|
||||
, m_graceTime(0)
|
||||
, m_noLock(false)
|
||||
, m_shellIntegration(new ShellIntegration(this))
|
||||
- , m_logindIntegration(new LogindIntegration(this))
|
||||
{
|
||||
auto interactive = std::make_unique<PamAuthenticator>(QStringLiteral(KSCREENLOCKER_PAM_SERVICE), KUser().loginName());
|
||||
std::vector<std::unique_ptr<PamAuthenticator>> noninteractive;
|
||||
@@ -142,9 +139,6 @@ UnlockApp::UnlockApp(int &argc, char **argv)
|
||||
noninteractive.push_back(
|
||||
std::make_unique<PamAuthenticator>(QStringLiteral(KSCREENLOCKER_PAM_SMARTCARD_SERVICE), KUser().loginName(), PamAuthenticator::Smartcard));
|
||||
m_authenticators = new PamAuthenticators(std::move(interactive), std::move(noninteractive), this);
|
||||
- connect(m_logindIntegration, &LogindIntegration::prepareForSleep, m_authenticators, [this] {
|
||||
- m_authenticators->cancel();
|
||||
- });
|
||||
initialize();
|
||||
|
||||
if (KWindowSystem::isPlatformX11()) {
|
||||
diff --git a/greeter/greeterapp.h b/greeter/greeterapp.h
|
||||
index 2362dd2..ff355de 100644
|
||||
--- a/greeter/greeterapp.h
|
||||
+++ b/greeter/greeterapp.h
|
||||
@@ -25,7 +25,6 @@ class QuickViewSharedEngine;
|
||||
}
|
||||
|
||||
class Authenticator;
|
||||
-class LogindIntegration;
|
||||
|
||||
struct org_kde_ksld;
|
||||
|
||||
@@ -94,6 +93,5 @@ private:
|
||||
|
||||
KPackage::Package m_wallpaperPackage;
|
||||
ShellIntegration *m_shellIntegration;
|
||||
- LogindIntegration *m_logindIntegration;
|
||||
};
|
||||
} // namespace
|
||||
--
|
||||
2.51.0
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
From: Anton Partanen
|
||||
Subject: [PATCH] Allow retrying non-interactive authentication
|
||||
|
||||
An active password conversation should not prevent a completed fingerprint
|
||||
conversation from being started again. PamAuthenticator::tryUnlock() already
|
||||
ignores authenticators that are still running, so let subsequent start
|
||||
requests reach each authenticator.
|
||||
|
||||
This is a minimal backport of the state-guard removal in upstream commit
|
||||
a5ed9ca0. It allows a fingerprint attempt interrupted by suspend to restart
|
||||
without cancelling the password conversation.
|
||||
---
|
||||
greeter/pamauthenticators.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/greeter/pamauthenticators.cpp b/greeter/pamauthenticators.cpp
|
||||
index 7c53b73..f60516d 100644
|
||||
--- a/greeter/pamauthenticators.cpp
|
||||
+++ b/greeter/pamauthenticators.cpp
|
||||
@@ -141,7 +141,7 @@ PamAuthenticators::AuthenticatorsState PamAuthenticators::state() const
|
||||
|
||||
void PamAuthenticators::startAuthenticating()
|
||||
{
|
||||
- if (d->state == AuthenticatorsState::Authenticating || d->graceLocked) {
|
||||
+ if (d->graceLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
--
|
||||
2.51.0
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
From: Anton Partanen
|
||||
Subject: [PATCH] Allow retry after transient authentication unavailability
|
||||
|
||||
PAM_AUTHINFO_UNAVAIL is not necessarily permanent. In particular,
|
||||
pam_fprintd returns it when an active verification is interrupted by system
|
||||
suspend. Treat that result as a failed attempt so a later start request can
|
||||
retry it. Continue to cache PAM_MODULE_UNKNOWN as permanent unavailability.
|
||||
---
|
||||
greeter/pamauthenticator.cpp | 4 +++-
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/greeter/pamauthenticator.cpp b/greeter/pamauthenticator.cpp
|
||||
index e2011fd..bb2521f 100644
|
||||
--- a/greeter/pamauthenticator.cpp
|
||||
+++ b/greeter/pamauthenticator.cpp
|
||||
@@ -176,7 +176,9 @@ void PamWorker::authenticate()
|
||||
pam_setcred(m_handle, PAM_REFRESH_CRED);
|
||||
/* ignore errors on refresh credentials. If this did not work we use the old ones. */
|
||||
Q_EMIT succeeded();
|
||||
- } else if (rc == PAM_AUTHINFO_UNAVAIL || rc == PAM_MODULE_UNKNOWN) {
|
||||
+ } else if (rc == PAM_AUTHINFO_UNAVAIL) {
|
||||
+ Q_EMIT failed();
|
||||
+ } else if (rc == PAM_MODULE_UNKNOWN) {
|
||||
m_unavailable = true;
|
||||
Q_EMIT unavailabilityChanged(m_unavailable);
|
||||
} else {
|
||||
--
|
||||
2.51.0
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
From: Anton Partanen
|
||||
Subject: [PATCH] Retry transient unavailability for fingerprint only
|
||||
|
||||
Other non-interactive authenticators, notably smartcards, also use
|
||||
PAM_AUTHINFO_UNAVAIL to report that no credential is present. Restrict the
|
||||
retry behavior to fingerprint authenticators so absent smartcards remain
|
||||
unavailable instead of repeatedly flashing in the UI.
|
||||
---
|
||||
greeter/pamauthenticator.cpp | 12 +++++++-----
|
||||
1 file changed, 7 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/greeter/pamauthenticator.cpp b/greeter/pamauthenticator.cpp
|
||||
index bb2521f..e27e61c 100644
|
||||
--- a/greeter/pamauthenticator.cpp
|
||||
+++ b/greeter/pamauthenticator.cpp
|
||||
@@ -27,7 +27,7 @@ class PamWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
- PamWorker();
|
||||
+ explicit PamWorker(bool retryOnAuthInfoUnavailable);
|
||||
~PamWorker() override;
|
||||
Q_DISABLE_COPY_MOVE(PamWorker)
|
||||
void start(const QString &service, const QString &user);
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
bool m_unavailable = false;
|
||||
bool m_inAuthenticate = false;
|
||||
std::chrono::steady_clock::time_point m_nextAttemptAllowedTime;
|
||||
+ const bool m_retryOnAuthInfoUnavailable;
|
||||
int m_result = -1;
|
||||
QString m_service;
|
||||
};
|
||||
@@ -163,10 +164,11 @@ int PamWorker::converse(int n, const struct pam_message **msg, struct pam_respons
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
-PamWorker::PamWorker()
|
||||
+PamWorker::PamWorker(bool retryOnAuthInfoUnavailable)
|
||||
: QObject(nullptr)
|
||||
, m_conv({&PamWorker::converse, this})
|
||||
, m_nextAttemptAllowedTime(std::chrono::steady_clock::now())
|
||||
+ , m_retryOnAuthInfoUnavailable(retryOnAuthInfoUnavailable)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -176,9 +178,9 @@ void PamWorker::authenticate()
|
||||
pam_setcred(m_handle, PAM_REFRESH_CRED);
|
||||
/* ignore errors on refresh credentials. If this did not work we use the old ones. */
|
||||
Q_EMIT succeeded();
|
||||
- } else if (rc == PAM_AUTHINFO_UNAVAIL) {
|
||||
+ } else if (rc == PAM_AUTHINFO_UNAVAIL && m_retryOnAuthInfoUnavailable) {
|
||||
Q_EMIT failed();
|
||||
- } else if (rc == PAM_MODULE_UNKNOWN) {
|
||||
+ } else if (rc == PAM_AUTHINFO_UNAVAIL || rc == PAM_MODULE_UNKNOWN) {
|
||||
m_unavailable = true;
|
||||
Q_EMIT unavailabilityChanged(m_unavailable);
|
||||
} else {
|
||||
@@ -268,7 +271,7 @@ PamAuthenticator::PamAuthenticator(const QString &service, const QString &user, N
|
||||
})
|
||||
, m_service(service)
|
||||
, m_authenticatorType(types)
|
||||
- , d(new PamWorker)
|
||||
+ , d(new PamWorker(types.testFlag(NoninteractiveAuthenticatorType::Fingerprint)))
|
||||
{
|
||||
d->moveToThread(&m_thread);
|
||||
|
||||
--
|
||||
2.51.0
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ "$1" = post ] || exit 0
|
||||
systemctl --quiet is-active fprintd.service || exit 0
|
||||
|
||||
systemctl --no-block restart fprintd.service
|
||||
@@ -0,0 +1,2 @@
|
||||
payload_url=https://git.ajpanton.se/api/packages/ajp_anton/generic/plasma-fingerprint-workaround/6.7.4-1.fc44.ajp5/kscreenlocker-6.7.4-1.fc44.ajp5.x86_64.rpm
|
||||
payload_sha256=d64515f3c7cb9c44b1c8fc194e90503be5944b1d24094aab2f68d989b3eca381
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PATH=/usr/sbin:/usr/bin
|
||||
readonly data_dir=/usr/share/plasma-fingerprint-workaround
|
||||
readonly payload_config="$data_dir/payload.conf"
|
||||
readonly program=${0##*/}
|
||||
temporary_dir=
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$program status
|
||||
$program enable [--rpm PATH] [--force] [--yes]
|
||||
$program disable [--yes]
|
||||
|
||||
Without --rpm, enable downloads the tested prebuilt package. A local package
|
||||
must have been built from this tool's KScreenLocker spec. --force permits a
|
||||
base-version mismatch, including an intentional downgrade.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'Error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_root() {
|
||||
(( EUID == 0 )) || die "run this command as root"
|
||||
}
|
||||
|
||||
require_fedora() {
|
||||
# shellcheck source=/dev/null
|
||||
source /etc/os-release
|
||||
[[ ${ID:-} == fedora ]] || die "this workaround only supports Fedora Linux"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
[[ -n $temporary_dir ]] || return
|
||||
rm -f -- "$temporary_dir/kscreenlocker.rpm"
|
||||
rmdir -- "$temporary_dir"
|
||||
}
|
||||
|
||||
package_value() {
|
||||
local mode=$1
|
||||
local format=$2
|
||||
local package=$3
|
||||
|
||||
rpm "$mode" --qf "$format" -- "$package"
|
||||
}
|
||||
|
||||
package_base() {
|
||||
local mode=$1
|
||||
local package=$2
|
||||
local provides
|
||||
|
||||
provides=$(rpm "$mode" --provides -- "$package")
|
||||
sed -n 's/^plasma-fingerprint-workaround-base = //p' <<<"$provides"
|
||||
}
|
||||
|
||||
is_workaround_package() {
|
||||
local mode=$1
|
||||
local package=$2
|
||||
local provides
|
||||
|
||||
provides=$(rpm "$mode" --provides -- "$package")
|
||||
grep -qx 'plasma-fingerprint-workaround-payload = 1' <<<"$provides"
|
||||
}
|
||||
|
||||
is_legacy_workaround() {
|
||||
[[ $(installed_value '%{RELEASE}') =~ \.ajp[[:digit:]]+$ ]]
|
||||
}
|
||||
|
||||
is_any_workaround() {
|
||||
is_workaround_package -q kscreenlocker || is_legacy_workaround
|
||||
}
|
||||
|
||||
installed_value() {
|
||||
package_value -q "$1" kscreenlocker
|
||||
}
|
||||
|
||||
installed_base() {
|
||||
local base
|
||||
|
||||
base=$(package_base -q kscreenlocker)
|
||||
if [[ -n $base ]]; then
|
||||
printf '%s\n' "$base"
|
||||
elif is_legacy_workaround; then
|
||||
printf '%s-%s\n' \
|
||||
"$(installed_value '%{VERSION}')" \
|
||||
"$(installed_value '%{RELEASE}' | sed 's/\.ajp[[:digit:]]*$//')"
|
||||
else
|
||||
installed_value '%{EVR}'
|
||||
fi
|
||||
}
|
||||
|
||||
show_status() {
|
||||
if ! rpm -q kscreenlocker >/dev/null 2>&1; then
|
||||
printf 'KScreenLocker is not installed.\n'
|
||||
return 1
|
||||
fi
|
||||
|
||||
if is_workaround_package -q kscreenlocker; then
|
||||
printf 'Fingerprint workaround: enabled\n'
|
||||
printf 'Installed package: kscreenlocker-%s.%s\n' \
|
||||
"$(installed_value '%{EVR}')" "$(installed_value '%{ARCH}')"
|
||||
printf 'Based on Fedora package: kscreenlocker-%s\n' "$(installed_base)"
|
||||
elif is_legacy_workaround; then
|
||||
printf 'Fingerprint workaround: enabled (legacy unmanaged build)\n'
|
||||
printf 'Installed package: kscreenlocker-%s.%s\n' \
|
||||
"$(installed_value '%{EVR}')" "$(installed_value '%{ARCH}')"
|
||||
printf 'Based on Fedora package: kscreenlocker-%s\n' "$(installed_base)"
|
||||
else
|
||||
printf 'Fingerprint workaround: disabled\n'
|
||||
printf 'Installed package: kscreenlocker-%s.%s\n' \
|
||||
"$(installed_value '%{EVR}')" "$(installed_value '%{ARCH}')"
|
||||
fi
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local answer
|
||||
|
||||
if (( assume_yes )); then
|
||||
return
|
||||
fi
|
||||
|
||||
printf 'Continue? [y/N] '
|
||||
read -r answer || exit 0
|
||||
[[ $answer == y || $answer == Y ]] || exit 0
|
||||
}
|
||||
|
||||
load_payload_config() {
|
||||
[[ -r $payload_config ]] || die "payload configuration is missing"
|
||||
|
||||
unset payload_url payload_sha256
|
||||
# This root-owned file is installed by the controller RPM.
|
||||
# shellcheck source=/dev/null
|
||||
source "$payload_config"
|
||||
|
||||
[[ ${payload_url:-} == https://* ]] || die "invalid payload URL"
|
||||
[[ ${payload_sha256:-} =~ ^[[:xdigit:]]{64}$ ]] ||
|
||||
die "invalid payload checksum"
|
||||
}
|
||||
|
||||
validate_candidate() {
|
||||
local candidate=$1
|
||||
local name arch candidate_evr candidate_base current_base current_evr
|
||||
|
||||
rpm -K --nosignature "$candidate" >/dev/null || die "the RPM is corrupt"
|
||||
|
||||
name=$(package_value -qp '%{NAME}' "$candidate")
|
||||
[[ $name == kscreenlocker ]] ||
|
||||
die "expected a kscreenlocker RPM, got $name"
|
||||
|
||||
is_workaround_package -qp "$candidate" ||
|
||||
die "the RPM was not built from this workaround's KScreenLocker spec"
|
||||
|
||||
arch=$(package_value -qp '%{ARCH}' "$candidate")
|
||||
[[ $arch == "$(installed_value '%{ARCH}')" ]] ||
|
||||
die "RPM architecture $arch does not match installed architecture $(installed_value '%{ARCH}')"
|
||||
|
||||
candidate_base=$(package_base -qp "$candidate")
|
||||
[[ -n $candidate_base ]] || die "the RPM has no base-version marker"
|
||||
|
||||
candidate_evr=$(package_value -qp '%{EVR}' "$candidate")
|
||||
current_base=$(installed_base)
|
||||
current_evr=$(installed_value '%{EVR}')
|
||||
|
||||
printf 'Installed: kscreenlocker-%s\n' "$current_evr"
|
||||
printf 'Candidate: kscreenlocker-%s (based on %s)\n' \
|
||||
"$candidate_evr" "$candidate_base"
|
||||
|
||||
if [[ $candidate_base != "$current_base" ]]; then
|
||||
if (( ! force )); then
|
||||
die "candidate base $candidate_base does not match installed base $current_base; use --force to allow the version change"
|
||||
fi
|
||||
printf 'Warning: forcing a KScreenLocker base-version change.\n' >&2
|
||||
fi
|
||||
}
|
||||
|
||||
enable_workaround() {
|
||||
local rpm_path='' candidate
|
||||
local -a dnf_args
|
||||
|
||||
force=0
|
||||
assume_yes=0
|
||||
while (( $# )); do
|
||||
case $1 in
|
||||
--rpm)
|
||||
(( $# >= 2 )) || die "--rpm requires a path"
|
||||
rpm_path=$2
|
||||
shift 2
|
||||
;;
|
||||
--force)
|
||||
force=1
|
||||
shift
|
||||
;;
|
||||
--yes)
|
||||
assume_yes=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
die "unknown enable option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_root
|
||||
require_fedora
|
||||
rpm -q kscreenlocker >/dev/null 2>&1 || die "KScreenLocker is not installed"
|
||||
|
||||
umask 077
|
||||
temporary_dir=$(mktemp -d /var/tmp/plasma-fingerprint-workaround.XXXXXX)
|
||||
trap cleanup EXIT
|
||||
candidate="$temporary_dir/kscreenlocker.rpm"
|
||||
|
||||
if [[ -n $rpm_path ]]; then
|
||||
[[ -f $rpm_path ]] || die "RPM not found: $rpm_path"
|
||||
install -m 0600 -- "$rpm_path" "$candidate"
|
||||
else
|
||||
load_payload_config
|
||||
printf 'Downloading the tested KScreenLocker build...\n'
|
||||
curl --fail --location --show-error --silent \
|
||||
--proto '=https' --proto-redir '=https' \
|
||||
--output "$candidate" "$payload_url"
|
||||
printf '%s %s\n' "$payload_sha256" "$candidate" |
|
||||
sha256sum --check --status - || die "downloaded RPM checksum does not match"
|
||||
fi
|
||||
|
||||
validate_candidate "$candidate"
|
||||
printf '\nThis replaces Fedora\x27s security-sensitive KScreenLocker package.\n'
|
||||
confirm
|
||||
|
||||
dnf_args=(install --assumeyes)
|
||||
(( force )) && dnf_args+=(--allow-downgrade)
|
||||
dnf5 "${dnf_args[@]}" "$candidate"
|
||||
|
||||
is_workaround_package -q kscreenlocker ||
|
||||
die "DNF completed without enabling the workaround"
|
||||
printf 'Fingerprint workaround enabled. Reboot before testing it.\n'
|
||||
}
|
||||
|
||||
disable_workaround() {
|
||||
assume_yes=0
|
||||
while (( $# )); do
|
||||
case $1 in
|
||||
--yes)
|
||||
assume_yes=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
die "unknown disable option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_root
|
||||
require_fedora
|
||||
if ! is_any_workaround; then
|
||||
printf 'Fingerprint workaround is already disabled.\n'
|
||||
return
|
||||
fi
|
||||
|
||||
printf 'This will replace the workaround with Fedora\x27s current KScreenLocker package.\n'
|
||||
confirm
|
||||
|
||||
dnf5 \
|
||||
--repo=fedora,updates \
|
||||
--refresh \
|
||||
distro-sync \
|
||||
--assumeyes \
|
||||
kscreenlocker
|
||||
|
||||
if is_any_workaround; then
|
||||
die "no suitable Fedora KScreenLocker package was installed"
|
||||
fi
|
||||
printf 'Fingerprint workaround disabled. Reboot before testing the lock screen.\n'
|
||||
}
|
||||
|
||||
command=${1:-}
|
||||
[[ -n $command ]] || {
|
||||
usage
|
||||
exit 2
|
||||
}
|
||||
shift
|
||||
|
||||
case $command in
|
||||
status)
|
||||
(( $# == 0 )) || die "status takes no options"
|
||||
show_status
|
||||
;;
|
||||
enable)
|
||||
enable_workaround "$@"
|
||||
;;
|
||||
disable)
|
||||
disable_workaround "$@"
|
||||
;;
|
||||
-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,54 @@
|
||||
Name: plasma-fingerprint-workaround
|
||||
Version: 0.1.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Opt-in installer for a patched Fedora KScreenLocker
|
||||
|
||||
License: MIT
|
||||
URL: https://git.ajpanton.se/ajp_anton/fedora-tools
|
||||
Source0: plasma-fingerprint-workaround
|
||||
Source1: payload.conf
|
||||
Source2: LICENSE
|
||||
Source3: README.md
|
||||
Source4: test-controller
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: bash
|
||||
BuildRequires: grep
|
||||
Requires: bash
|
||||
Requires: coreutils
|
||||
Requires: curl
|
||||
Requires: dnf5
|
||||
Requires: grep
|
||||
Requires: rpm
|
||||
Requires: sed
|
||||
|
||||
%description
|
||||
Provides an explicit, reversible installer for an experimental KScreenLocker
|
||||
fingerprint workaround. Installing this package alone makes no system changes.
|
||||
|
||||
%prep
|
||||
|
||||
%build
|
||||
|
||||
%check
|
||||
bash %{SOURCE4} %{SOURCE0} %{SOURCE1}
|
||||
|
||||
%install
|
||||
install -D -m 0755 %{SOURCE0} \
|
||||
%{buildroot}%{_bindir}/plasma-fingerprint-workaround
|
||||
install -D -m 0644 %{SOURCE1} \
|
||||
%{buildroot}%{_datadir}/plasma-fingerprint-workaround/payload.conf
|
||||
install -D -m 0644 %{SOURCE2} \
|
||||
%{buildroot}%{_licensedir}/%{name}/LICENSE
|
||||
install -D -m 0644 %{SOURCE3} \
|
||||
%{buildroot}%{_docdir}/%{name}/README.md
|
||||
|
||||
%files
|
||||
%license %{_licensedir}/%{name}/LICENSE
|
||||
%doc %{_docdir}/%{name}/README.md
|
||||
%{_bindir}/plasma-fingerprint-workaround
|
||||
%{_datadir}/plasma-fingerprint-workaround/payload.conf
|
||||
|
||||
%changelog
|
||||
* Fri Sep 04 2026 Anton - 0.1.0-1
|
||||
- Initial package
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
controller=$1
|
||||
payload_config=$2
|
||||
|
||||
bash -n "$controller"
|
||||
"$controller" --help | grep -q '^ .* enable \[--rpm PATH\] \[--force\] \[--yes\]$'
|
||||
|
||||
if "$controller" unknown-command >/dev/null 2>&1; then
|
||||
printf 'Unknown command unexpectedly succeeded.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
payload_url=
|
||||
payload_sha256=
|
||||
# shellcheck source=/dev/null
|
||||
source "$payload_config"
|
||||
[[ $payload_url == https://*/*.rpm ]]
|
||||
[[ $payload_sha256 =~ ^[[:xdigit:]]{64}$ ]]
|
||||
|
||||
printf 'Controller tests passed.\n'
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
rpm_path=$1
|
||||
expected_base=$2
|
||||
provides=$(rpm -qp --provides "$rpm_path")
|
||||
files=$(rpm -qpl "$rpm_path")
|
||||
|
||||
rpm -K --nosignature "$rpm_path" >/dev/null
|
||||
[[ $(rpm -qp --qf '%{NAME}' "$rpm_path") == kscreenlocker ]]
|
||||
grep -qx 'plasma-fingerprint-workaround-payload = 1' <<<"$provides"
|
||||
grep -qx "plasma-fingerprint-workaround-base = $expected_base" <<<"$provides"
|
||||
grep -qx '/usr/lib/systemd/system-sleep/restart-fprintd-after-resume' <<<"$files"
|
||||
|
||||
printf 'Patched KScreenLocker package tests passed.\n'
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
source_dir="$repo_root/plasma-fingerprint-workaround/kscreenlocker"
|
||||
topdir="$repo_root/rpmbuild"
|
||||
version=${KSCREENLOCKER_VERSION:-6.7.4}
|
||||
base_release=${KSCREENLOCKER_BASE_RELEASE:-1}
|
||||
workaround_release=${KSCREENLOCKER_WORKAROUND_RELEASE:-5}
|
||||
archive="$topdir/SOURCES/kscreenlocker-$version.tar.xz"
|
||||
archive_url="https://download.kde.org/stable/plasma/$version/kscreenlocker-$version.tar.xz"
|
||||
|
||||
if [[ $version == 6.7.4 ]]; then
|
||||
default_archive_sha256=f9cd525d501f08931f2c0b4d08f5384588967bc89fac62e39ed4ae2da2925a39
|
||||
else
|
||||
default_archive_sha256=
|
||||
fi
|
||||
archive_sha256=${KSCREENLOCKER_SOURCE_SHA256:-$default_archive_sha256}
|
||||
if [[ ! $archive_sha256 =~ ^[[:xdigit:]]{64}$ ]]; then
|
||||
printf 'Error: set KSCREENLOCKER_SOURCE_SHA256 for KScreenLocker %s.\n' \
|
||||
"$version" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "$topdir"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
|
||||
if [[ ! -f $archive ]]; then
|
||||
temporary_archive="$archive.download"
|
||||
trap 'rm -f -- "$temporary_archive"' EXIT
|
||||
curl --fail --location --show-error \
|
||||
--proto '=https' --proto-redir '=https' \
|
||||
--output "$temporary_archive" "$archive_url"
|
||||
mv -- "$temporary_archive" "$archive"
|
||||
trap - EXIT
|
||||
fi
|
||||
|
||||
printf '%s %s\n' "$archive_sha256" "$archive" |
|
||||
sha256sum --check --status - || {
|
||||
printf 'Error: KScreenLocker source archive checksum does not match.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for patch in "$source_dir"/patches/*.patch; do
|
||||
install -m 0644 "$patch" "$topdir/SOURCES/${patch##*/}"
|
||||
done
|
||||
install -m 0755 \
|
||||
"$source_dir/restart-fprintd-after-resume" \
|
||||
"$topdir/SOURCES/restart-fprintd-after-resume"
|
||||
|
||||
rpmbuild \
|
||||
--define "_topdir $topdir" \
|
||||
--define "upstream_version $version" \
|
||||
--define "base_release $base_release" \
|
||||
--define "workaround_release $workaround_release" \
|
||||
-ba "$source_dir/kscreenlocker.spec"
|
||||
|
||||
rpm_path="$topdir/RPMS/$(rpm --eval '%{_arch}')/kscreenlocker-$version-$base_release$(rpm --eval '%{?dist}').ajp$workaround_release.$(rpm --eval '%{_arch}').rpm"
|
||||
bash "$repo_root/plasma-fingerprint-workaround/tests/test-patched-kscreenlocker-rpm" \
|
||||
"$rpm_path" "$version-$base_release$(rpm --eval '%{?dist}')"
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
tool_dir="$repo_root/plasma-fingerprint-workaround"
|
||||
topdir="$repo_root/rpmbuild"
|
||||
|
||||
mkdir -p "$topdir"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
install -m 0755 \
|
||||
"$tool_dir/plasma-fingerprint-workaround" \
|
||||
"$topdir/SOURCES/plasma-fingerprint-workaround"
|
||||
install -m 0644 "$tool_dir/payload.conf" "$topdir/SOURCES/payload.conf"
|
||||
install -m 0644 "$repo_root/LICENSE" "$topdir/SOURCES/LICENSE"
|
||||
install -m 0644 "$tool_dir/README.md" "$topdir/SOURCES/README.md"
|
||||
install -m 0755 "$tool_dir/tests/test-controller" "$topdir/SOURCES/test-controller"
|
||||
|
||||
rpmbuild \
|
||||
--define "_topdir $topdir" \
|
||||
-bb "$tool_dir/plasma-fingerprint-workaround.spec"
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
printf 'Usage: GITEA_TOKEN=... %s PATH_TO_RPM\n' "$0" >&2
|
||||
}
|
||||
|
||||
if (( $# != 1 )); then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
payload_config="$repo_root/plasma-fingerprint-workaround/payload.conf"
|
||||
rpm_path=$1
|
||||
|
||||
if [[ ! -f $rpm_path || $rpm_path != *.rpm ]]; then
|
||||
printf 'Error: %s is not an RPM file.\n' "$rpm_path" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z ${GITEA_TOKEN:-} ]]; then
|
||||
printf 'Error: set GITEA_TOKEN to a Gitea access token with package write permission.\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
payload_url=
|
||||
payload_sha256=
|
||||
# shellcheck source=/dev/null
|
||||
source "$payload_config"
|
||||
|
||||
actual_sha256=$(sha256sum "$rpm_path")
|
||||
actual_sha256=${actual_sha256%% *}
|
||||
if [[ $actual_sha256 != "$payload_sha256" ]]; then
|
||||
printf 'Error: RPM checksum does not match payload.conf.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ${rpm_path##*/} != "${payload_url##*/}" ]]; then
|
||||
printf 'Error: RPM filename does not match payload.conf.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl \
|
||||
--fail-with-body \
|
||||
--show-error \
|
||||
--silent \
|
||||
--user "ajp_anton:${GITEA_TOKEN}" \
|
||||
--upload-file "$rpm_path" \
|
||||
"$payload_url"
|
||||
|
||||
printf 'Published %s as a generic Gitea package.\n' "$rpm_path"
|
||||
Reference in New Issue
Block a user