This question is about understanding the reason(s) behind a perceived inconsistency between documented and actual behavior when calling an executable through sudo. When the secure_path option is enabled (default on my system), the search path behave as expected. But when the option is disabled, something strange happens: an executable in /usr/local/bin
can be reached without a fully qualified path name despite its location not being in the search path.
System Information
The following software is currently installed on my system:
## Yeah... still haven't migrated to Alma
[me@localhost ~]$ cat /etc/centos-release
CentOS Linux release 8.5.2111
[me@localhost ~]$ bash --version | head -1
GNU bash, version 4.4.20(1)-release (x86_64-redhat-linux-gnu)
[me@localhost ~]$ sudo --version
Sudo version 1.8.29
Sudoers policy plugin version 1.8.29
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.29
[me@localhost ~]$ ssh -V
OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021
PAM does not set or update the value of the PATH variable on my system:
[me@localhost ~]$ grep --recursive 'pam_env\.so' /etc/pam.d
/etc/pam.d/fingerprint-auth:auth required pam_env.so
/etc/pam.d/smartcard-auth:auth required pam_env.so
/etc/pam.d/su:auth required pam_env.so
/etc/pam.d/password-auth:auth required pam_env.so
/etc/pam.d/system-auth:auth required pam_env.so
[me@localhost ~]$ sudo cat /etc/security/pam_env.conf /etc/environment | grep PATH
# be useful to be set: NNTPSERVER, LESS, PATH, PAGER, MANPAGER .....
#PATH DEFAULT=${HOME}/bin:/usr/local/bin:/bin\
My /etc/sudoers
file sets a secure_path value for all sudoers:
[me@localhost ~]$ sudo grep --recursive secure_path /etc/sudoers /etc/sudoers.d
/etc/sudoers:Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Bash defaults to the following PATH value:
[me@localhost ~]$ env --ignore-environment bash -c 'echo $PATH'
/usr/local/bin:/usr/bin
Finally, I am connected to the system through SSH, whose /etc/ssh/sshd_config
file contains this line:
[me@localhost ~]$ sudo grep PATH /etc/sshd_config
# This sshd was compiled with PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
Replication Procedure
To replicate, I first create a dummy script and install it in /usr/local/bin
:
[me@localhost ~]$ cat > dummy <<EOF
#!/usr/bin/bash
echo 'Found!'
EOF
[me@localhost ~]$ sudo install --owner=root --group=root --mode=755 dummy /usr/local/bin
I verify that the search path works as expected when not using sudo:
## The /usr/local/bin location is part of my search path
[me@localhost ~]$ echo $PATH
/home/me/.local/bin:/home/me/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
## This is expected
[me@localhost ~]$ dummy
Found!
And using sudo, no surprise here either:
## Sudo's secure_path value
[me@localhost ~]$ sudo --user=other env | grep PATH
PATH=/usr/bin:/bin:/usr/sbin:/sbin
## This is expected
[me@localhost ~]$ sudo --user=other which dummy
which: no dummy in (/sbin:/bin:/usr/sbin:/usr/bin)
## This too, of course
[me@localhost ~]$ sudo --user=other dummy
sudo: dummy: command not found
## Indeed, a fully qualified path name is required
[me@localhost ~]$ sudo --user=other /usr/local/bin/dummy
Found!
Then, I disable secure_path (careful here! use of visudo is advised), either by commenting the line out in /etc/sudoers
or by creating a file /etc/sudoers.d/local
with the following lines:
# Disable secure_path if set
Defaults !secure_path
This should prevent sudo, when called with the --preserve-env
switch, from overriding the PATH environment variable with the value of secure_path. And it works as expected.
However, when not using the --preserve-env
switch, and not prompting a full login sequence with the --login
switch (thereby not sourcing any of Bash's startup files), and with no assignment to PATH in any of PAM environment files, something strange happens:
## Not sure where this PATH value is from, neither from sudo's secure_path option
## (not set), PAM environment files (contain no assignment to PATH), Bash startup
## scripts (not sourced), nor Bash or sshd default PATH values (no match).
[me@localhost ~]$ sudo --user=other env | grep PATH
PATH=/usr/bin:/bin:/usr/sbin:/sbin
## Regardless, this is expected
[me@localhost ~]$ sudo --user=other which dummy
which: no dummy in (/usr/bin:/bin:/usr/sbin:/sbin)
## But wait! What?!?
[me@localhost ~]$ sudo --user=other dummy
Found!
So, how is it that which dummy
complains of dummy not in search path while a straight call to dummy
without path prefix finds it?
Relevant Documentation
Following are references to various bits of information that I found relevant while researching this question.
Sudo documentation says this about the secure_path option:
Path used for every command run from sudo. If you don't trust the people running sudo to have a sane PATH environment variable you may want to use this. Another use is if you want to have the “root path” be separate from the “user path”. Users in the group specified by the exempt_group option are not affected by secure_path. This option is not set by default.
As per pam_env documentation in man pages, only the /etc/environment
, the /etc/security/pam_env.conf
and ~/.pam_environment
should be processed by the pam_env module by default, unless non-default file names are specified. This is not the case on my system and none of these files set or update the value of PATH.
Bash's man page says:
PATH The search path for commands. It is a colon-separated list of directories in which the shell looks for commands (see COMMAND EXECUTION below). A zero-length (null) directory name in the value of PATH indicates the current directory. A null directory name may appear as two adjacent colons, or as an initial or trailing colon. The default path is system-dependent, and is set by the administrator who installs bash. A common value is “/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin”.
This page of Bash documentation on gnu.org (https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html) explains when the various startup files are sourced. In my demonstration above, none of these files should be sourced since I'm neither spawning new shells in --login
or --interactive
, nor calling sudo with the --login
switch.
This ServerFault answer (https://serverfault.com/questions/833762/where-does-the-bash-path-on-centos-7-get-usr-local-bin-from#answer-838552) explains Bash's default values for the PATH variable. Even though it was answered for Bash on CentOS 7, the answer is still relevant to the Bash version packaged with CentOS 8. As per the accepted answer, the bash source config-top.h
has this:
/* The default value of the PATH variable. */
#ifndef DEFAULT_PATH_VALUE
#define DEFAULT_PATH_VALUE \
"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:."
#endif
/* The value for PATH when invoking `command -p'. This is only used when
the Posix.2 confstr () function, or CS_PATH define are not present. */
#ifndef STANDARD_UTILS_PATH
#define STANDARD_UTILS_PATH \
"/bin:/usr/bin:/sbin:/usr/sbin:/etc:/usr/etc"
#endif