Debian GNU/Linux · Desktop environment

Refuse root logins at the graphical login screen

When it fails: root can open a graphical session. A full desktop session as root runs a browser, a mail client and many other programs with every privilege, and leaves no trace of which administrator used it.

CB-DEB-DESK-007mediumactiveautomatedautomated fix, lockout riskprofile desktop

As released in v0.1.0 beta · pre-release. Current state.

Description

Display managers authenticate users through PAM services in /etc/pam.d/. A line auth required pam_succeed_if.so user != root quiet_success at the top of the auth stack refuses root before the password is checked. Debian's GDM and SDDM packages ship that line; LightDM's lightdm service does not. The control checks the password, fingerprint and smart card services of GDM, and the login services of LightDM and SDDM, that are present.

Automatic login services (gdm-autologin, lightdm-autologin, sddm-autologin) are not checked: CB-DEB-DESK-006 forbids automatic login. The fix only adds the line to services that lack it and was tested by removing the line from gdm-password; it was not tested with LightDM or SDDM. It changes PAM, so it is disabled by default.

Rationale

A graphical session runs hundreds of processes and parses untrusted content (web pages, mail, documents). Running all of it as root turns any bug into a full compromise. Administrators should log in with their own account and elevate with sudo, which keeps their actions attributable.

Impact

root cannot log in graphically. Administrators use their own account and sudo.

Setting

Location/etc/pam.d/gdm-password, gdm-fingerprint, gdm-smartcard, lightdm, sddm
DefaultPresent for GDM and SDDM, missing for LightDM
Recommendedauth required pam_succeed_if.so user != root quiet_success

Audit

For each of /etc/pam.d/gdm-password, gdm-fingerprint, gdm-smartcard, lightdm and sddm that exists, check that the auth stack contains a pam_succeed_if.so line with user != root and a required or requisite control (or default=bad).

Automated: every check must pass. Scanners report each check by its id.

pam-root

Display manager PAM services refuse root.

script: |
  re='^[[:space:]]*auth[[:space:]]+(required|requisite|\[[^]]*default=(bad|die)[^]]*\])[[:space:]]+pam_succeed_if\.so[[:space:]].*user[[:space:]]*!=[[:space:]]*root'
  found=0 rc=0
  for f in /etc/pam.d/gdm-password /etc/pam.d/gdm-fingerprint /etc/pam.d/gdm-smartcard \
    /etc/pam.d/lightdm /etc/pam.d/sddm; do
    [ -f "$f" ] || continue
    found=1
    if grep -Eq "$re" "$f"; then echo "$f refuses root"; else echo "$f allows root"; rc=1; fi
  done
  [ "$found" = 1 ] || { echo "no display manager PAM service"; exit 2; }
  exit "$rc"

Remediation

In each display manager PAM service listed by the audit that lacks it, add before the first auth line:

auth required pam_succeed_if.so user != root quiet_success
Restartnone
Disruptionlow
Lockout riskyes: can cut off administrative access. Off by default in generated scripts and playbooks, opt in through a tailoring file.

Rollback

Restore the changed files of /etc/pam.d/ from the backup directory.

pam-root

# Add "auth required pam_succeed_if.so user != root quiet_success" before the first auth line of
# each display manager PAM service that lacks a root refusal.
re='^[[:space:]]*auth[[:space:]]+(required|requisite|\[[^]]*default=(bad|die)[^]]*\])[[:space:]]+pam_succeed_if\.so[[:space:]].*user[[:space:]]*!=[[:space:]]*root'
line='auth	required	pam_succeed_if.so user != root quiet_success'
for f in /etc/pam.d/gdm-password /etc/pam.d/gdm-fingerprint /etc/pam.d/gdm-smartcard \
  /etc/pam.d/lightdm /etc/pam.d/sddm; do
  [ -f "$f" ] || continue
  grep -Eq "$re" "$f" && continue
  grep -Eq '^[[:space:]]*(auth[[:space:]]|@include[[:space:]]+common-auth)' "$f" || {
    echo "$f has no auth line, left unchanged" >&2
    continue
  }
  cb_backup "$f"
  L=$line awk '!done && /^[ \t]*(auth[ \t]|@include[ \t]+common-auth)/ { print ENVIRON["L"]; done = 1 } { print }' \
    "$f" >"$f.cirsec-tmp" && cat "$f.cirsec-tmp" >"$f"
  rc=$?
  rm -f "$f.cirsec-tmp"
  [ "$rc" -eq 0 ] || exit 1
  echo "added root refusal to $f"
done

Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.

References

Mappings

FrameworkIDRelationNote
ANSSI-BP-028 - Recommandations de configuration d'un système GNU/Linux2.0R33relatedR33 asks for attributable administration; refusing graphical root sessions is one part of it.
NIST SP 800-53 Rev. 55.1.1AC-6(2)related

How this control relates to the other framework's: equivalent, stricter, looser, partial or related.

Threats mitigated

  • related CB-DEB-DESK-006 Require a password at the graphical login, without automatic login or guest session

People and lifecycle

Authors
Verified2026-10-18 by Sn0wAlice on 12, Proxmox VM with minimal GNOME (gdm3, gnome-shell), Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. Check and fix tested on GDM after removing the root line from gdm-password; pamtester confirmed root refused and a normal account still authenticated after the fix. LightDM and SDDM services not tested.
Verified2026-10-18 by Sn0wAlice on 13, Proxmox VM with minimal GNOME (gdm3, gnome-shell), Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. Check and fix tested on GDM after removing the root line from gdm-password; pamtester confirmed root refused and a normal account still authenticated after the fix. LightDM and SDDM services not tested.
Introduced inv0.1.0
Created, updated2026-10-18, 2026-10-18
Translationsfr