Understanding the effects of PAM module results ('controls' in PAM jargon)

April 9, 2022

To simplify, PAM is the standard system on Linux (and often other Unixes) for configuring how applications such as SSH handle various aspects of authenticating and logging people in. PAM splits this work up into a collection of PAM modules (sometimes 'PAMs', since PAM is theoretically short for 'Pluggable Authentication Modules'), which are then configured in per application (or 'service') stacks. Each individual PAM module can succeed, fail, or signal other conditions, and the PAM stack for a given application tells you not only what order PAM modules should be checked in, but what the effects of their results are. In the jargon of pam.conf, this is the control of a PAM module.

There are two forms for these controls; the 'historical' and still very common single-word form that uses things like 'required' and 'sufficient', and the newer, more detailed and complicated syntax. There are two descriptions of the meanings of the historical single-word forms in pam.conf; the somewhat informal main description, and then the potentially more explicit version that restates them in terms of the newer and more detailed syntax. All of this makes for a system that can be hard to understand and follow.

(In the following I'm going to assume that you've read through the pam.conf manual page to somewhat understand the various controls, or that you know PAM well enough to not need to.)

Taken from CentOS 7, here is one example of the standard 'auth' stack:

auth required    pam_env.so
auth sufficient  pam_unix.so nullok try_first_pass
auth requisite   pam_succeed_if.so uid >= 1000 quiet_success
auth required    pam_deny.so

Let's walk through this and try to understand its aggregate effects.

  1. Pam_env sets environment variables; since it's required, if it fails for some reason the authentication process will appear to continue, but at the end it will be considered to fail.

    Pam_env normally succeeds; as far as I know it fails only in exceptional circumstances.

  2. Pam_unix verifies Unix passwords. It's 'sufficient', so if it succeeds the authentication is done. However, if it fails, further modules in the auth stack continue to be checked, and I believe that later modules could accept the authentication.

  3. Pam_succeed_if will succeed if the UID is 1,000 or higher and fail otherwise. Since it's set as 'requisite', if it fails (the UID is under 1,000), authentication immediately fails, but if the UID is 1,000 or higher, the stack continues to check further modules.

  4. Pam_deny does what you'd think; it always says no. Since it's 'required', this failure is not immediately fatal, but since it's the last module listed, its failure will cause the entire stack to fail and authentication to not be successful.

Under normal circumstances, if you enter a correct password the stack will stop at pam_unix and consider you authenticated. If you enter an incorrect password, you will eventually fail in the rest of the stack, but possibly with different error messages depending on your UID. If your UID is under 1,000, the failure will be caused by pam_succeed_if; if your UID is 1,000 or over, it will be caused by pam_deny. This two-sided failure is somewhat confusing; it's not clear what CentOS 7 is up to.

The Ubuntu 20.04 stack is as confusing in its own way, although it has comments. Here it is:

auth [success=1 default=ignore]  pam_unix.so nullok_secure
auth requisite   pam_deny.so
auth required    pam_permit.so
auth optional    pam_cap.so
  1. Pam_unix with the 'success=1' syntax will skip the next module on success and otherwise do nothing.
  2. Pam_deny with 'requisite' will immediately fail the authentication if it's processed, but it will only be processed if pam_unix did not succeed, ie if you got the password wrong.

  3. Pam_permit with 'required' apparently is present simply to, to quote the comments in the file:

    prime the stack with a positive return value if there isn't one already; this avoids us returning an error just because nothing sets a success code since the modules above will each just jump around

    If it fails for some inexplicable reason, as a 'required' module it will make the authentication fail. But we only got to it if we skipped pam_deny because you entered the right Unix password.

  • Pam_cap "sets the current process' inheritable capabilities", to quote its manual page. Since it's 'optional', its result appears not to matter, but the stack will have been set to succeed by pam_permit if we got here.

The first two PAM modules here and their setup seem to have the effect of failing the authentication if you enter a bad Unix password and otherwise letting the rest of the stack go on, but I'm not sure why Ubuntu needs two modules where making pam_unix a 'requisite' module seems like it should work.

(The one difference that jumps out to me is that this way, if the pam_unix module returns 'new_authtok_reqd', meaning 'the user needs to change their password', the Ubuntu stack will fail and a 'requisite' would make things succeed. See eg here. In the same situation, I believe the CentOS 7 authentication stack will succeed.)

There's an entire additional level of complexity in real world PAM usage (and in understanding the effects of changes to your system's PAM stuff), but that's a topic for another entry.

Written on 09 April 2022.
« On the ordering of password and MFA challenges during login
A Linux PAM setup and the problem of stopping authentication »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Sat Apr 9 22:16:55 2022
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.