DEV Community

Safia Abdalla
Safia Abdalla

Posted on

What happens when you run sudo?

So, what the heck happens when you sudo?

If you’ve been working with computers, and specifically Unix-like systems, you’ve probably used the sudo command. It stands for s uper u ser d o. It runs whatever command you want to run as an administrator. It’s often used to give you the privilege to edit system files (like /etc/hosts) or to add directories to system directories and so on.

But how does it work?

If you’ve been around this blog long enough, you know what’s coming next. And you’re either terrified or excited.

It’s time to read some code!

Any command you run on a command line is likely implemented as C program. So, to figure out how sudo works under the hood, we just have to read some C code.

shudders uncontrollably

The home page for the sudo command can be found here. It appears that the latest stable release of sudo, as of writing this, is 1.8.22 which was released on January 16th, 2018. That’s surprisingly recent. In any case, I found the most recent version of the sudo command on GitHub. Time to dive in!

This is my first time reading the C code for a Unix command, so I have no idea where to start. Ideally, I’d like to find the entry point for the application. I assume that this is going to be the main function in some file called sudo.c. I found such an entry point in this file. The first couple of lines of the function seem to be related to variable setup and initialization mostly.

int nargc, ok, status = 0;
char **nargv,** env_add;
char **user_info,** command_info, **argv_out,** user_env_out;
struct sudo_settings *settings;
struct plugin_container *plugin, *next;
sigset_t mask;
debug_decl_vars(main, SUDO_DEBUG_MAIN)

/* Make sure fds 0-2 are open and do OS-specific initialization. */
fix_fds();
os_init(argc, argv, envp);

setlocale(LC_ALL, "");
bindtextdomain(PACKAGE_NAME, LOCALEDIR);
textdomain(PACKAGE_NAME);

(void) tzset();

I could spend a lot of time looking into what each of these functions is, but life is short, and I don’t wanna go down that rabbit hole today. I skimmed through a couple more lines until I ran into a function that piqued my interest.

/* Make sure we are setuid root. */
  sudo_check_suid(argc > 0 ? argv[0] : "sudo");

Intriguing! What does setuid mean? It’s a way to set the user ID (or the group ID but let’s not get into that here) of the command that is going to be run. Maybe you want to run a command under your standard user (captainsafia), or maybe you wanna do it under a sudo. You would use setuid to accomplish this. So the sudo_check_suid function invokes the geteuid function which gets the user ID of the current running process. It checks to see if that is equal to the ROOT_UID (the user id of the root user) if not, it checks to see if the sudo binary is stored in the user’s PATH variable, which similarly, elevates the user to sudo.

If it does find the sudo binary in the user’s path, it sets a qualified variable to true and checks to see if the sudo command is running properly by calling stat and examining the properties of the stat struct. Here’s the sudo_check_suid function in its entirety.

static void
sudo_check_suid(const char *sudo)
{
    char pathbuf[PATH_MAX];
    struct stat sb;
    bool qualified;
    debug_decl(sudo_check_suid, SUDO_DEBUG_PCOMM)

    if (geteuid() != ROOT_UID) {
    /* Search for sudo binary in PATH if not fully qualified. */
    qualified = strchr(sudo, '/') != NULL;
    if (!qualified) {
        char *path = getenv_unhooked("PATH");
        if (path != NULL) {
        const char *cp, *ep;
        const char *pathend = path + strlen(path);

        for (cp = sudo_strsplit(path, pathend, ":", &ep); cp != NULL;
            cp = sudo_strsplit(NULL, pathend, ":", &ep)) {

            int len = snprintf(pathbuf, sizeof(pathbuf), "%.*s/%s",
            (int)(ep - cp), cp, sudo);
            if (len <= 0 || (size_t)len >= sizeof(pathbuf))
            continue;
            if (access(pathbuf, X_OK) == 0) {
            sudo = pathbuf;
            qualified = true;
            break;
            }
        }
        }
    }

    if (qualified && stat(sudo, &sb) == 0) {
        /* Try to determine why sudo was not running as root. */
        if (sb.st_uid != ROOT_UID || !ISSET(sb.st_mode, S_ISUID)) {
        sudo_fatalx(
            U_("%s must be owned by uid %d and have the setuid bit set"),
            sudo, ROOT_UID);
        } else {
        sudo_fatalx(U_("effective uid is not %d, is %s on a file system "
            "with the 'nosuid' option set or an NFS file system without"
            " root privileges?"), ROOT_UID, sudo);
        }
    } else {
        sudo_fatalx(
        U_("effective uid is not %d, is sudo installed setuid root?"),
        ROOT_UID);
    }
    }
    debug_return;
}

Yes! It’s a lot of code. But don’t be frightened. C code is often excessively verbose because you have to do a lot of things like string concatenation and splitting and error checking manually (errr, more manually than you would in other languages). Higher level languages take care of a lot of this stuff for you. Thank you, Python and JavaScript (and others)!

At this point, I’m scrolling through the rest of the code for the main function in sudo.c and, boy, is there a lot going on! Most of it is setting configurations and warning the user if things aren’t set up correctly.

There was one line of code that caught my eye.

if (!sudo_load_plugins(&policy_plugin, &io_plugins))
    sudo_fatalx(U_("fatal error, unable to load plugins"));

sudo_load_plugins? What plugins are we loading? What’s going on here? It’s time to investigate!

It turns out that sudo_load_plugins is defined in another file. This function was really long, so I decided just to read the function definition to see if it could provide some information about what the function might do. Usually, in C, you pass pointers to objects into a function. The function then modifies the objects pointed to by those pointers and returns some status code. So by looking at the function definition, I can get a pretty good sense of what the function is doing. In this case, the function definition for sudo_load_plugins looks like this.

static bool
sudo_load_plugin(struct plugin_container *policy_plugin,
    struct plugin_container_list *io_plugins, struct plugin_info *info)

So the three structs that are modified in this function are the plugin_container, plugin_container_list, and plugin_info. I did some more digging around the code. I won’t narrate all that I did here because it would take a while but I’ll just summarize it here.

The sudo_load_plugins function is responsible for loading policy plugins. The policy plugins are used to define what the privileges the sudo command has for a particular user. If you were administering a Unix system, you could configure sudo never to be able to execute a certain command under sudo for any user or to add logging for sessions. That’s useful!

OK! Back to looking at the main function. The next interesting bit of code is a giant switch statement. It checks the value of the bitwise and the sudo_mode and MODE_MASK variables against a set of constants.

switch (sudo_mode & MODE_MASK) {

I won’t go into the details of the code line by line, but here’s the overall gist. As it turns out, the sudo command can be run in a variety of different modes. You can run it in one mode that allows you to preserve the SHELL variables used by the command (MODE_SHELL).

The last portion of the main function has an extra helpful comment to explain what is going on.

/*
* If the command was terminated by a signal, sudo needs to terminated
* the same way. Otherwise, the shell may ignore a keyboard-generated
* signal. However, we want to avoid having sudo dump core itself.
*/
if (WIFSIGNALED(status)) {
    struct sigaction sa;

So basically, if the command that we are running is sudo exits unexpectedly than so should sudo.

Phew! A lot was going on in that function. And to be honest, even more in the sudo command in general. I learned that there is a lot more to sudo than just sudo !!. I’m not a Unix system administrator or anything, so I wasn’t aware of all the ways that sudo can be used to monitor and restrict access to privileged commands across Unix systems. The more you know!

Thank you to Todd Miller, the maintainer of sudo, for maintaining such a valuable tool!

Latest comments (0)