Hi,

The opt-in sshd profile in profiles/apparmor/profiles/extras/usr.sbin.sshd
hardcodes Debian's /usr/lib/openssh/ for the OpenSSH 9.8+ helper binaries.
On distributions that place them elsewhere the exec is denied and no one
can log in over SSH.

Current master, lines 157-161:

    /usr/lib/openssh/sshd-auth ix,
    /usr/lib/openssh/sshd-session ix,
    ...
    /usr/lib/openssh/sftp-server PUx,

Known-affected layouts:

    Debian / Ubuntu    /usr/lib/openssh/     <- the only one covered
    openSUSE Leap 16   /usr/libexec/ssh/
    Arch               /usr/lib/ssh/         (issue #499)

Reproduced on openSUSE Leap 16.0, apparmor 4.1.7-160000.2.1,
openssh-server 10.5p1. /usr/lib/openssh does not exist there;
openssh-server installs
/usr/libexec/ssh/{sshd-session,sshd-auth,sftp-server}. The profile
contains no occurrence of "libexec", carries no "complain" flag (so it
loads in enforce), and has no catch-all exec rule -- line 155 is "/** r,"
which grants read, not execute.

Result:

    apparmor="DENIED" operation="exec" class="file"
      name="/usr/libexec/ssh/sshd-session"
      requested_mask="x" denied_mask="x" fsuid=0 ouid=0

and client-side:

    kex_exchange_identification: read: Connection reset by peer

sftp-server fails the same way.

Why this is worse than it first looks: AppArmor attaches a profile at
exec, so loading it does not confine the already-running sshd. An
administrator activates the profile, sees SSH still working, and concludes
it is fine. The lockout lands at the next sshd restart or reboot --
remotely, unattended, with no console.

Prior art: issue #499 "Apparmor sshd profile issues on Arch Linux"
(2025-03-16, closed 2025-03-17) reported the same class of problem for
Arch's /usr/lib/ssh/sftp-server and /usr/bin/sshd. It has no linked MR and
no commit, and master is still unfixed, so it appears to have been closed
without a code change. Issue #553 (RFC: @{bin}, @{sbin} and @{lib}) looks
like the structural fix.

Suggested minimal change -- cover the known layouts:

    /usr/{lib/openssh,libexec/ssh,lib/ssh}/sshd-auth ix,
    /usr/{lib/openssh,libexec/ssh,lib/ssh}/sshd-session ix,
    /usr/{lib/openssh,libexec/ssh,lib/ssh}/sftp-server PUx,

That also closes the sftp-server half of #499. A tunable would be more in
keeping with commit "profiles: update the rest of the profiles to use
@{exec_path}" (2025-05-09) and with #553 -- an @{libexecdir}-style tunable
set per distribution, so this stops recurring per-profile.

SAFE REPRODUCTION -- do not simply activate the profile on a machine you
reach over SSH. Test a copy of the binary under a renamed profile instead,
so the real sshd is never confined:

    # 1. isolated copy of the daemon
    sudo cp -a /usr/sbin/sshd /usr/local/sbin/sshd-aatest

    # 2. same profile, attachment path rewritten (2 lines only)
    sed -e 's|^/usr/sbin/sshd {|/usr/local/sbin/sshd-aatest {|' \
        -e 's|^\(\s*\)/usr/sbin/sshd mrix,|\1/usr/local/sbin/sshd-aatest 
mrix,|' \
        /usr/share/apparmor/extra-profiles/usr.sbin.sshd > /tmp/aatest.profile
    sudo apparmor_parser -r /tmp/aatest.profile

    # 3. run it on a verified-free port, loopback only
    sudo ss -lntH 'sport = :22022'          # must be empty
    sudo /usr/local/sbin/sshd-aatest -p 22022 -o ListenAddress=127.0.0.1 \
         -o PidFile=/tmp/aatest.pid -o LogLevel=DEBUG2 -E /tmp/aatest.log

    # 4. connect and observe
    ssh -p 22022 127.0.0.1
    sudo ausearch -m AVC -ts recent | grep sshd-session

    # 5. clean up
    sudo kill "$(sudo cat /tmp/aatest.pid)"
    sudo apparmor_parser -R /tmp/aatest.profile
    sudo rm -f /usr/local/sbin/sshd-aatest

Confirm the connection actually reached the test daemon before drawing a
conclusion: /tmp/aatest.log should contain "rexec start in ... newsock
...". The listener does not log "Connection from"; that is sshd-session,
which never runs.

Happy to send this as a merge request instead if that is preferred -- I do
not currently have a gitlab.com account, which is why it is coming to the
list.

Regards,
James C. Owens

Reply via email to