25

I'm new here and new to bash/linux.

My teacher gave me an assignment to allow a script to be run only when you're "really" root and not when you're using sudo. After two hours of searching and trying I'm beginning to think he's trolling me. Allowing only root is easy, but how do I exclude users that run it with sudo?

This is what I have:

if [[ $EUID -ne 0 ]]; then
  echo "You must be root to run this script."
  exit
fi
13
  • 2
    Take away sudo all and enumerate only the commands a user should be allowed to run as root, excluding your script from that list.
    – mikem
    Dec 30, 2020 at 6:54
  • 6
    How would you stop the sudo user from removing the restriction from the script once you have put it in place? There is no difference between the root user logged in from a console and the root user accessing the system via sudo. The foolproof solution woud be to simply uninstall sudo.
    – Kusalananda
    Dec 30, 2020 at 8:06
  • 1
    I think that any answer has to consider the possibility that the lecturer is ignoring (or hasn't thought of) the possibility of running sudo su and intends that the solution prevents the script from starting to run rather than aborting if it doesn't like its execution environment. As such I agree with @mikem and would highlight man sudoers -> SECURITY NOTES since some of their caveats aren't relevant if the script can't be edited or renamed. Dec 30, 2020 at 9:45
  • 6
    I can see a couple of possibilities here. 1. Your teacher is an idiot who didn't/doesn't realize how hard it would be, or how fragile his/her pet "solution" really is. 2. Your teacher is using this as an example to demonstrate the inadequacy of the students attempted solutions. 3. Your teacher is a jerk who decided to throw a neophyte to the wolves just for fun (you mention the trolling possibility in your question). In any case, I question the motives behind such an assignment... Dec 30, 2020 at 12:45
  • 7
    Nothing about sudo requires it to give you root access; that's just the default behavior everyone is familiar with. sudo can be configured to allow you to do only very specific things, including not gain root access at all.
    – chepner
    Dec 30, 2020 at 17:09

8 Answers 8

24

The only way I could think of is to check one of the SUDO_* environment variables set by sudo:

#!/usr/bin/env sh

if [ "$(id -u)" -eq 0 ]
then
    if [ -n "$SUDO_USER" ]
    then
        printf "This script has to run as root (not sudo)\n" >&2
        exit 1
    fi
    printf "OK, script run as root (not sudo)\n"
else
    printf "This script has to run as root\n" >&2
    exit 1
fi

Notice that of course this solution is not future proof as you cannot stop anyone from setting a variable before running the script:

$ su
Password:
# SUDO_USER=whatever ./root.sh
This script has to run as root (not sudo)
# ./root.sh
OK, script run as root (not sudo)
5
  • 10
    +1, but this can still be tricked by something like sudo bash -c 'unset SUDO_USER=; my_command;'. I would not rely on it.
    – pLumo
    Dec 29, 2020 at 14:07
  • It might not be good for actual work, but it will probably get my assignment done, which is what matters at the moment. Thank you very much!
    – PK001
    Dec 29, 2020 at 14:40
  • The paranoids among us would begin with env | grep -E 'SUDO|PPID|PID, and validate each via /usr/bin/ps and other command line tools. One could check each of env | sort, if one wished.
    – waltinator
    Dec 30, 2020 at 1:10
  • 4
    Care! If user run sudo su -, variables SUDO_* doesn't exist anymore! See unix.stackexchange.com/a/626764/27653 Dec 30, 2020 at 7:45
  • 2
    Care, if user run screen,. detach then re-attach, you can't see anything more than a root login shell!! See last paragraph at: unix.stackexchange.com/a/626764/27653 Dec 31, 2020 at 11:37
20

Another option would be to check if the grandparent process name is "sudo":

#!/bin/sh
if [ "$(id -u)" -eq 0 ]
then
  if [ $(ps -o comm= -p $(ps -o ppid= -p $$)) = "sudo" ]
  then
    echo Running under sudo
  else
    echo Running as root and not via sudo
  fi
else
  echo Not running as root
fi
4
  • 5
    This fails when run under "sudo su" or "sudo bash". I think you need to check multiple levels of processes.
    – piojo
    Dec 30, 2020 at 9:22
  • 3
    Granted; you could extend this to loop over the PPID process tree, but I thought I'd cover the most common usage of ./script or sudo ./script, to point out the core idea of checking for "sudo" as being the parent process or not.
    – Jeff Schaller
    Dec 30, 2020 at 13:28
  • 2
    This can be circumvented by sudoing then running a process that disowns right? Dec 30, 2020 at 23:51
  • 1
    This answer attempts to address the question "how do I exclude users that run it with sudo?" in the straightforward interpretation, not as an exhaustive security measure. I don't know what the professor was aiming for, but maybe parent processes were on the syllabus and they thought this would be an instructive exercise. Any simple measure can be fooled, as we've seen so far with the straightforward SUDO_USER environment variable and these process checks.
    – Jeff Schaller
    Dec 30, 2020 at 23:57
13

The information about which user logged in is available in /proc/self/loginuid. EDIT due to comments: That file does not seem to exist on all systems. I tested and it is available on Centos 6, Fedora 32, Fedora 33 and Ubuntu 20.04, all in standard x86_64 setups. If we login as our user and than use sudo or su to become root, this will not change /proc/self/loginuid and it will be some non-zero value. If we directly log in as root, then cat /proc/self/loginuid will return 0. Note that this file can NOT be modified, even root cannot do this. EDIT due to Stéphane Chazelas' comment: Root can overwrite this file using echo 0 > /proc/self/loginuid. However, this can be prevented by setting auditctl --loginuid-immutable.

The script to check for real root (if auditctl --loginuid-immutable is set) could look like

#!/bin/bash
loginuid=$(cat /proc/self/loginuid)
echo $loginuid
if [[ $loginuid -ne 0 ]]; then
    echo "You did not log in as root."
    exit
fi
14
  • 4
    I believe this requires CONFIG_AUDIT=y, on Linux, but on such a system this is the right answer (though I imagine the actual goal was to read the manual and find SUDO_USER). Dec 31, 2020 at 4:24
  • 1
    If you would like to see a script that defeats /proc/self/loginuid I think I can provide.
    – Joshua
    Dec 31, 2020 at 5:28
  • 2
    sudo sh -c 'echo 0 > /proc/self/loginuid && cat /proc/self/loginuid' outputs 0 for me on Ubuntu 20.04 with a 5.4.0-58-generic Linux kernel Dec 31, 2020 at 11:58
  • 3
    Can be changed with auditctl --loginuid-immutable. Dec 31, 2020 at 12:10
  • 1
    auditctl --loginuid-immutable doesn't make this perfect either. In particular, it only prevents you from changing your loginuid once it's been set, but there are ways that you can gain control of, or start, a process where it never gets set at all. Jan 1, 2021 at 1:42
6

Avoid sudo in root bash script?

Preamble: Care sharing root account!!

Unfortunely, there is no resistant way... Please read carefuly upto last paragraph

Once you give root access to someone, they could do anything, including editing your script!!

For sample, if user hit sudo su -, then variables SUDO_* doesn't exist anymore...

First quick way using pstree

So simplier way to search for sudo presence in whole current tree, seem to use pstree:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
pstree -s $$ | grep -q '\bsudo\b' && die "Can't be run under sudo"

With ps only, you could loop over ps ho ppid:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
pid=$$
while read pid name foo < <(ps ho ppid,cmd $pid) && ((pid>1));do
    [ "$name" = "sudo" ] && die "Can't be run under sudo"
done

Regarding comment about renamed sudo

If sudo command is renamed or copied, then instead of looking for command name, look for UID in whole parent tree. So script is same than previous, but searching for UID >= 1000 in parent tree:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
pid=$$
while read pid uid < <(ps ho ppid,uid $pid) && ((pid>1));do
    ((uid>999)) && die "Can't be run under sudo"
done

Because we are speaking about Un*x

To be correct, avoid using fixed statical datas, use of UID_MIN from /etc/login.defs:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
while read fld val;do
    case $fld in UID_MIN ) UIDMIN=$val ;break ;; esac
done </etc/login.defs
((UIDMIN)) || die Getting UID_MIN.
pid=$$
while read pid uid < <(ps ho ppid,uid $pid) && ((pid>1));do
    (( uid >= UIDMIN )) && die "Can't be run under sudo"
done

Workaround for executing this by using sudo anyway

But all this is someting fragile:

$ sudo su -
# screen -D -R  # apt install screen if not installed

Now hit Ctrl + a , then d to be detached. Type exit or hit Ctrl + d to return in user mode...

Then simply:

$ sudo screen -x

Now, you'll be logged in a root login session. No trace of any sudo.

# ps $PPID
 PID TTY      STAT   TIME COMMAND
 26367 ?        Ss     0:00 SCREEN -D -R
# ps ho ppid $PPID
 1
# set | grep SUDO
                                         # <-- nothing here!

Conclusion

As chepner rightly commented: sudo is drawn to give specifics access to specifics tools:

Nothing about sudo requires it to give you root access; that's just the default behavior everyone is familiar with. sudo can be configured to allow you to do only very specific things, including not gain root access at all – chepner

Care to configure them correctly, before using fragile workaround!

See:

apropos sudo

And read carefully

man sudo.conf
man sudoers

Regarding logname

Have a look at correct Stéphane Chazelas's answer! This could be the best answer for a homework!!

Again, lot of workaround, like: echo 0 > /proc/self/loginuid...

About /proc/self/loginuid under Linux

Please read interesting laolux's answer about this!

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
read lUid </proc/self/loginuid || die "Can't access procfile"
((lUid)) && die "You must be logged as root."

( This syntax avoid forks! )

But anyway

  • script could be copied and edited
  • depending on config/kernel, this kernel entry could be spoofed
  • Sudoer could create cron entry for initiating special screen session as root. (cron and screen are not the only way for doing things like this! Just the first coming to my mind. )
1
  • 2
    In common with other solutions, this fails if the sudo command has been renamed Dec 30, 2020 at 10:09
4

I suggest checking out process list strings and see if the user is running the program using sudo

contype=`tty | cut -d '/' -f 3`
tty="$contype/`tty | cut -d '/' -f 4`"

if [ "$(id -u)" -eq 0 ]
then
    res=`ps ax | grep "$tty" | grep "$0" | grep "sudo"`
    if [ $? == 0 ]
    then
        echo "You should not run the script using sudo!"
        exit 2
    else
        echo "Done."
    fi
else
    echo "You are not root. Run this script as root."
    exit 2
fi

That variable res is simply for you if you want to filter results again.

Arkadusz code is nice but unfortunately it can be bypassed really easy...

4
  • This fails if the user is logged in on tty/1 or another physical device.
    – doneal24
    Dec 29, 2020 at 17:40
  • Thanks for alerting me. I've edited the code...
    – zbx0310
    Dec 29, 2020 at 17:48
  • Bypassable by running screen inside of sudo Jan 1, 2021 at 1:43
  • Please try this: IFS=/ read foo{,} tty < <(tty), then ps --tty $tty fw! Anyway, this fail if sudo is renamed, if user run sudo su -, then screen... Jan 5, 2021 at 10:41
4
if [ "$(id -u)" -ne 0 ] || [ "$(logname)" != root ]; then
  echo >&2 "You either don't have superuser privilege or didn't login as root"
  exit 1
fi

Not foolproof as any process with superuser privilege that can run any command can do anything to work around anything, but may be close to what the teacher's expecting and is standard.

1
2

Just to post something completely different:

if ! tty -s 2>&0
then    echo "Need to run with standard error on a terminal"
        exit 1
fi

if [ x"`stat --printf=%u $(tty)`" != x"0" ]
then    echo "Must be logged in as root. Can't use sudo or su here."
        exit 1
fi

This works by checking if the terminal device of the logged-in user is owned by root. As with all other answers, it can be fooled if really desired.

-2
man sudoers 

(the details may vary from system to system)

"The sudoers policy plugin determines a user's sudo privileges. It is the default sudo policy plugin. The policy is driven by the /etc/sudoers file..."

"The sudoers file is composed of two types of entries: aliases (basically variables) and user specifications (which specify who may run what)."

Remove all users from /etc/sudoers (details omitted), and the sudo command will not work for anyone. After that, only root can run commands that require the root privilege.

There are configuration options to allow a user to run commands except a particular command, and vice versa.

Please note that we are doing your homework for you and we should not do that again.

2
  • 1
    This misses the point of the question entirely.
    – Mark
    Dec 30, 2020 at 23:23
  • 1
    @Mark I knew someone would say that. The question doesn't specify that the script itself has to "decide" who can run it, just that root can run it but not a sudo user. It occurred to me that this was what his instructor was looking for. Maybe I'm wrong.
    – Wastrel
    Dec 31, 2020 at 18:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.