Skip to content

Conversation

@calsys456
Copy link
Contributor

@calsys456 calsys456 commented Jan 14, 2026

The original design is wrong: pam_open_session() should only be called inside child process. Move it into childModifier (after fork()), and execve manually to insert environ into the child process.

The internal API is also changed to adapt the new code structure. The Pam.cpp is merged into Auth.cpp, and some change happened in Display.cpp

Summary by Sourcery

Rework authentication and session handling to open PAM sessions only in the child process, integrate PAM directly into Auth, and adjust display/session management accordingly.

Bug Fixes:

  • Fix inability to handle multiple user logins by correctly scoping PAM sessions to the per-session child process and cleaning up per-auth state on logout.

Enhancements:

  • Inline PAM handling into Auth, introducing a dedicated PAM conversation and lifecycle management with AuthPrivate.
  • Revise session startup so Auth both opens the logind session and launches the user process, tracking session PID and ID via logind.
  • Improve utmp login/logout handling to use cached TTY and session process IDs and ensure cleanup on Auth destruction.
  • Simplify Display and UserSession ownership and cleanup by deleting Auth instances on session end and on logout, and by removing the separate userProcessFinished slot in Display.
  • Update UserSession to open the PAM session inside childModifier, set up the VT based on Auth, and exec the user process with the PAM-provided environment.

Build:

  • Remove Pam.cpp from the daemon build as PAM logic is now implemented inside Auth.

Documentation:

  • Update file headers and licensing comments in Auth to the current project copyright and SPDX identifier.

The original design is wrong: pam_open_session() should only be called
inside child process. Move it into childModifier (after fork()), and
execve manually to insert environ into the child process.

The internal API is also changed to adapt the new code structure. The
Pam.cpp is merged into Auth.cpp, and some change happened in Display.cpp
@deepin-ci-robot
Copy link

Hi @calsys456. Thanks for your PR.

I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 14, 2026

Reviewer's Guide

Refactors PAM handling so that pam_open_session is executed only in the child process, inlines the former Pam wrapper into Auth, and adjusts session/login lifecycle management across Auth, Display, and UserSession to correctly handle multiple concurrent logins and PAM environments.

Sequence diagram for the new multi-user login and session flow

sequenceDiagram
    actor User
    participant Display
    participant Auth
    participant UserSession
    participant PAM
    participant Logind
    participant VirtualTerminal

    User->>Display: requestLogin(user, password)
    Display->>Display: findOrCreateAuth(user)
    Display->>Auth: authenticate(secret)
    Auth->>PAM: pam_start(service ddm, user, conv)
    Auth->>PAM: pam_authenticate(handle, 0)
    PAM-->>Auth: PAM_SUCCESS
    Auth->>PAM: pam_acct_mgmt(handle, 0)
    PAM-->>Auth: PAM_SUCCESS
    Auth->>Auth: authenticated = true
    Auth-->>Display: true

    Display->>Auth: openSession(command, env, cookie)
    Auth->>UserSession: setProcessEnvironment(env)
    Auth->>UserSession: start(command, type, cookie)
    UserSession-->>Auth: processStarted
    Auth->>Auth: sessionOpened = true

    par Parent_process
        Auth->>VirtualTerminal: path(tty)
        Auth->>Auth: sessionProcessId = m_session.processId()
        Auth->>PAM: pam_handle still valid
        Auth->>Logind: GetSessionByPID(sessionProcessId)
        Logind-->>Auth: sessionObjectPath
        Auth->>Logind: read property Id
        Logind-->>Auth: xdgSessionId
        Auth->>Auth: xdgSessionId stored
        Auth-->>Display: xdgSessionId
        Display->>Display: auths.append(auth)
    and Child_process_fork
        UserSession->>Auth: openSessionInternal(processEnvironment)
        Auth->>PAM: pam_setcred(handle, PAM_ESTABLISH_CRED)
        Auth->>PAM: pam_set_item(PAM_TTY, VirtualTerminal.path(tty))
        Auth->>PAM: pam_set_item(PAM_XDISPLAY, DISPLAY)
        Auth->>PAM: pam_putenv(handle, env vars)
        Auth->>PAM: pam_open_session(handle, 0)
        PAM-->>Auth: PAM_SUCCESS
        Auth-->>UserSession: char** pamEnv
        UserSession->>VirtualTerminal: jumpToVt(tty, false)
        UserSession->>UserSession: setupNamespacesAndUser()
        UserSession->>UserSession: setupStdioAndLogs()
        UserSession->>UserSession: execve(program, args, pamEnv)
    end

    UserSession-->>Auth: userProcessFinished(status)
    Auth-->>Display: userProcessFinished(status)
    Display->>Display: auths.remove(auth)
    Display->>Auth: delete
    Auth->>PAM: pam_close_session(handle, 0)
    Auth->>PAM: pam_setcred(handle, PAM_DELETE_CRED)
    Auth->>PAM: pam_end(handle, ret)
    PAM-->>Auth: cleanup complete
Loading

Class diagram for updated authentication and session management

classDiagram
    class Auth {
        +bool authenticated
        +bool sessionOpened
        +QString user
        +QString display
        +int type
        +int tty
        +int xdgSessionId
        +qint64 sessionProcessId
        +int sessionId
        +Auth(QObject* parent, QString user)
        +~Auth()
        +bool authenticate(QByteArray secret)
        +int openSession(QString command, QProcessEnvironment env, QByteArray cookie)
        +bool closeSession()
        +char** openSessionInternal(QProcessEnvironment sessionEnv)
        +void userProcessFinished(int status)
        -UserSession* m_session
        -AuthPrivate* d
    }

    class AuthPrivate {
        +AuthPrivate(Auth* parent)
        +~AuthPrivate()
        +pam_handle_t* handle
        +const char** secretPtr
        +pam_conv conv
        +int ret
    }

    class UserSession {
        +UserSession(Auth* parent)
        +void start(QString command, Display_DisplayServerType type, QByteArray cookie)
        +void stop()
        -void childModifier()
    }

    class Display {
        +Display(QObject* parent)
        +~Display()
        +void stop()
        +void login(QLocalSocket* socket, QString user, QString password)
        +void logout(QLocalSocket* socket, int id)
        +void handleSocket(QLocalSocket* socket)
        -QList~Auth*~ auths
        -SocketServer* m_socketServer
    }

    class VirtualTerminal {
        +static int setUpNewVt()
        +static QString path(int vt)
        +static void jumpToVt(int vt, bool nowait)
        +static void setVtSignalHandler(void* handler, void* data)
    }

    class OrgFreedesktopLogin1ManagerInterface {
        +QDBusObjectPath GetSessionByPID(uint pid)
        +void TerminateSession(QString id)
    }

    class OrgFreedesktopLogin1SessionInterface {
        +QVariant property(QString name)
    }

    Display "1" o-- "*" Auth
    Auth "1" o-- "1" UserSession
    Auth "1" *-- "1" AuthPrivate
    UserSession --> Auth
    Auth ..> OrgFreedesktopLogin1ManagerInterface
    Auth ..> OrgFreedesktopLogin1SessionInterface
    Auth ..> VirtualTerminal
    UserSession ..> VirtualTerminal
Loading

File-Level Changes

Change Details Files
Inline PAM handling into Auth and introduce explicit PAM/session lifecycle management.
  • Merge previous Pam wrapper into Auth by adding PAM conversation, pam_start/authenticate/acct_mgmt handling directly in Auth.cpp.
  • Introduce AuthPrivate to hold pam_handle_t, conversation state, and return codes, and manage PAM cleanup in Auth destructor.
  • Replace Auth::active with authenticated and sessionOpened flags plus sessionProcessId tracking, and ensure utmp login/logout entries are written using cached PID.
  • Change authenticate() to run pam_start, pam_authenticate, and pam_acct_mgmt with a password-based conversation handler and proper error handling.
  • Add closeSession() to call pam_close_session and pam_setcred(PAM_DELETE_CRED), and openSessionInternal() to set PAM items, push env via pam_putenv, call pam_open_session, and return pam_getenvlist().
src/daemon/Auth.cpp
src/daemon/Auth.h
Change the session-opening flow to combine starting the user process and obtaining XDG_SESSION_ID via logind.
  • Replace old openSession(env)/startUserProcess(command, cookie) split with a single openSession(command, env, cookie) that sets HOME/SHELL/USER/LOGNAME, starts UserSession, writes utmp login, queries logind for the session by PID, and stores xdgSessionId.
  • Ensure Auth destructor stops an active UserSession, closes the PAM session if opened, logs logout in utmp, and calls pam_end.
  • Switch Display to use authenticated and sessionOpened flags instead of active, and to manage Auth lifetimes explicitly via delete and destroyed() connections.
src/daemon/Auth.cpp
src/daemon/Auth.h
src/daemon/Display.cpp
src/daemon/Display.h
Move pam_open_session and environment handling into the child process via UserSession::childModifier, and exec the session command manually with PAM-provided envp.
  • In UserSession::childModifier, call Auth::openSessionInternal(processEnvironment()) in the child to establish PAM credentials, set PAM_TTY/PAM_XDISPLAY, push session env into PAM, and open the PAM session.
  • Use the Auth instance as parent to access tty, session type, and username; derive VT path from auth->tty instead of reading XDG_VTNR again.
  • Adjust VT handling so non-X11 sessions open and possibly take control of the VT, then always jumpToVt(auth->tty, false) before continuing.
  • After switching user and setting up stdio and logging, build argv from QProcess program/arguments and call execve with the PAM environment returned from openSessionInternal instead of letting QProcess exec on its own.
  • Remove unused cachedProcessId and related helper types from UserSession, and simplify includes.
src/daemon/UserSession.cpp
src/daemon/UserSession.h
Update Display’s session and Auth lifecycle management to support multiple users and clean teardown.
  • In Display::start/stop, replace calls to Auth::stop() and the userProcessFinished slot with explicit disconnection, deletion of Auth instances, and removal from the auths list.
  • On new login, connect Auth::destroyed to a lambda that removes the session from DisplayManager, and connect Auth::userProcessFinished to a lambda that logs completion, removes Auth from auths, and deletes it.
  • In logout(), find the Auth with matching xdgSessionId, disconnect its signals, remove from auths, delete it, and then ask logind to terminate the session.
  • Adjust code paths that referenced auth->user assignment and active flag to rely on constructor-provided user and authenticated flag instead.
src/daemon/Display.cpp
src/daemon/Display.h
Remove the old Pam wrapper and update build configuration.
  • Delete Pam.cpp and Pam.h and remove them from the daemon CMake source list.
  • Update Auth and UserSession includes to no longer reference the Pam class.
src/daemon/CMakeLists.txt
src/daemon/Pam.cpp
src/daemon/Pam.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 7 issues, and left some high level feedback:

  • In Display::logout you modify auths (removeAll/delete) while iterating it with for (Auth *auth : std::as_const(auths)), which is undefined for Qt containers; consider iterating by index or using an iterator-based loop where you adjust the iterator after removal.
  • In Display::stop you delete Auth objects inside the loop but leave their dangling pointers in auths; it would be safer to clear or rebuild the list after deletion (e.g. call auths.clear() once done, or remove each pointer as you delete it) to avoid accidental reuse.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In Display::logout you modify `auths` (removeAll/delete) while iterating it with `for (Auth *auth : std::as_const(auths))`, which is undefined for Qt containers; consider iterating by index or using an iterator-based loop where you adjust the iterator after removal.
- In Display::stop you delete `Auth` objects inside the loop but leave their dangling pointers in `auths`; it would be safer to clear or rebuild the list after deletion (e.g. call `auths.clear()` once done, or remove each pointer as you delete it) to avoid accidental reuse.

## Individual Comments

### Comment 1
<location> `src/daemon/Auth.cpp:125-134` </location>
<code_context>
+    static int converse(int num_msg,
</code_context>

<issue_to_address>
**issue (bug_risk):** The PAM conversation handler uses an unsafe cast that breaks const-correctness and type safety for the secret pointer.

In `converse`, `secret_ptr` is passed as `static_cast<void *>(secretPtr)` with `secretPtr` declared as `const char **`, but later cast back to `char **` and dereferenced:

```c++
resp[i]->resp = strdup(*static_cast<char **>(secret_ptr));
```

This breaks const-correctness and is undefined behavior because you treat a `const char *` as `char *`. Please align the types instead of casting through `char **` (e.g., make the stored pointer non-const in a controlled way, or wrap the secret in a struct and pass a pointer to that struct) so the PAM callback can access it without UB.
</issue_to_address>

### Comment 2
<location> `src/daemon/Auth.cpp:205-214` </location>
<code_context>
     }

     Auth::~Auth() {
-        stop();
+        if (m_session->state() != QProcess::NotRunning)
+            m_session->stop();
+        if (sessionOpened)
+            closeSession();
+        // write logout to utmp/wtmp
+        if (sessionProcessId > 0)
+            utmpLogout(std::to_string(tty).c_str(), display, sessionProcessId);
+        if (d->handle) {
+            d->ret = pam_end(d->handle, d->ret);
+            if (d->ret != PAM_SUCCESS)
+                qWarning() << "[Auth] end:" << pam_strerror(d->handle, d->ret);
</code_context>

<issue_to_address>
**issue (bug_risk):** Using the PAM handle after pam_end() is called can be unsafe when logging errors.

In the destructor, `pam_end(d->handle, d->ret)` may free or invalidate `d->handle`, so using it in `pam_strerror(d->handle, d->ret)` is not guaranteed safe. Capture `d->ret` before calling `pam_end`, then use `pam_strerror(nullptr, ret)` (supported by Linux-PAM) or log the numeric code instead. Also set `d->handle = nullptr` after `pam_end` to prevent accidental reuse.
</issue_to_address>

### Comment 3
<location> `src/daemon/Auth.cpp:252-259` </location>
<code_context>
-    void Auth::startUserProcess(const QString &command, const QByteArray &cookie) {
-        Q_ASSERT(!m_env.isEmpty());
-        QProcessEnvironment env = m_env;
+    int Auth::openSession(const QString &command,
+                          QProcessEnvironment env,
+                          const QByteArray &cookie) {
</code_context>

<issue_to_address>
**issue (bug_risk):** Ignoring the result of waitForStarted() can lead to utmp/logind state being updated for a session that never started.

Here you call:

```c++
m_session->start(command, type, cookie);
m_session->waitForStarted();
sessionOpened = true;
sessionProcessId = m_session->processId();
utmpLogin(..., sessionProcessId, true);
```

If `waitForStarted()` fails, `processId()` may be invalid (0 or -1), yet `sessionOpened` is set, utmp/wtmp is updated, and later code will try to close a session that never started. Please check the return value of `waitForStarted()` and/or `m_session->state() == QProcess::Running` and abort early (e.g., return a non‑positive `xdgSessionId`) when startup fails.
</issue_to_address>

### Comment 4
<location> `src/daemon/UserSession.cpp:132-141` </location>
<code_context>
-        QString sessionType = processEnvironment().value(QStringLiteral("XDG_SESSION_TYPE"));
-        const bool waylandUserSession = sessionType == QLatin1String("wayland");
+        // Open session
+        Auth *auth = qobject_cast<Auth *>(parent());
+        char **env = auth->openSessionInternal(processEnvironment());
+        if (!env) {
</code_context>

<issue_to_address>
**🚨 issue (security):** execve() failures are not handled, which can leave the child running unexpected code with modified privileges.

In `childModifier` the direct `execve` call:

```c++
execve(qPrintable(program), c_args.data(), env);
```

means that on failure the function returns and the child continues in `childModifier`/QProcess internals, potentially after user, namespaces, FDs, or PAM session state have already been changed. This can leave a semi-privileged helper executing an unintended path.

Please handle failure explicitly and terminate the child, for example:

```c++
execve(...);
perror("execve");
_exit(1);
```

so that no further code runs in a misconfigured child process.
</issue_to_address>

### Comment 5
<location> `src/daemon/Auth.cpp:174` </location>
<code_context>
     // Auth implementation //
     /////////////////////////

+    class AuthPrivate : public QObject {
+        Q_OBJECT
+    public:
</code_context>

<issue_to_address>
**issue (complexity):** Consider simplifying the new PAM integration by making AuthPrivate a plain struct, passing it directly to the PAM conversation, and replacing error-handling macros with small helper functions to make control flow clearer.

You can keep all the new PAM behavior while reducing complexity and indirection quite a bit.

### 1. Drop `QObject` + heap `secretPtr` in `AuthPrivate`

`AuthPrivate` doesn’t use signals/slots and only holds PAM-related state. You can make it a plain struct, store the secret directly, and pass the whole struct as `appdata_ptr` to the conversation function.

```cpp
// Before
class AuthPrivate : public QObject {
    Q_OBJECT
public:
    AuthPrivate(Auth *parent)
        : QObject(parent)
        , secretPtr(new const char *)
        , conv({ converse, static_cast<void *>(secretPtr) }) {
        *secretPtr = nullptr;
    }

    ~AuthPrivate() {
        delete secretPtr;
    }

    pam_handle_t *handle{ nullptr };
    const char **secretPtr{};
    pam_conv conv{};
    int ret{};
};
```

```cpp
// After (no QObject, no heap pointer)
struct AuthPrivate {
    pam_handle_t *handle = nullptr;
    pam_conv conv{};
    QByteArray secret;   // owns password bytes
    int ret = 0;

    AuthPrivate() {
        conv.conv = &converse;
        conv.appdata_ptr = this;
    }
};
```

Then in `Auth`:

```cpp
Auth::Auth(QObject *parent, QString user)
    : QObject(parent)
    , user(std::move(user))
    , m_session(new UserSession(this))
    , d(new AuthPrivate{})
{
    // unchanged signal connection
}
```

### 2. Simplify `converse()` to use `AuthPrivate` directly

Instead of `void *secret_ptr` → `char **` → pointer into `QByteArray`, just use the `AuthPrivate` and its `QByteArray secret`.

```cpp
// Before
static int converse(int num_msg,
                    const struct pam_message **msg,
                    struct pam_response **resp,
                    void *secret_ptr) {
    ...
    case PAM_PROMPT_ECHO_OFF:
        resp[i]->resp = strdup(*static_cast<char **>(secret_ptr));
        ...
}
```

```cpp
// After
static int converse(int num_msg,
                    const struct pam_message **msg,
                    struct pam_response **resp,
                    void *appdata_ptr)
{
    auto *d = static_cast<AuthPrivate *>(appdata_ptr);

    *resp = static_cast<pam_response *>(
        calloc(num_msg, sizeof(struct pam_response)));
    if (!*resp)
        return PAM_BUF_ERR;

    for (int i = 0; i < num_msg; ++i) {
        switch (msg[i]->msg_style) {
        case PAM_PROMPT_ECHO_OFF:
            // use owned QByteArray
            resp[i]->resp = ::strdup(d->secret.constData());
            resp[i]->resp_retcode = 0;
            break;
        ...
        }
    }
    return PAM_SUCCESS;
}
```

And in `authenticate()`:

```cpp
bool Auth::authenticate(const QByteArray &secret)
{
    Q_ASSERT(!user.isEmpty());

    qDebug() << "[Auth] Starting...";
    d->ret = pam_start("ddm", user.toLocal8Bit().constData(), &d->conv, &d->handle);
    if (!checkAuthRet("start"))
        return false;

    qDebug() << "[Auth] Authenticating user" << user;

    d->secret = secret;              // store on d
    d->ret = pam_authenticate(d->handle, 0);
    d->secret.clear();               // clear after use

    if (!checkAuthRet("authenticate"))
        return false;

    d->ret = pam_acct_mgmt(d->handle, 0);
    if (!checkAuthRet("acct_mgmt"))
        return false;

    authenticated = true;
    return true;
}
```

### 3. Replace macros with small helpers

The `CHECK_RET_*` macros hide control flow and depend on mutable `d->ret`. You can preserve the same behavior with small helpers; this makes it easier to see what each call does and avoids macro pitfalls.

```cpp
// Before
#define CHECK_RET_AUTH \
    if (d->ret != PAM_SUCCESS) { \
        qWarning() << "[Auth] Authenticate:" << pam_strerror(d->handle, d->ret); \
        utmpLogin(std::to_string(tty).c_str(), display, user, 0, false); \
        return false; \
    }
```

```cpp
// After
bool Auth::checkAuthRet(const char *context)
{
    if (d->ret == PAM_SUCCESS)
        return true;

    qWarning() << "[Auth]" << context << ":" << pam_strerror(d->handle, d->ret);
    utmpLogin(std::to_string(tty).c_str(), display, user, 0, false);
    return false;
}
```

Use similarly focused helpers for `closeSession` and `openSessionInternal`:

```cpp
bool Auth::checkCloseRet(const char *context)
{
    if (d->ret == PAM_SUCCESS)
        return true;

    qWarning() << "[Auth] closeSession(" << context << "):"
               << pam_strerror(d->handle, d->ret);
    return false;
}

bool Auth::checkOpenRet(const char *context)
{
    if (d->ret == PAM_SUCCESS)
        return true;

    qWarning() << "[Auth] openSessionInternal(" << context << "):"
               << pam_strerror(d->handle, d->ret);
    return false;
}
```

Usage example in `closeSession()`:

```cpp
bool Auth::closeSession()
{
    if (!sessionOpened) {
        qWarning() << "[Auth] closeSession: Session didn't opened.";
        return true;
    }

    qDebug() << "[Auth] Closing session for user" << user;

    d->ret = pam_close_session(d->handle, 0);
    if (!checkCloseRet("close_session"))
        return false;

    sessionOpened = false;

    d->ret = pam_setcred(d->handle, PAM_DELETE_CRED);
    if (!checkCloseRet("setcred_delete"))
        return false;

    qDebug() << "[Auth] Session closed.";
    return true;
}
```

### 4. Keep `d->ret` but limit its scope per operation

If you don’t want to fully remove `d->ret` yet, the helpers above at least make each logical operation’s use explicit and keep error handling localized:

- `checkAuthRet` used only in `authenticate`.
- `checkOpenRet` used only in `openSessionInternal`.
- `checkCloseRet` used only in `closeSession`.

This keeps current behavior intact while reducing indirection (`QObject`, heap pointer, `void *` gymnastics) and hiding of control flow (macros), making the PAM integration easier to follow and maintain.
</issue_to_address>

### Comment 6
<location> `src/daemon/Auth.h:23` </location>
<code_context>

-        /** Indicates whether the Auth is active (PAM module started) */
-        bool active{ false };
+        /** Indicates whether authenticated (authenticate() is called and succeed) */
+        bool authenticated{ false };
+
</code_context>

<issue_to_address>
**issue (complexity):** Consider hiding internal state flags and PAM helper methods behind private members and accessors to keep the Auth public API small and cohesive.

You can keep the new behavior while reducing API complexity by tightening encapsulation and pushing the “state machine” and PAM details behind the class boundary.

### 1. Hide raw state flags from the public API

Instead of exposing `authenticated`, `sessionOpened`, `xdgSessionId`, and `sessionProcessId` as mutable public fields, keep them in `AuthPrivate` (or private members) and expose read-only accessors. This keeps the protocol internal while still allowing callers to query state.

**Header:**

```cpp
class Auth : public QObject {
    Q_OBJECT
public:
    Auth(QObject *parent, QString user);
    ~Auth();

    bool isAuthenticated() const;
    bool isSessionOpened() const;
    int xdgSessionId() const;      // Positive ⇒ valid
    qint64 sessionProcessId() const; // Positive ⇒ valid

    // ... rest unchanged ...

private:
    UserSession *m_session{ nullptr };
    AuthPrivate *d{ nullptr };
};
```

**Impl (`Auth.cpp`):**

```cpp
// in AuthPrivate:
struct AuthPrivate {
    bool authenticated = false;
    bool sessionOpened = false;
    int xdgSessionId = 0;
    qint64 sessionProcessId = -1;
};

// in Auth methods:
bool Auth::isAuthenticated() const   { return d->authenticated; }
bool Auth::isSessionOpened() const   { return d->sessionOpened; }
int Auth::xdgSessionId() const       { return d->xdgSessionId; }
qint64 Auth::sessionProcessId() const{ return d->sessionProcessId; }
```

Then update `Auth` internals and `Display.cpp` (or other users) to use the accessors instead of touching the fields directly. This keeps the state machine (authenticate → openSession → closeSession) fully under `Auth`’s control and lets you change the protocol later without breaking callers.

### 2. Keep `openSessionInternal` out of the public surface

`openSessionInternal` is clearly a low-level PAM helper. You can maintain current behavior while not advertising it as part of the public API.

If only `UserSession` needs it:

**Header:**

```cpp
class Auth : public QObject {
    Q_OBJECT
public:
    // ... existing public API (authenticate, openSession, closeSession) ...

Q_SIGNALS:
    void userProcessFinished(int status);

private:
    friend class UserSession; // if you really must let UserSession call it

    // Low-level PAM helper, not part of public API
    char **openSessionInternal(const QProcessEnvironment &sessionEnv);

    UserSession *m_session{ nullptr };
    AuthPrivate *d{ nullptr };
};
```

This reduces surface area: general callers only see `authenticate`, `openSession(...)`, and `closeSession()`. Only `UserSession` (which already “knows” about PAM/process details) can call the low-level helper. If you later want a cleaner split, you can move `openSessionInternal` into `AuthPrivate` without further API breakage.

If you prefer to avoid `friend`, you can instead add a small, higher-level method tailored for `UserSession` while keeping the PAM specifics inside `Auth`:

```cpp
// in Auth.h
public:
    // Higher-level interface for child setup
    bool setupPamEnvironmentForChild(char ***envp,
                                     const QProcessEnvironment &sessionEnv);

// in Auth.cpp
bool Auth::setupPamEnvironmentForChild(char ***envp,
                                       const QProcessEnvironment &sessionEnv)
{
    char **list = openSessionInternal(sessionEnv);
    if (!list)
        return false;
    *envp = list;
    return true;
}
```

Then `openSessionInternal` can be `private`, and `UserSession` calls `setupPamEnvironmentForChild(...)` instead of dealing with PAM directly.

These two changes keep all new capabilities (combined openSession, session PID, PAM env handling) but present a much smaller, cohesive public API.
</issue_to_address>

### Comment 7
<location> `src/daemon/UserSession.cpp:130` </location>
<code_context>
     }

     void UserSession::childModifier() {
-        // Session type
-        QString sessionType = processEnvironment().value(QStringLiteral("XDG_SESSION_TYPE"));
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting the new PAM session handling, VT setup, and execve logic from childModifier() into small helper functions so the method becomes a clear orchestration of steps.

The added responsibilities in `childModifier()` (PAM session + env handling + manual `execve`) do increase its complexity. You can keep all behavior but make the function easier to read and maintain by extracting a few focused helpers.

For example:

1. **Isolate PAM session + env handling**

```c++
char **UserSession::openPamSessionEnv()
{
    auto *auth = qobject_cast<Auth *>(parent());
    Q_ASSERT(auth);

    char **env = auth->openSessionInternal(processEnvironment());
    if (!env) {
        qCritical() << "Failed to open PAM session in user session process";
        _exit(1);
    }
    return env;
}
```

Then at the top of `childModifier()`:

```c++
void UserSession::childModifier()
{
    Auth *auth = qobject_cast<Auth *>(parent());
    Q_ASSERT(auth);

    char **env = openPamSessionEnv();
    // ...
}
```

2. **Extract VT preparation**

```c++
void UserSession::prepareSessionVt(Auth *auth)
{
    if (auth->type == Display::X11) {
        return;
    }

    const QString ttyString = VirtualTerminal::path(auth->tty);
    const int vtFd = ::open(qPrintable(ttyString), O_RDWR | O_NOCTTY);

    bool takeControl = false;
    if (vtFd > 0) {
        dup2(vtFd, STDIN_FILENO);
        ::close(vtFd);
        takeControl = true;
    } else {
        qWarning().nospace() << "Unable to open " << ttyString
                             << " for wayland session (" << strerror(errno)
                             << ") – falling back to current stdin";
    }

    if (takeControl) {
        if (ioctl(STDIN_FILENO, TIOCSCTTY, 0) == -1) {
            qCritical().nospace() << "Failed to take control of " << ttyString
                                  << " (" << QFileInfo(ttyString).owner()
                                  << "): " << strerror(errno);
            _exit(1);
        }
        if (ioctl(STDIN_FILENO, KDSKBMODE, K_OFF) == -1) {
            qCritical().nospace() << "Failed to set keyboard mode to K_OFF";
            _exit(1);
        }
    }

    VirtualTerminal::jumpToVt(auth->tty, false);
}
```

Then in `childModifier()`:

```c++
void UserSession::childModifier()
{
    Auth *auth = qobject_cast<Auth *>(parent());
    Q_ASSERT(auth);

    char **env = openPamSessionEnv();
    prepareSessionVt(auth);

    // existing namespace + user switching + logging code...
}
```

3. **Separate exec + argv/env building**

```c++
static void execWithPamEnv(const QString &program,
                           const QStringList &args,
                           char **env)
{
    QList<char *> c_args;
    c_args.push_back(strdup(qPrintable(program)));
    for (const QString &arg : args) {
        c_args.push_back(strdup(qPrintable(arg)));
    }
    c_args.push_back(nullptr);

    execve(qPrintable(program), c_args.data(), env);
    // If execve returns, it's an error
    qCritical() << "execve failed:" << strerror(errno);
    _exit(1);
}
```

And at the end of `childModifier()`:

```c++
    // after stdio redirection
    execWithPamEnv(program(), arguments(), env);
```

These small helpers keep the behavior you introduced (PAM session env, VT leak decision based on `auth->type`, manual `execve`), but reduce `childModifier()` to a readable orchestration of clearly named steps, without reverting any of your changes.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical bug where multiple users could not log in concurrently by restructuring PAM session management. The key change is moving pam_open_session() calls from the parent process into the child process (after fork()), which is the correct PAM usage pattern for multi-user scenarios.

Changes:

  • Merged Pam.cpp/Pam.h into Auth.cpp/Auth.h, making Auth directly handle PAM lifecycle
  • Modified UserSession to call openSessionInternal() in childModifier() (post-fork) and manually execve() with PAM environment
  • Updated Display to delete Auth instances on session end/logout and use Auth for both logind session management and process launching
  • Enhanced systemd service capabilities (added CAP_AUDIT_* and CAP_SYS_RESOURCE) and removed some security hardening options

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/daemon/Auth.h Updated interface: removed stop(), added openSessionInternal() for child process, changed openSession() to launch process and return session ID
src/daemon/Auth.cpp Merged PAM conversation and lifecycle from Pam.cpp, restructured to support fork-then-open-session pattern
src/daemon/UserSession.h Removed unnecessary forward declarations and cached PID field
src/daemon/UserSession.cpp Added PAM session opening in childModifier, manual execve with PAM environment
src/daemon/Display.h Removed userProcessFinished slot
src/daemon/Display.cpp Simplified Auth lifecycle: delete on logout/session-end, integrated session opening into login flow
src/daemon/Pam.h Deleted (functionality moved to Auth)
src/daemon/Pam.cpp Deleted (functionality moved to Auth)
src/daemon/CMakeLists.txt Removed Pam.cpp from build
services/ddm.service.in Added audit capabilities, removed ProtectKernelTunables and RestrictSUIDSGID hardening
REUSE.toml Removed Pam.cpp/Pam.h from license annotations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@calsys456 calsys456 force-pushed the master branch 4 times, most recently from a6c2e3f to 1b8049c Compare January 14, 2026 08:55
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: calsys456, zccrs

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@wineee wineee merged commit a51b0ba into linuxdeepin:master Jan 15, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants