Skip to content

Defaults passwd_tries=0 allows infinite password tries because of integer underflow #1309

@Pingasmaster

Description

@Pingasmaster

Having a sudoers config with passwd_tries=0 lead to an integer underflow in sudo-rs, leading to unlimited password prompt:

Defaults passwd_tries=0

To Reproduce

  • Install / compile sudo-rs.
  • Run sudo-rs su or any other sudo-rs command and see that you can not only try to login one time but also an unlimited number of times as sudo-rs keeps prompting for new password.

Expected behavior
You cannot enter any password. In presence of Defaults passwd_tries=0, sudo just exits without even asking for a password, therefore this is not present in sudo.

Environment (please complete the following information):

  • Linux distribution: Ubuntu 25.10, Fedora 42
  • bug present in v0.2.9 commit 2b4d403, since v0.2.0

Additional context
The fix is simple, you just have to clamp passwd_tries at a minimum of one with allowed_attempts = max(1, passwd_tries) before handing it to attempt_authenticate, or bail out immediately when the configured value is zero or negative.

The affected code is:
src/defaults/mod.rs line 55:

      passwd_tries              = 3 [0..=1000]

This default allows passwd_tries to be set to any value from 0 through 1000, so sudoers can configure Defaults passwd_tries=0.

src/sudoers/policy.rs line 40:

              allowed_attempts: self.passwd_tries().try_into().unwrap(),

The setting is converted directly into the Authentication struct without clamping; a configured zero propagates as allowed_attempts = 0.

src/sudo/pipeline.rs lines 208 to 213:

          attempt_authenticate(
              &mut pam_context,
              &auth_user.name,
              context.non_interactive,
              allowed_attempts,
          )?;

The zero-valued allowed_attempts is handed to PAM authentication as the max_tries parameter.

src/sudo/pam.rs lines 138 to 166:

  pub(super) fn attempt_authenticate(
      pam: &mut PamContext,
      auth_user: &str,
      non_interactive: bool,
      mut max_tries: u16,
  ) -> Result<(), Error> {
      let mut current_try = 0;
      loop {
          current_try += 1;
          match pam.authenticate(auth_user) {
              Ok(_) => break,
              Err(PamError::Pam(PamErrorType::MaxTries)) => {
                  return Err(Error::MaxAuthAttempts(current_try));
              }
              Err(PamError::Pam(PamErrorType::AuthError | PamErrorType::ConversationError)) => {
                  max_tries -= 1;
                  if max_tries == 0 {
                      return Err(Error::MaxAuthAttempts(current_try));
                  } else if non_interactive {
                      return Err(Error::InteractionRequired);
                  } else {
                      user_warn!("Authentication failed, try again.");
                  }
              }
              Err(e) => {
                  return Err(e.into());
              }
          }
      }
      Ok(())
  }

max_tries is an unsigned counter that is decremented before checking for zero. When max_tries starts at 0, the first max_tries -= 1 underflows to 65535 in release builds. Technically PAM should authorize many more attempts than is normally possible, I could have infinite attemps on ubuntu 25.10. However I think this is limited somewhere in system logic in Arch linux to 3 attempts, as I cannot make more than 3 which is the default when passwd_tries is not specified in /etc/sudoers on arch.

See my suggested patch: #1313

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-pamPAM librarybugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions