Package: libpam-fscrypt
Version: 0.3.7-1
Severity: important
X-Debbugs-Cc: [email protected]

Dear Maintainer,
I could describe the bug, but since it is a complex topic for me, I prefer to share the analysis performed using local AI; it goes into detail and provides
an alternative solution as well as guidance on how to fix the issue.
For now, to resolve the issue, I removed the libpam-fscrypt package.

Thanks,
Antonio


-- Description: --


`pam_fscrypt.so`, when enabled in the PAM session stack (`/etc/pam.d/common-
session`, as shipped by the Debian packaging of `libpam-fscrypt`), enumerates
mount points taken from `/proc/self/mountinfo` and calls `stat()` on them
during `pam_sm_open_session()`.

If any of those mount points is a FUSE mount whose daemon is alive but not
serving requests, that `stat()` never returns: the calling thread goes into
uninterruptible sleep (`D`, `wchan=fuse_get_req`). Because PAM session opening is *in-line* with the SSH connection setup in `sshd-session`, this blocks the SSH connection itself — and, from then on, every subsequent SSH login on that
host.

The trigger is completely ordinary: mounting an `sshfs` share whose SSH server runs on the same host (or in the same mount namespace) as the FUSE client. In that situation `sshfs` creates the mount point *before* its SFTP handshake has
completed, so the new mount point exists but cannot be served; meanwhile the
daemon is waiting for data on the very SSH connection whose session setup is
now blocked in `stat()` on that mount point.
Neither side can progress and there is no timeout anywhere in the path: a
textbook circular wait.

Note that this is *not* an sshfs bug and not an SFTP problem: plain `ssh`,
`sftp` and a manually invoked `sftp-server` all work correctly on the same
machine.


-- Steps to reproduce --

Prerequisites: Debian forky/sid, `libpam-fscrypt 0.3.7-1`, `openssh-server
1:10.5p1-1`, `sshfs 3.7.3-1.2`, `fuse3 3.18.3-1`; sshd listening on port 22
(any port); key-based auth for root; `session optional pam_fscrypt.so` enabled
in `/etc/pam.d/common-session`.

  1. As root: `sshfs -p 22 root@localhost:/ /mnt`
     -> the command never returns and prints nothing (expected: instant mount).
  2. In another terminal, as root or any user:
     `timeout -s KILL 15 ssh -p 22 -o BatchMode=yes root@localhost 'echo hi'`      -> "hi" is never printed; the command is killed by the timeout (rc=137).
        (expected: instant output)
  3. `ps -eo pid,state,comm,wchan:30 | awk '$2=="D"'`
     -> one or more `sshd-session` processes stuck in `D`,
`wchan=fuse_get_req`.

Recovery requires killing the FUSE daemon from a local console (or an already- open session); SIGTERM/SIGKILL do not remove the tasks while they are in `D`:

  # kill -9 <sshfs pid>       # requests then fail immediately with ENOTCONN
  # umount -l /mnt


-- Actual result --

`sshfs` hangs indefinitely; every subsequent SSH login hangs in PAM session
open; the hung tasks are unkillable until the FUSE daemon disappears.

-- Expected result --

PAM session opening must never block on a filesystem it does not need.
`fscrypt` has no reason to `stat()` arbitrary mount points synchronously inside
sshd's session phase, and certainly not without any protection against non-
responding filesystems.


-- Impact --

* Any local/loopback `sshfs` (or other FUSE) usage can silently render the SSH service of   a host unusable: logins hang after authentication, with no error
message anywhere.
* Because PAM session open is in-line, sshd's connection slots and
`MaxStartups` fill up   with hung sessions.
* The condition is self-perpetuating: each failed attempt leaves another dead
mount point   in `/proc/self/mountinfo`, which hangs the next login as well.
* Recovery needs local console access; on a headless box this is a hard lockout
(hence severity important rather than normal).


-- Workaround used --

Commenting out the module in `/etc/pam.d/common-session`:

    -session    optional        pam_fscrypt.so
    +#session   optional        pam_fscrypt.so

No sshd restart is needed (PAM configuration is read per authentication). After
this, `sshfs -p 22 root@localhost:/ /mnt` mounts instantly and unmounts
cleanly. This is safe on the affected machine because fscrypt was never
provisioned there:

    $ sudo fscrypt status
    filesystems supporting encryption: 0
    filesystems with fscrypt metadata: 0
    [ERROR] fscrypt status: filesystem / is not setup for use with fscrypt

i.e. there are no fscrypt policies to unlock at login, so the module was doing
nothing except hanging logins.


-- Proposed fix --

In `pam_sm_open_session()` (and in any mount-table walk in the library):

  * Do not `stat()` mount points of filesystem types that cannot carry an
fscrypt policy (`fuse`, `fuse.sshfs`, network filesystems, ...) — the
information needed is already available as text in `/proc/self/mountinfo`.
  * If a stat is unavoidable, make it non-blocking / interruptible:
`openat(..., O_PATH | O_NONBLOCK)` or `statx(..., AT_STATX_DONT_SYNC)`, and/or run the enumeration in a child process with a deadline so that PAM session open
can never block indefinitely.

Additionally (separate, lower priority; sshfs side): creating the FUSE mount
before the SFTP handshake succeeds exposes a mount point that cannot be served for an unbounded time window. Mounting only after the handshake completes — or tearing the mount down immediately when the handshake fails/times out — would remove this class of deadlock and the classic "orphaned sshfs mount" problem in
general.


-- End of report --


-- System Information: --
Debian Release: forky/sid
  APT prefers unstable
Architecture: amd64 (x86_64)
Kernel: Linux 7.2.6-1-liquorix-amd64 (SMP w/32 CPU cores)
Locale: LANG=it_IT.UTF-8, LC_CTYPE=it_IT.UTF-8 (charmap=UTF-8), LANGUAGE=it
(charmap=UTF-8)
Shell: /bin/sh linked to: dash

Versions of packages related to libpam-fscrypt:
ii  fscrypt          0.3.7-1
ii  libpam-fscrypt   0.3.7-1
ii  fuse3            3.18.3-1
ii  openssh-server   1:10.5p1-1
ii  sshfs            3.7.3-1.2

Reply via email to