Demystifying `root` on macOS, Part 2 — The `sudo` Command

As mentioned before, the recommended way of gaining super user privileges from the command line in macOS is the sudo command. The name means ‘super user do’ and will perform the following command with root privileges after verifying the user running sudo has the permission to do so.

How sudo works

sudo allows a user to execute a command with super user privileges, without needing to authenticate as the super user. The user has to authenticate as themself, however, and the sudo will check whether the user is authorized to use sudo.

For example, you want to run a command that requires super user privileges:

$ systemsetup -getremotelogin
You need administrator access to run this tool... exiting!

You can then repeat the command with sudo to run it with temporary super user privileges.

$ sudo systemsetup -getremotelogin
Password:
Remote Login: On

On macOS, administator users are allowed to use sudo.

A few notes on sudo:

  • you can type sudo !! as a short cut for ‘repeat the last command with sudo’. More details on this in my post “On Bash History Substitution”.
  • the first time you run sudo with an account on a Mac it will show a longer dialog with a warning or ‘lecture’. (You can change the lecture.)
  • the system will prompt for your password when executing a command with sudo. However, there is a 5 minute (300 seconds) ‘grace period’ where the sudo system caches your credentials and you do not have to re-enter the password. This grace period is limited to a given shell session, so if you open a new Terminal window, you will have to re-enter the password.
  • on macOS sudo will not work if your administrator account’s password is empty: Using the sudo command in Terminal requires an administrator password
  • use of sudo is logged. You can find the log entries in the Console.app by searching for process:sudo:
armin : TTY=ttys000 ; PWD=/Users/armin ; USER=root ; COMMAND=/bin/echo "#iamroot"

sudo and the Environment

sudo runs the command in the current shell environment. The user (or effective user ID) the command is run as switches to root:

$ whoami
armin
$ sudo whoami
root

The sudo environment changes some of the variables, while some will be passed through from your shell. To show this, create a short script:

#!/bin/bash

echo $USER
echo $HOME
echo $EUID

and run it as yourself and then with sudo:

$ chmod +x printenvs.sh
$ ./printenvs.sh 
armin
/Users/armin
501
$ sudo ./printenvs.sh 
Password:
root
/Users/armin
0

Some tools might not read environment variables and determine the user environment through different means. This may lead to some tools writing data to root’s home directory instead of the current users when running with sudo.

The defaults command is one example:

$ sudo defaults write com.apple.loginwindow LoginHook /path/to/script
~ $ defaults read com.apple.loginwindow LoginHook
2018-04-16 15:09:00.378 defaults[69217:3291382] 
The domain/default pair of (com.apple.loginwindow, LoginHook) does not exist
~ $ sudo !!
sudo defaults read com.apple.loginwindow LoginHook
/path/to/script
~ $ sudo plutil -p /var/root/Library/Preferences/com.apple.loginwindow.plist
{
  "LoginHook" => "/path/to/script"
}

Note: This form of customizing login behavior in macOS is deprecated (but still works as of 10.13). LaunchAgents are the preferred method to run scripts or processes at login. (More info here.) If you find yourself building custom LaunchAgents and LaunchDaemons frequently, you need to check out outset.

You have to be aware that running commands with sudo results in a different environment than when you run them directly.

root Shells

In most cases running a single command with sudo is sufficient. However, sometimes it can be convenient to have an interactive shell that runs with super user privileges.

There are two ways of achieving this withsudo:

When you run sudo -s it will invoke a new shell, running as root. The shell that is run is the default shell of your account. So when you have bash set as your shell (the default on macOS) you will get a bash shell running as root. Most other environment settings will remain the same:

$ sudo -s
Password:
# whoami
root
# echo $HOME
/Users/armin
# echo $SHELL
/bin/bash
# exit
$ 

Usually, the Terminal prompt is set up to change from the $ prompt to # when you are running with super user privileges, to remind you of the power you have right now and the danger you are in.

Note: learn how to configure your shell prompt.

To leave the root shell, just type exit.

Alternatively you can use sudo -i to invoke a root shell. With the -i option, the shell will be chosen from the default shell set for root user (/bin/sh on macOS) and will be set up as if the root user were logging in, ignoring your settings, like profile files etc.

$ sudo -i
Mac:~ root# whoami
root
Mac:~ root# echo $HOME
/var/root
Mac:~ root# echo $SHELL
/bin/sh

Note that my custom minimal shell prompt changes when I switch to root with sudo -i since it creates the root shell with the root user’s environment.

In most cases sudo -s should serve well. However, when you want to avoid any customization you might have set in your user environment and work in more pristine environment then it is good to know sudo -i exists.

sudo vs su

There is a different command which allows you to change the user: su (short for ‘switch user’). The main difference between these tools is how they verify if you are authorized to switch.

su will ask for credentials of the user you are switching to. So if you run su bob you need to have Bob’s credentials.

When you run su without a username, it assumes root. But since logging in as root is disabled by default on macOS, it will fail.

$ su
Password:
su: Sorry

sudo, on the other hand, will check its configuration files to see if your account is authorized to run the command as the given user. It asks for your credentials to verify you. You do not need the credentials of the other user, whether it is root or a different user.

Since the root account login is usually disabled on macOS, you cannot use su root - or su - to get a root shell. Use sudo -s or sudo -i instead.

sudo and Scripting

sudo is very useful when working interactively in the shell. However, when you are scripting workflows then you don’t want to encounter the interactive prompt for authentication.

We will look at strategies for privilege escalation (and the opposite) in scripts in the next post.

3 thoughts on “Demystifying `root` on macOS, Part 2 — The `sudo` Command”

  1. Informative and instructional post, as usual. For those of us who (contrary to the suggestion given in Part 1) run as a standard user, the process for using sudo is slightly more complicated. One needs to either su or login to an account with administrator privileges before invoking sudo. I tend to use login, as this puts me in a shell within a shell. When I no longer need admin/super user privileges, I logout (logout or Control-D), which drops me back in to the original user shell.

    1. I did not “recommend against the practice” as much as I doubt its efficiency in general. If that is what it takes for you to pause and and consider what this dialog is prompting for then it it’s a great solution for you! I believe most people will just enter the admin name and password with just as little consideration, though.

      If the hoops you need to jump through to sudo from the CLI with a non-Admin account are too annoying you could change the sudoers file, but I guess that would defeat the purpose.

      1. I chose the word “suggestion” fairly carefully in my note, but apparently not carefully enough; I fully understood you were saying that you personally run as Admin but acknowledged that others do not. I did not mean to infer you were criticizing those who ran as a standard user or even telling them that they shouldn’t. My post was more for completeness (especially if some of this article makes it into a future book of yours). My personal reason for sticking with a standard user account (which I adopted pre-SIP) is to prevent malware that might make its way on to my Mac from leveraging the fact that the current user has admin privileges. Post-SIP, that’s probably a pretty small security hole, but since I’m used to the workflow and the small overhead already, I keep it. As a side benefit, it also makes it clear when I am being prompted for user-level authorization (e.g., web site certificate exception) or admin-level, since the username will be filled in for the former but not for the latter. The Terminal is the only place where it is a (minor) inconvenience.

Leave a Reply

Your email address will not be published. Required fields are marked *