Package: sddm
Version: 0.19.0-5
Severity: normal
Tags: patch
X-Debbugs-Cc: adrela...@kicksecure.com, arraybo...@gmail.com

NOTICE: This bug only affects Bookworm to my awareness. This bug is
present in SDDM 0.19.0 upstream, and does not exist in SDDM 0.20.0
upstream from my testing, and both Trixie and Sid use SDDM 0.21.0.

SDDM supports reading several kinds of messages from PAM, including
informational messages intended for display to the end-user. These
messages can be generated if, for instance, a line is present in
/etc/pam.d/common-auth such as:

    auth    optional        pam_exec.so debug stdout seteuid 
/usr/bin/script-that-writes-to-stdout

While SDDM currently lacks the ability to actually display these
messages, it's designed to read them and log them, and their presence
should not block login. Unfortunately, these informational message
actually do block login on Debian 12. If any PAM module outputs info
to stdout that SDDM considers "informational", it may result in SDDM
hanging forever while trying to log in. To reproduce:

* Install Debian 12 with a desktop environment that comes with SDDM.
  KDE Plasma and LXQt both work here.
* Log in.
* Create a script at /usr/bin/messup.sh, with the following contents:

    #!/bin/bash
    echo 'Mess1'
    echo 'Mess2'
    echo 'Mess3'
    echo 'Mess4'
    echo 'Mess5'
    echo 'Mess6'
    echo 'Mess7'

* Set /usr/bin/messup.sh executable.
* In a new terminal, run `sudo -i` and ensure you see seven lines of
  text, 'Mess1' through 'Mess7' displayed.
* Add a line to /etc/pam.d/common-auth as follows:

    auth    optional        pam_exec.so debug stdout seteuid /usr/bin/messup.sh

* Log out.
* Attempt to log back in. SDDM will hang.
* To recover, switch to a TTY, log in, run `sudo vi
  /usr/bin/messup.sh`, and comment out all seven 'Mess' lines.
  Save the file, then run `sudo systemctl restart sddm`.

I tested SDDM upstream and discovered that 0.19.0 had this bug, but
0.20.0 did not. I then bisected between them, and found that the first
good commit was 6a9d0875, "Port to Qt6 QRegularExpression". This
significantly changed how several regular expressions in SDDM were
parsed. I then applied the changes made in this commit to Debian 12's
SDDM package (adjusting the patch as necessary for it to work with
Debian's SDDM), tested it by repeating the steps above, and found that
the issue persisted.

After checking the systemd journal for anything about SDDM, I
discovered that SDDM was failing to read all of the output from my
messup.sh script. This bug was also already fixed in SDDM upstream,
in commit 48a9ec7b, "Process all available auth messages in a loop".
This commit also required some slight changes to make it apply to
Debian's SDDM. After applying this patch as well, I ran the above
tests again, and the issue was resolved.

The final patch I used is as follows. Some notes:

* I have no idea why changing the way regex matches are done fixes
  the problem. I've looked through all of the regexes in this patch and
  they all look exactly the same in both the old and new versions.
* The changes to the src/auth/Auth.cpp file's
  Auth::Private::dataPending function (seen just below the line
  `SafeDataStream str(socket);` below) may look alarming, but it's
  only because of `diff -u` being weird. The only change I backported
  from upstream in this area was to wrap a large block of code in a
  `while (socket->bytesAvailable() > 0) { ... }` block, and fix
  indentation as appropriate. Apparently the extra intendation made
  diff throw a tantrum.

-----

diff -r -u sddm-0.19.0.bak/src/auth/Auth.cpp sddm-0.19.0/src/auth/Auth.cpp
--- sddm-0.19.0.bak/src/auth/Auth.cpp   2024-12-03 20:49:22.259092853 -0600
+++ sddm-0.19.0/src/auth/Auth.cpp       2024-12-03 21:01:03.363190219 -0600
@@ -24,6 +24,7 @@
 #include "SafeDataStream.h"
 
 #include <QtCore/QProcess>
+#include <QtCore/QRegularExpression>
 #include <QtCore/QUuid>
 #include <QtNetwork/QLocalServer>
 #include <QtNetwork/QLocalSocket>
@@ -101,7 +102,7 @@
         static std::unique_ptr<Auth::SocketServer> self;
         if (!self) {
             self.reset(new SocketServer());
-            
self->listen(QStringLiteral("sddm-auth%1").arg(QUuid::createUuid().toString().replace(QRegExp(QStringLiteral("[{}]")),
 QString())));
+            
self->listen(QStringLiteral("sddm-auth%1").arg(QUuid::createUuid().toString().replace(QRegularExpression(QStringLiteral("[{}]")),
 QString())));
         }
         return self.get();
     }
@@ -152,55 +153,57 @@
         Auth *auth = qobject_cast<Auth*>(parent());
         Msg m = MSG_UNKNOWN;
         SafeDataStream str(socket);
-        str.receive();
-        str >> m;
-        switch (m) {
-            case ERROR: {
-                QString message;
-                Error type = ERROR_NONE;
-                str >> message >> type;
-                Q_EMIT auth->error(message, type);
-                break;
-            }
-            case INFO: {
-                QString message;
-                Info type = INFO_NONE;
-                str >> message >> type;
-                Q_EMIT auth->info(message, type);
-                break;
-            }
-            case REQUEST: {
-                Request r;
-                str >> r;
-                request->setRequest(&r);
-                break;
-            }
-            case AUTHENTICATED: {
-                QString user;
-                str >> user;
-                if (!user.isEmpty()) {
-                    auth->setUser(user);
-                    Q_EMIT auth->authentication(user, true);
+        while (socket->bytesAvailable() > 0) {
+            str.receive();
+            str >> m;
+            switch (m) {
+                case ERROR: {
+                    QString message;
+                    Error type = ERROR_NONE;
+                    str >> message >> type;
+                    Q_EMIT auth->error(message, type);
+                    break;
+                }
+                case INFO: {
+                    QString message;
+                    Info type = INFO_NONE;
+                    str >> message >> type;
+                    Q_EMIT auth->info(message, type);
+                    break;
+                }
+                case REQUEST: {
+                    Request r;
+                    str >> r;
+                    request->setRequest(&r);
+                    break;
+                }
+                case AUTHENTICATED: {
+                    QString user;
+                    str >> user;
+                    if (!user.isEmpty()) {
+                        auth->setUser(user);
+                        Q_EMIT auth->authentication(user, true);
+                        str.reset();
+                        str << AUTHENTICATED << environment << cookie;
+                        str.send();
+                    }
+                    else {
+                        Q_EMIT auth->authentication(user, false);
+                    }
+                    break;
+                }
+                case SESSION_STATUS: {
+                    bool status;
+                    str >> status;
+                    Q_EMIT auth->sessionStarted(status);
                     str.reset();
-                    str << AUTHENTICATED << environment << cookie;
+                    str << SESSION_STATUS;
                     str.send();
+                    break;
                 }
-                else {
-                    Q_EMIT auth->authentication(user, false);
+                default: {
+                    Q_EMIT auth->error(QStringLiteral("Auth: Unexpected value 
received: %1").arg(m), ERROR_INTERNAL);
                 }
-                break;
-            }
-            case SESSION_STATUS: {
-                bool status;
-                str >> status;
-                Q_EMIT auth->sessionStarted(status);
-                str.reset();
-                str << SESSION_STATUS;
-                str.send();
-                break;
-            }
-            default: {
-                Q_EMIT auth->error(QStringLiteral("Auth: Unexpected value 
received: %1").arg(m), ERROR_INTERNAL);
             }
         }
     }
diff -r -u sddm-0.19.0.bak/src/greeter/XcbKeyboardBackend.cpp 
sddm-0.19.0/src/greeter/XcbKeyboardBackend.cpp
--- sddm-0.19.0.bak/src/greeter/XcbKeyboardBackend.cpp  2024-12-03 
20:49:22.259092853 -0600
+++ sddm-0.19.0/src/greeter/XcbKeyboardBackend.cpp      2024-12-03 
20:49:35.899166662 -0600
@@ -19,6 +19,7 @@
 
 #include <QtCore/QDebug>
 #include <QtCore/QObject>
+#include <QtCore/QRegularExpression>
 
 #include "KeyboardModel.h"
 #include "KeyboardModel_p.h"
@@ -267,8 +268,7 @@
     }
 
     QList<QString> XcbKeyboardBackend::parseShortNames(QString text) {
-        QRegExp re(QStringLiteral(R"(\+([a-z]+))"));
-        re.setCaseSensitivity(Qt::CaseInsensitive);
+        QRegularExpression re(QStringLiteral(R"(\+([a-z]+))"), 
QRegularExpression::CaseInsensitiveOption);
 
         QList<QString> res;
         QSet<QString> blackList; // blacklist wrong tokens
@@ -276,10 +276,11 @@
 
         // Loop through matched substrings
         int pos = 0;
-        while ((pos = re.indexIn(text, pos)) != -1) {
-            if (!blackList.contains(re.cap(1)))
-                res << re.cap(1);
-            pos += re.matchedLength();
+        QRegularExpressionMatch match;
+        while ((match = re.match(text, pos)).hasMatch()) {
+            if (!blackList.contains(match.captured(1)))
+                res << match.captured(1);
+            pos += match.capturedLength();
         }
         return res;
     }
diff -r -u sddm-0.19.0.bak/src/helper/backend/PamBackend.cpp 
sddm-0.19.0/src/helper/backend/PamBackend.cpp
--- sddm-0.19.0.bak/src/helper/backend/PamBackend.cpp   2024-12-03 
20:49:22.259092853 -0600
+++ sddm-0.19.0/src/helper/backend/PamBackend.cpp       2024-12-03 
20:49:35.899166662 -0600
@@ -26,6 +26,7 @@
 
 #include <QtCore/QString>
 #include <QtCore/QDebug>
+#include <QtCore/QRegularExpression>
 
 #include <stdlib.h>
 
@@ -58,14 +59,14 @@
     AuthPrompt::Type PamData::detectPrompt(const struct pam_message* msg) 
const {
         if (msg->msg_style == PAM_PROMPT_ECHO_OFF) {
             QString message = QString::fromLocal8Bit(msg->msg);
-            if (message.indexOf(QRegExp(QStringLiteral("\\bpassword\\b"), 
Qt::CaseInsensitive)) >= 0) {
-                if 
(message.indexOf(QRegExp(QStringLiteral("\\b(re-?(enter|type)|again|confirm|repeat)\\b"),
 Qt::CaseInsensitive)) >= 0) {
+            if ((QRegularExpression(QStringLiteral("\\bpassword\\b"), 
QRegularExpression::CaseInsensitiveOption)).match(message).hasMatch()) {
+                if 
((QRegularExpression(QStringLiteral("\\b(re-?(enter|type)|again|confirm|repeat)\\b"),
 QRegularExpression::CaseInsensitiveOption)).match(message).hasMatch()) {
                     return AuthPrompt::CHANGE_REPEAT;
                 }
-                else if (message.indexOf(QRegExp(QStringLiteral("\\bnew\\b"), 
Qt::CaseInsensitive)) >= 0) {
+                else if ((QRegularExpression(QStringLiteral("\\bnew\\b"), 
QRegularExpression::CaseInsensitiveOption)).match(message).hasMatch()) {
                     return AuthPrompt::CHANGE_NEW;
                 }
-                else if 
(message.indexOf(QRegExp(QStringLiteral("\\b(old|current)\\b"), 
Qt::CaseInsensitive)) >= 0) {
+                else if 
((QRegularExpression(QStringLiteral("\\b(old|current)\\b"), 
QRegularExpression::CaseInsensitiveOption)).match(message).hasMatch()) {
                     return AuthPrompt::CHANGE_CURRENT;
                 }
                 else {
@@ -153,7 +154,7 @@
     }
 
     Auth::Info PamData::handleInfo(const struct pam_message* msg, bool 
predict) {
-        if 
(QString::fromLocal8Bit(msg->msg).indexOf(QRegExp(QStringLiteral("^Changing 
password for [^ ]+$")))) {
+        if ((QRegularExpression(QStringLiteral("^Changing password for [^ 
]+$"))).match(QString::fromLocal8Bit(msg->msg)).hasMatch()) {
             if (predict)
                 m_currentRequest = Request(changePassRequest);
             return Auth::INFO_PASS_CHANGE_REQUIRED;

-----

-- System Information:
Debian Release: 12.8
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable-security'), (500, 'stable')
Architecture: amd64 (x86_64)

Kernel: Linux 6.1.0-28-amd64 (SMP w/2 CPU threads; PREEMPT)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages sddm depends on:
ii  adduser                 3.134
ii  debconf [debconf-2.0]   1.5.82
ii  libc6                   2.36-9+deb12u9
ii  libgcc-s1               12.2.0-14
ii  libpam0g                1.5.2-6+deb12u1
ii  libqt5core5a            5.15.8+dfsg-11+deb12u2
ii  libqt5dbus5             5.15.8+dfsg-11+deb12u2
ii  libqt5gui5              5.15.8+dfsg-11+deb12u2
ii  libqt5network5          5.15.8+dfsg-11+deb12u2
ii  libqt5qml5              5.15.8+dfsg-3
ii  libqt5quick5            5.15.8+dfsg-3
ii  libstdc++6              12.2.0-14
ii  libsystemd0             252.31-1~deb12u1
ii  libxcb-xkb1             1.15-1
ii  libxcb1                 1.15-1
ii  qml-module-qtquick2     5.15.8+dfsg-3
ii  x11-common              1:7.7+23
ii  xauth                   1:1.1.2-1
ii  xserver-xorg [xserver]  1:7.7+23

Versions of packages sddm recommends:
ii  libpam-systemd                         252.31-1~deb12u1
ii  sddm-theme-debian-elarun [sddm-theme]  0.19.0-5

Versions of packages sddm suggests:
pn  libpam-kwallet5           <none>
pn  qtvirtualkeyboard-plugin  <none>

-- debconf information:
  sddm/daemon_name: /usr/bin/sddm
* shared/default-x-display-manager: sddm

Attachment: pgpOckW53Y66D.pgp
Description: OpenPGP digital signature

Reply via email to