Suid and Setgroups: Permission with Precision

Published

It’s a good idea to give only just enough permission to programs instead of sudoing everything. Download my wrapper from GitHub and then customize it to your needs.

Prerequisites

This project it self is fairly quick and easy, but it does touch on some of the more advanced aspects of Linux. You will need to have a working knowledge of the command line, Linux file permissions, and the C language.

Intro

Permissions are great for security. Whether you’re dealing with malicious programs, naughty users, or incompetent users; permissions help minimize potential damages. Unix permissions allow parts of a system to be sectioned off and set boundaries on what users and programs can do. If a user gets ransom ware, that ransom ware will only have write access to that users files. That user’s home directory and a few shared files may be taken hostage, but the files of the other users and the core of the system are kept safe.

For this tutorial we be using the Fusuma program as an example. Then we will show how this solution can be modified to work with any program and any user group(s).

The Problem

Input devices such as mice and keyboards are normally protected under the input group. This keeps keyloggers from gaining access to them. However, Fusuma needs access to the input group in order to work. Fusuma is meant to read events from the touchpad and translates those to hotkey events.

Naive Solutions

The way Fusuma’s readme recommends to fix this is by making the user a member of input. However, making the user a member of the input group opens up a potential vulnerability. If the user is a member of input then all the programs they run will also be members of input. If a the user runs malware, or if malware infects a user program, it will have unrestricted access to the mouse and keyboard. Thus you could get a keylogger spying on every thing you do.

A better solution is to give only Fusuma access to input. sudo can accomplish this but that requires entering a password every time Fusuma is run.

A similar effect to sudo can be achieved with the suid file permission. When and executable has it’s suid flag set, it runs with the permissions of the executable’s owner instead of the user who ran it. For example: if an executable in owned by root and has suid set, then whenever it is run, it runs as if it was run with sudo, even if the user who ran it lacks sudo privileges.

This is close, but there is one more issue. Using sudo or suid gives Fusuma full root user privileges when it only needs input privileges. If Fusuma is faulty or gets exploited there is no limit on the actions it may take and the damage it could do.

The Good Solution

The best solution (that I know of) is to combine suid and the setgroups system call to make a wrapper program that upgrades Fusuma’s permissions just enough for it to do it’s job. Setgroups allows a process to change what groups it is a member of so long as it has the rights to do so.

We will be using suid to give the wrapper sudo privileges. With those privileges the wrapper can join the input group. After the groups are set the wrapper gives up it’s sudo privilege and execs Fusuma. Fusuma will inherit the groups that the wrapper had.

The Code

This code is available on my GitHub account.

Main

First lets look at the main method to get an overview of the program.

int main (int argc, char *argv[]) {
    // Add input to our groups.
    gid_t input_gid = get_gid ("input");
    join_group (input_gid);

    // Drop sudo privileges.
    // They are no longer needed and should not be passed on to fusuma.
    drop_sudo ();

    // Exec Fusuma and pass along the command line args.
    exec_pass_args ("fusuma", argc, argv);
}

Finding the Group Id

In order to join a group we need to know its group id. You could just hard code it here, but different machines might be setup with different ids for the same group. The code would be less portable and less readable.

We can get the group id from the group’s name using the getgrnam method. If the name exists and there are no errors getgrnam returns a stuct containing the group id.

Here we check for errors and return the group id if everything is alright.

gid_t get_gid (char *name) {
    errno = 0;
    struct group *group_info = getgrnam (name);
    if (group_info == NULL) {
        if (errno == 0) {
            fprintf (stderr, "The group \"%s\" could not be found.\n", name);
            exit (1);
        } else {
            perror ("Could not get group info");
        }
    }
    return group_info->gr_gid;
}

Joining the Input Group

If we tell it to “setgroup to input” the process will lose all of it’s other groups. We only want to add input so we need to say “setgroups to input and the groups we are already a member of.”

void join_group (gid_t group_to_add) {
    // Get the groups we are currently in.
    int num_groups = getgroups (0, NULL);
    gid_t *groups = calloc (num_groups + 1, sizeof (gid_t));
    getgroups (num_groups, groups + 1);

    // Add the new group.
    groups[0] = group_to_add;

    setgroups (num_groups + 1, groups);
}

Dropping Excess Privileges

Here we drop the sudo privileges by setting the effective user id to be the same as the real user id.

void drop_sudo () {
    uid_t uid = getuid ();
    seteuid (uid);
}

Execing Fusuma and Passing Along Arguments

We may want to pass command line arguments to Fusuma through the wrapper. To do this a copy is made of the argv array (including the null terminator). The first argument should be the name of the program as it was run so this is set to “fusuma”. Finally we call execvp with the name of the command we want to run and the arguments to pass along.

void exec_pass_args (char *file, int argc, char **argv) {
    // Make a copy of argv including the NULL terminator.
    char **argv_new = calloc (argc + 1, sizeof (char **));
    memcpy (argv_new, argv, (argc + 1) * sizeof (char **));

    // Set the program name arg.
    argv_new[0] = file;

    execvp (file, argv_new);
    perror ("execvp returned");
}

Compiling

This program can be compiled with a simple call to gcc.

gcc main.c -o fusuma-s

Once compiled you will need to change the owner to root and set the suid flag like so:

sudo chown root:root fusuma-s
sudo chmod u+s fusuma-s

Now your done! Optionally, you may want to move the wrapper to one of the directories in $PATH such as /usr/local/bin/

sudo mv fusuma-s /usr/local/bin/fusuma-s

Customize to Your Needs

Now that you have learned how to add the input group to Fusuma it will be easy to do this for any other program and any other group(s). In the main method replace the strings "input" to the name of the group you need and "fusuma" to the name of the program you want to run and there you have it.

If you need more than one group, just duplicate this section for every group you want to add:

    gid_t input_gid = get_gid ("input");
    join_group (input_gid);