One of the exercises of “The Linux Programming Interface” chapter 38 is about implementing program, called douser, that should have the same functionality as the sudo progam. This means that if you run $ douser whoami, this should asks for the root user password and, if the password matches, should run the command whoami, which would print root. If the -u <user> flag is used, douser should ask for the user password and execute whoami on behaf of that user, printing its name.

To be able to authenticate users on the system, our program must read the /etc/shadow file, which is only readable by the root user. This means that it must run as root. But it would be pretty bad if we needed to know the root password to run execute a command as an unprivileged user, e.g, $ douser -u ubuntu ls. That is where the set-user-id permission bit comes to our rescue.

Set-User-ID programs

A Set-User-ID program sets the process effective user ID to the same as the user ID that owns the executable file. So it does not matter what user executes the program, the process will always run as the owner of the executable.

If we inspect our sudo binary we can see that the set-user-ID permission bit is set on the file:

$ ls -l /usr/bin/sudo
-rwsr-xr-x 1 root root 155008 Oct 14  2016 /usr/bin/sudo

We can see that it shows an s instead of x on the execution permission bit.

The implementation

My full implementation is available on github. In this section I will discuss the most relevant parts of it.

Authenticating

User authenticating is the most sensitive part of our program and is handled by the authenticate function. This function uses getpwnam(username) to obtain a struct contained the fields available at the /etc/passwd file on linux for the user with that particular username.

The user password is actually stored in a different file (/etc/shadow), read only by the root user. We use the function getspnam(username) to obtain a struct representing the data on that file.

Then we use the function getpass to prompt the user for the password and the function crypt to encrypt the password. If the input password and the real password matches, authenticate returns 0.

/*
    authenticate prompts the user for the password
    and validates it, returning 0 on success.
*/
int 
authenticate(char *username) 
{
    struct spwd *spwd;
    struct passwd *pwd;
    char *encrypted;
    Boolean authOk;

    pwd = getpwnam(username);
    if (pwd == NULL) 
        fatal("could not get password file");

    spwd = getspnam(username);
    if (spwd == NULL && errno == EACCES)
        fatal("no permission to read shadow password file");
    
    if (spwd != NULL)
        pwd->pw_passwd = spwd->sp_pwdp;
    
    encrypted = crypt(getpass("Password:"), pwd->pw_passwd);

    return strcmp(encrypted, pwd->pw_passwd);
}

Executing the user provided command

After authenticating the user, we use the setuid syscall to set the effective user ID of the running process to the ID of the authenticated user.

Following that, we use fork to create a child process that will have its text segment replaced by using the execvpe library function. We replace the environment variables setting $PATH to known safe locations. The parent process exits right after forking.

pwd = getpwnam(username);
if (pwd == NULL)
    fatal("unable to retrieve user info");

if (setuid(pwd->pw_uid) != 0)   
    errExit("setuid");

pid = fork();
if (pid == -1) {
    errExit("fork");
} else if (pid != 0) {
    execvpe(argv[optind], &argv[optind], envp);
    errExit("execvpe");
}
exit(EXIT_SUCCESS);

Usage

After compiling the code, one must login as root and properly set the douser binary using chown root:root ./douser and chmod u+s ./douser. The latter turns on the set-user-ID permission bit on the executable.

After the setup, run ./douser whoami and it should print root. Woot!

Disclaimer

The implementation is far from being a complete copy of sudo (that is probably obvious but wanted to make it clear). The real sudo implementation can be read here.

I decided to share my implementation and some of my reasoning as a way to both share the knowlged and also enforce it. Would love to get some feedback.