Bounding Unicorns

Linux Password Fail Delay (Login/Sudo/SU/SSH)

When authentication on a GNU/Linux system fails, normally there is a delay added before the failure is reported to the user. I find the length of the delay excessive, and today we'll take a look at how to configure it to be more sensible.

But first I'd like to note that FreeBSD (and, likely, other BSDs) have a much better implementation of the login delay: the first couple of authentication failures are instant, and subsequent failures have the delay added (which is also longer than Linux's delay). This makes the delay normally non-intrusive at all, i.e., in normal system usage I virtually never encounter it. Linux's implementation is to apply the delay on every failure, which I guess was easier to implement but provides a poor user experience.

Authentication Methods

I am concerned with 4 authentication methods:

  1. login - this is the prompt that is displayed after a computer boots. It can also be manually invoked by running login from a root shell.
  2. su - this is normally how I gain root from a regular user account.
  3. sudo - I generally do not use sudo myself but I will include it here for completeness.
  4. ssh - it can be useful to have different settings for local vs remote logins. To test ssh login delay, it is sufficient to execute ssh localhost from any user account and enter empty login and password.

These login methods are hard to time because they consume input from terminal rather than from redirectable standard input. The timings below are my approximations.

Default Configuration

On a newly installed Debian system, we find the following note and setting in /etc/pam.d/login:

# Enforce a minimal delay in case of failure (in microseconds).
# (Replaces the `FAIL_DELAY' setting from login.defs)
# Note that other modules may require another minimal delay. (for example,
# to disable any delay, you should add the nodelay option to pam_unix)
auth       optional   pam_faildelay.so  delay=3000000

... and, the related entry in /etc/login.defs:

################# OBSOLETED BY PAM ##############
#                                               #
# These options are now handled by PAM. Please  #
# edit the appropriate file in /etc/pam.d/ to   #
# enable the equivelants of them.
#
###############

# ... other lines snipped ...
#FAIL_DELAY

Testing our 4 authentication methods, we get:

Method Delay
login 3 seconds
su 3 seconds
sudo 2 seconds
ssh 2 to 4 seconds depending on user account

While login and su produce the expected 3 second delay, sudo and ssh appear to have a mind of their own and have a delay that is clearly less than 3 seconds (I'd say it is just over 2 seconds long). For ssh, the delay is all over the place: running ssh localhost from a non-root user account produces a 2 second delay, ssh localhost from the root account produces a 3 second delay, ssh bogus@localhost from the root account produces sometimes a 4 second delay and sometimes a 2 second delay. Interesting.

Weirdness Begins

OK, let's comment out the auth optional pam_faildelay.so delay=3000000 line and see what happens. No restart of any services or reboot of the system is required for the changes to take effect.

On my system commenting that line out appeared to have had no effect whatsoever - both login and su retained their 3 second delay, sudo retained its 2 second delay and ssh kept delaying between 2 and 4 seconds. In actuality, the delay for login decreased from 3 seconds to 2 seconds, but I didn't realize this until later. The behavior with the pam_faildelay.so line commented out is as follows:

Method Delay
login 2 seconds
su 3 seconds
sudo 2 seconds
ssh 2 to 4 seconds depending on user account

Since I didn't realize that login behavior actually changed, I thought that I didn't make the change correctly. Increasing the delay to 10 seconds will clarify the situation, as it did for me. Testing the following configuration: auth optional pam_faildelay.so delay=10000000, we get:

Method Delay
login 10 seconds
su 3 seconds
sudo 2 seconds
ssh 2 to 4 seconds depending on user account

The change to the pam_faildelay.so line only affects login method, and the delay there is always at least 2 seconds regardless of the setting. With this information the following comment at the very top of /etc/pam.d/login now makes more sense:

#
# The PAM configuration file for the Shadow `login' service
#

By "'login' service" they must mean the login program. I don't know why it's being referenced as a "service". The manual page for it calls it a program. Re-reading the comment above the pam_faildelay.so configuration line we also see that the delay specified is minimal. Clearly something else is specifying a minimum delay of 2 seconds, but there isn't any other mention of "delay" under /etc/ on my system.

Question to the authors of default PAM configuration: why is the login delay set to 3 seconds and sudo/ssh delay is 2 seconds? Most systems will be attacked over the network, so why is one local login mechanism configured to have a minimally longer delay than network logins?

FAIL_DELAY in login.defs

Now that we know there are minimal delays specified by unknown software, let's play with the "obsoleted" FAIL_DELAY setting in login.defs. Unlike the pam_faildelay.so, the value here is in seconds, thus I added to my login.defs:

FAIL_DELAY=10

I commented out the pam_faildelay.so in /etc/pam.d/login for this test. Result:

Method Delay
login 2 seconds
su 10 seconds
sudo 2 seconds
ssh 2 to 4 seconds depending on user account

Now, let's set FAIL_DELAY=0 and see what happens:

Method Delay
login 2 seconds
su 2 seconds
sudo 2 seconds
ssh 2 to 4 seconds depending on user account

It appears that FAIL_DELAY has a non-zero default value (3 seconds), which is not documented but is used by su. su is also subject to the 2 second delay established by as of yet unknown software.

Note also that although FAIL_DELAY is claimed to be "overridden by PAM", the overrides by default only exist for one program (login, the only program that /etc/pam.d/login apparently applies to) thus changing the value in /etc/pam.d/login where it can be found by grep doesn't affect su in the slightest, as per our testing so far.

pam_unix.so nodelay

Let's add nodelay to pam_unix.so configuration, as advised in /etc/pam.d/login. Unfortunately there are many references to pam_unix.so on my system:

/etc/login.defs:# overriden by PAM, since the default pam_unix module has it's own built
/etc/pam.d/common-account:account   [success=1 new_authtok_reqd=done default=ignore]    pam_unix.so 
/etc/pam.d/common-auth:auth [success=1 default=ignore]  pam_unix.so nullok
/etc/pam.d/common-password:# Explanation of pam_unix options:
/etc/pam.d/common-password:# used to change user passwords.  The default is pam_unix.
/etc/pam.d/common-password:#`OBSCURE_CHECKS_ENAB' option in login.defs.  See the pam_unix manpage
/etc/pam.d/common-password:password [success=1 default=ignore]  pam_unix.so obscure yescrypt
/etc/pam.d/common-session-noninteractive:session    required    pam_unix.so 
/etc/pam.d/common-session:session   required    pam_unix.so 
/etc/pam.d/login:# to disable any delay, you should add the nodelay option to pam_unix)
/etc/pam.d/runuser:session      required    pam_unix.so

Editing these files by hand is tedious. The following shell command adds nodelay to all lines:

for f in /etc/pam.d/*; do sed -i -e 's/pam_unix.so/pam_unix.so nodelay/' $f; done

And the following commands removes nodelay:

for f in /etc/pam.d/*; do sed -i -e 's/pam_unix.so nodelay/pam_unix.so/' $f; done

Result after disabling the delay in all files (with /etc/pam.d/login and /etc/login.defs also still setting the delay to zero):

Method Delay
login None
su None
sudo None
ssh None

The delay is now gone, and individual settings can be verified to do what we figured out them to do earlier:

  • Having no FAIL_DELAY in login.defs at all adds about 2 seconds of delay to su and does not change behavior of login, sudo or ssh. Having no delay in su requires setting FAIL_DELAY=0 and adding nodelay to pam_unix.so.
  • The pam_faildelay.so setting in /etc/pam.d/login affects only the login program, nothing else.

pam_delay.so Revisited

The man page for pam_faildelay.so states:

pam_faildelay is a PAM module that can be used to set the delay on failure per-application.

If no delay is given, pamfaildelay will use the value of FAILDELAY from /etc/login.defs.

Our testing reveals that the first paragraph of this description is misleading: pam_faildelay.so is one of three sources of the delay and the delay is set to the largest one requested by any of the sources (pam_faildelay.so has no privileged standing in this regard).

The second part, however, appears to be accurate. After disabling the delay added by pam_unix.so, specifying the following in /etc/pam.d/login:

auth       optional   pam_faildelay.so

... causes the delay experienced by login to match the delay specified by FAIL_DELAY in login.defs, and if there is no FAIL_DELAY setting in login.defs, there isn't any delay in login operation.

pam_unix.so Delay Configuration

The man page for pam_unix.so says the following about the nodelay argument:

This argument can be used to discourage the authentication component from requesting a delay should the authentication as a whole fail. The default action is for the module to request a delay-on-failure of the order of two second.

Whoever wrote this deserves an award for language contortionism, at the collective expense of the rest of the world who has to make sense of what this description is struggling to convey. "authentication component" refers to the part of pam_unix.so which handles authentication; for whatever reason, pam_unix.so performs several tasks rather than those tasks being split across modules. Because of this pam_unix.so figures many times in /etc/pam.d. You'd need to understand the various components of pam_unix.so and map the lines under /etc/pam.d to the component to figure out where nodelay would be applicable and where it wouldn't be. I imagine in practice there's no functional problem with appending it to all pam_unix.so references.

Separately, we learn that the delay is not fixed but "on the order of two second", which I suppose can explain the variable delay seen by ssh but doesn't explain why sudo's delay is always shorter and why login's delay doesn't ever seem to be more than 3 seconds with the default configuration. Maybe both sudo and login do delay longer on occasion and I'm just missing it.

SU Delay

su really is a special case. Remember how we figured out that FAIL_DELAY defaults to zero if not specified? Yeah, this doesn't apply to su. If there is no FAIL_DELAY given in login.defs, su imposes a delay of about 1.5 seconds and every other program has no delay. This delay remains in effect even if another delay is configured via PAM elsewhere, as will be done in the next section. To have su start out with no delay, login.defs must have an explicit FAIL_DELAY=0 in it.

Lowering The Delay

In my case, I want the delay to be lower than the default of 2-5 seconds. This means I have to disable the default delay of pam_unix.so by adding nodelay to every mention of pam_unix.so in /etc/pam.d, then replacing the delay specified in pam_faildelay.so with a smaller value (or remove this setting entirely), and add some delays for programs other than login (su, sudo and ssh).

The order of directives in PAM configuration matters; pam_faildelay.so must be specified before pam_unix.so or the delay specification will be ignored (with no warnings). On my system /etc/pam.d/login includes /etc/pam.d/common-auth, and the delay for all programs can be specified by adding the following line to the top of /etc/pam.d/common-auth, before thepam_unix.so` line:

auth       optional   pam_faildelay.so  delay=500000

This requests a very sensible 0.5 second delay. Let's see the results:

Method Delay
login 0.5 seconds
su 0.5 seconds (~1.5 seconds if you don't have FAIL_DELAY=0 in login.defs)
sudo 0.5 seconds
ssh 0.5 to 1 1 seconds

Clearly we are still too optimistic about understanding how this delay business is working. I verified that setting the delay to 0 instead of 500000 in pam_faildelay.so removes the delay completely in all programs, including ssh, but with any delay configured via pam_faildelay.so, the actual delay produced by ssh is longer. Depending on the configured delay I see something like this:

Configured Delay Actual Delay
0.5 seconds 0.7 to 1 seconds
5 seconds 6-9 seconds

It looks like ssh is somehow increasing the delay and doing it in a way that depends on the configured delay length. How and why it does that is something I elected to omit researching, because I wanted a slightly longer delay for ssh anyway, and here it is doing that all on its own, thus requiring zero additional configuration, even if it's pure chance that its logic is indeed desirable. I settled on reducing the delay configured with pam_faildelay.so to 0.3 seconds; this produces a roughly half a second delay for SSH logins and a shorter delay for local logins on the machine, which I am quite happy with.