sudo. If that's not solid yet, read the
Linux Command & File Reference primer first —
this one picks up exactly where its PAM section leaves off and goes deeper, on purpose, because PAM is
what the FIDO lab modifies. Command blocks with a copy button are things you could type; dimmed blocks
headed OUTPUT are representative results, nothing to run.
1. What PAM actually is
Before PAM, "can this user log in?" was a question every program answered for itself, usually by
reading /etc/passwd directly. Change how authentication works, and you had to patch
every program that authenticated anyone. Pluggable Authentication Modules fixes that
by putting one layer in between: programs like login, sudo, su,
passwd, and sshd don't check credentials themselves — they ask PAM, and PAM
runs whatever modules are configured for that program.
The payoff is that none of those programs need to know or care how identity gets proven. A module can check a password, a fingerprint, a time-of-day restriction, or — the whole reason this primer exists — a touch on a hardware key. Swap the module, and every program that defers to PAM picks up the new behavior automatically.
PAM organizes its work into four facilities, each answering a different question:
| Facility | Question it answers |
|---|---|
auth | Are you who you say you are? |
account | Is this account currently allowed to log in? (expired, locked, time-restricted…) |
password | How is a new password set or checked? |
session | What has to be set up (or torn down) around a login — home directory, environment, logging? |
pam_unix.so,
doing different jobs depending on which facility calls it — that only makes sense once the four are
distinct in your head.
2. Anatomy of a rule
Every line in a PAM config is a rule with four columns:
| Column | Meaning |
|---|---|
facility | Which of the four questions from Section 1 this line answers. |
control | What a success or failure from this module should do to the stack. |
module | The .so file that does the actual work, e.g. pam_unix.so. |
args | Options passed straight through to that module. |
Control: what a result means to the stack
A facility is rarely just one module — it's a short stack of them, evaluated top to bottom. The
control field decides how one module's result affects the rest of the stack.
required— must succeed for the overall facility to succeed, but on failure PAM keeps evaluating the remaining modules before reporting failure. This is deliberate: it stops an attacker from timing responses to figure out which specific check rejected them.requisite— same asrequired, but on failure it stops immediately and reports failure right away, skipping whatever comes after.sufficient— if this module succeeds and no earlierrequiredmodule already failed, the whole facility succeeds immediately, skipping whatever comes after.optional— its result usually doesn't affect the outcome, unless it's the only module in the stack.
There's also a more precise bracket syntax, [value1=action1 value2=action2 …], for when
the four keywords above aren't specific enough. You'll see it in real configs — for example, the
common-password line on a stock system:
password [success=1 default=ignore] pam_unix.so obscure yescrypt minlen=10 remember=5
success=1 means "on success, skip the next 1 rule in this stack" (there's a fallback
module below it that only runs if this one didn't handle things). default=ignore means
"for any other result, don't let it affect the overall outcome by itself — move on." It's the same
idea as the four keywords, just spelled out precisely instead of relying on a preset meaning.
sufficient is the control that adds a hardware key as an
alternative way to log in rather than an additional requirement — succeed with the
key, and the password check underneath never runs. Whether that's what you want, or whether you want
the key to be required alongside the password, is a real design decision, not a default to
accept blindly. The FIDO lab uses the bracket syntax to be explicit about which one it means — you'll
recognize it in Section 8.
3. The stack files
PAM configuration lives in /etc/pam.d/, one file per "service" — the name a program
passes when it talks to PAM. login, sudo, sshd, and
passwd are all separate services with their own files. But on Debian and Ubuntu, those
per-service files are usually short: most of the real rules live in four shared files —
common-auth, common-account, common-password,
common-session — and each service file just pulls them in with @include.
cat /etc/pam.d/sudo
Example output
# Set up user-specific environment
session required pam_env.so readenv=1 user_readenv=0
session required pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
@include common-auth
@include common-account
@include common-session-noninteractive
Read that as: "for authentication and account checks, do whatever common-auth and
common-account say." That indirection is exactly why editing PAM is so consequential — a
single change to common-auth changes how every service that includes it behaves,
not just one.
common-session-noninteractive and not common-session?
A handful of services (like sudo and cron) don't want the interactive parts
of session setup — a message-of-the-day banner, for instance — running every time. The
-noninteractive variant is the same idea, trimmed for a service that isn't a real login.
4. pam_unix.so — the password module
pam_unix.so is the traditional Unix password check, and it's a good example of how one
module can serve more than one facility with different behavior in each: as an auth
module it checks a typed password against the stored hash; as a password module it sets a
new hash when one is changed; as an account module it can enforce expiry. Same
.so, different job, depending on which stack calls it.
grep pam_unix /etc/pam.d/common-password
Example output
password [success=1 default=ignore] pam_unix.so obscure yescrypt minlen=10 remember=5
obscure— run basic strength checks (not just a dictionary word, not the username, etc).yescrypt— the hashing scheme used to store the password.minlen=10— require at least ten characters.remember=5— block reuse of the last five passwords.
minlen and remember both raise the cost of a
guessed or cracked credential — minlen by making brute-force more expensive,
remember by stopping the "change it, then change it right back" workaround.
5. pam_faillock.so — lockout
pam_unix.so checks whether a password is correct, but it doesn't count how many times
someone got it wrong. pam_faillock.so adds that: after too many consecutive failures, the
account is locked out for a while. It wraps the existing auth line rather than replacing
it, which is a good second example of the control keywords from Section 2 in real use:
auth requisite pam_faillock.so preauth silent deny=5 unlock_time=900
auth [success=1 default=ignore] pam_unix.so
auth [default=die] pam_faillock.so authfail deny=5 unlock_time=900
auth required pam_faillock.so authsucc
- The first line is
requisiteon purpose: if the account is already locked, stop right there — there's no point running the password check at all. deny=5— lock after 5 consecutive failures.unlock_time=900— unlock automatically after 900 seconds (15 minutes).- Check or clear counts with
faillock --user user1andfaillock --user user1 --reset.
6. Editing PAM safely
A typo in /etc/pam.d/ doesn't throw an error you'll see before it matters — it can lock
out every account, including root, the next time someone tries to authenticate. The rule that makes
this safe to work in is simple and non-negotiable:
sudo -i is enough). Make your change, save, and test login/sudo
from a third terminal — while the root shell from step one stays open. If the change locked
you out, that still-open root shell is how you undo it. Never close your editor on a PAM change you
haven't tested.
Two more things worth knowing before you touch common-* files directly:
- On Debian and Ubuntu,
pam-auth-updatemanages thecommon-*files for you from small "profile" fragments, and is the preferred way to enable or disable a whole authentication method — includingpam_u2f, which is how the FIDO lab plugs it in. Hand-editing is for understanding what's there and for fine control the profile system doesn't expose. - Keep a break-glass account or password-only login path available while you experiment. The FIDO lab insists on this for the same reason this section does: authentication changes are the one class of mistake where "just fix the file" isn't an option if you're the one locked out.
7. Watching it happen: journalctl for PAM
None of the modules above print anything to the terminal you're testing in. Success or failure both happen silently from your point of view — every PAM module logs through syslog instead, which on a systemd system means the journal. Section 6 already told you to test from a second terminal before trusting an edit; this is what you'd have open in a third one, watching it happen live.
Uses sudojournalctl -f
That alone is the single most useful command while testing a PAM edit — it streams new log lines as
they arrive, so a failed sudo attempt in one terminal shows up in this one within a second.
A few more ways to narrow it down:
# Only lines from one program's PAM stack
journalctl -t sudo
journalctl _COMM=sshd
# Grep the message text directly — finds a module by name regardless of what invoked it
journalctl -g pam_faillock
# Errors and worse, since the current boot
journalctl -b -p err
# Just the last few minutes — narrows to your actual test window
journalctl --since "5 minutes ago"
A real pam_unix failure followed by a pam_faillock lockout — the two modules
from Sections 4 and 5 — reads like this in the journal:
sudo: pam_unix(sudo:auth): authentication failure; logname= uid=1000 euid=0 tty=/dev/pts/1 ruser= rhost= user=user1
sudo: pam_faillock(sudo:auth): Consecutive login failures for user user1 account temporarily locked
journalctl -g pam_ across a wider --since window shows every module's
decisions, not just the one you're testing. And practically: journalctl -f running in real
time is the fastest confirmation that a break-glass path still works, before you ever close the editor
on a common-auth change.
8. Preview: pam_u2f
Everything above is general-purpose PAM literacy. This section previews one specific module —
pam_u2f.so — so its shape is already familiar when the FIDO lab has you install and
configure it for real. This is a preview, not a walkthrough: no VM is required here, and nothing below
needs to be typed.
pam_u2f plugs into the auth facility exactly like pam_unix.so or
pam_faillock.so above — same four columns, same stack. What it checks is a hardware
security key instead of a typed password:
auth [success=done default=die] pam_u2f.so authfile=/etc/security/u2f_keys cue pinverification=1
authfile=/etc/security/u2f_keys— a central, root-owned file mapping usernames to registered key credentials, instead of each user managing their own.cue— prompt the user to touch the key. If you need custom prompt text, it takes square brackets, not quotes:cue [cue_prompt=touch your key now]— PAM splits module arguments on whitespace and does not understand shell-style quoting, so a quotedcue="..."silently breaks into several unrecognized options instead of one prompt.pinverification=1— require the key's PIN in addition to the touch.[success=done default=die]— the bracket syntax from Section 2, but stricter than thecommon-passwordexample: succeed here and the entire auth stack succeeds immediately (done); fail here and the entire stack fails immediately (die). No password fallback happens either way.
pinverification=1) is the control the FIDO lab teaches as its second factor, and it is
only trustworthy on libpam-u2f 1.1.1 or later. Version 1.1.0, which
introduced the feature, shipped a PIN-authentication bypass (CVE-2021-31924), fixed in
1.1.1. The lab will have you verify this on the actual VM with
dpkg -l libpam-u2f — worth remembering why that check exists.
9. After the decision: pam_systemd & loginctl
Section 1 split PAM into four facilities. pam_u2f above lives in auth — it's
part of the decision. This module lives in session, the facility that only runs
after auth has already succeeded: it doesn't decide whether you get in, it sets things up once
you have.
session optional pam_systemd.so
optional here isn't an accident — it's the control keyword from Section 2 doing exactly
what it's for. pam_systemd isn't gating anything, so its result shouldn't be able to affect
whether the session as a whole succeeds. What it actually does: registers the now-successful login with
systemd-logind, which creates a session object, a session-N.scope unit that
puts every process from that login into its own cgroup, and sets XDG_RUNTIME_DIR /
XDG_SESSION_ID for it.
loginctl is the inspection tool for what pam_systemd just created —
effectively systemctl for logged-in humans instead of services:
loginctl list-sessions
Example output
SESSION UID USER SEAT TTY
3 1000 user1 seat0 tty2
7 1000 user1 pts/1
loginctl session-status 3
loginctl list-sessions is a more complete "who's on this
box" than who or w — it shows session type (console, X11, Wayland, or a bare
SSH pts) and whether each one is active, and every row corresponds to a real cgroup you
could inspect or terminate with loginctl terminate-session <id>. It's the same
unit-and-cgroup machinery systemd uses for ordinary services — applied to logins instead of daemons.
10. Where this goes next
That's the whole shape of PAM: four facilities, four-column rules, a small set of control keywords
that decide how a stack behaves, and shared common-* files that make one edit apply
everywhere. Everything the FIDO lab does — installing pam_u2f, wiring it into
common-auth with a specific control, gating it to a group, testing it from a break-glass
session before trusting it — is an application of exactly what's above, not new machinery.
pam_u2f for real, on a VM, with a break-glass account preserved throughout. If you want
the account/permissions/services context PAM sits inside, the
Linux Command & File Reference primer covers
that ground, and the Linux Hardening Checklist turns this
material into a scored self-audit once you're ready to test it.