UID
and EUID
variables are initialized by Bash at shell startup, but my tests indicate that if they are already in the environment then they will not be changed. The output of id -ru
and id -u
is reliable (unless id
is counterfeit). See for yourself:
env UID=foo EUID=bar bash -c 'printf "UID: %s\nEUID: %s\nid -ru: %s\nid -u: %s\n" \"$UID" "$EUID" "$(id -ru)" "$(id -u)"'
Shells other than Bash may or may not set UID
and EUID
, so in general id
is the right tool.
You designed your script to be run by sudo
. Depending on the settings in the sudoers
file (we will get to them), sudo
may or may not pass the variables from the environment of the invoking user to the environment of the script. Any of these may or may not fool your script:
env UID=foo EUID=bar sudo /path/to/your_scriptsudo UID=foo EUID=bar /path/to/your_script
SUDO_USER
and SUDO_UID
are set by sudo
itself. Values from the environment of sudo
are always overwritten, so these attempts to fool the script won't fool it:
env SUDO_USER= sudo /path/to/your_scriptSUDO_USER= sudo /path/to/your_script
The following example explicitly tells sudo
itself to set the variable empty and if it works (again, depending on sudoers
) then it will fool the script:
sudo SUDO_USER= /path/to/your_script
In sudoers
the easiest way to disallow users changing the variables while calling your script is to use NOSETENV
, e.g.:
ALL ALL=(ALL) NOSETENV: /path/to/your_script
Note the line shall be after more general lines.
With NOSETENV
and if the script is interpreted by Bash, the relevant variables available inside your script run with sudo
should be reliable. Still there is no point in checking UID
against EUID
, because normally sudo
sets both to the ID of the target user. The two IDs differ for setuid processes, so e.g. for sudo
itself. You can make sudo
keep the difference by specifying stay_setuid
in sudoers
, but (if set globally) this may have other consequences. There is no need, since you have reliable SUDO_USER
and SUDO_UID
which tell you the invoking user.
Now in your script you can test as follows:
- Check if
$EUID
(or$(id -u)
in general) expands to0
or not, to tell if the script is elevated or not. - Check if
$SUDO_UID
(or$SUDO_USER
) is not empty, to tell if there issudo
in play. - Check if
$SUDO_UID
expands to0
or not, to tell if the invoking user is elevated or not. Alternatively check if$SUDO_USER
expands toroot
or not, to tell if the invoking user is namedroot
or not; but note the two tests are not equivalent.
Finally note that users may still fool your script:
The root user or anyone that can become root, or anyone that can use
sudo
to run arbitrary commands as root can run your script in arbitrary environment as root, so if they want to fool your script then they will. There is no point in protecting specifically the script against this, as such users can run anything as root anyway, so if they are irresponsible or rogue then it's a bigger problem.If a user manages to run the script without
sudo
, he or she can run it withoutsudo
in arbitrary environment (includingPATH
orLD_PRELOAD
that changes the behavior ofid
), so the script may initially "think" it is elevated, run commands that don't require being elevated and fail at commands that require being elevated. Depending on what the script does, this may or may not lead to problems. In general a regular user can only hurt him- or herself, or resources he or she has write access to. Users can hurt themselves even without your script.It is not your job to protect users from themselves; and fooling your script takes deliberate effort, it is unlikely this will happen by accident; so if users hurt themselves by fooling your script, it will be at their own wish. But if you really want to make this difficult for them then make the script non-executable for general public (
chmod o-x …
) and even not readable for general public (chmod o-r …
).