Monday, April 13, 2015

How to drop root permissions in C

Sometimes it is necessary to run an application with root permissions in order to do some elevated work like binding to ports lower than 1024 or writing to files in /var/log, etc.  After the elevated work is finished, it is good practice to drop the root privileges and continue with the execution path as a non-privileged user. The motivation is based on security reasons: if ever an attacker takes control over your application, it should not give him control over the whole system.

There are two approaches to dropping root privileges:
  1. drop the privileges temporarily, and get them back at a later time - in case the application needs to perform some elevated work after initialization
  2. drop the privileges permanently - in case the application has finished the elevated work at initialization

Dropping root permissions temporarily

The following code snippet does the privilege dropping in C:
int drop_root_effective(void)
{
    struct passwd *pw;
    pw = getpwnam("nobody");
    assert(pw != NULL);

    if(pw == NULL)
    {
        return 0;
    }

    if(setegid(pw->pw_gid) != 0 ||
       seteuid(pw->pw_uid) != 0
       )
    {
        return 0;
    }
    return 1;
}
The function above changes the effective group and user IDs of the program to the user "nobody" - which is a default non-privileged user on Linux systems. It is important to drop first the group id via setegid and then the user id via seteuid, as after dropping the user id, the call to setegid will fail (you lost your permission).
In case the application needs to do some processing using elevated privileges, it can gain them back using the following snippet:
int get_root(void)
{
    if(seteuid(0) != 0 ||
       setegid(0) != 0)
    {
        return 0;
    }
    return 1;
}
Calling setegid and seteuid with 0 as parameter will gain you back the root privileges.


Dropping root permissions permanently

The C code is as follows:
int drop_root_permanent(void)
{
    struct passwd *pw;
    pw = getpwnam("nobody");
    assert(pw != NULL);

    if(pw == NULL)
    {
        return 0;
    }

    if(setgid(pw->pw_gid) != 0 ||
       setuid(pw->pw_uid) != 0 )
    {
        return 0;
    }
    return 1;
}
After calling setgid and setuid you shouldn't be able to get the root permissions back. If paranoid, you can double-check by using the get_root function above, with a negative check.

On newer kernels one can stop using root permissions by setting different capabilities for the application.

No comments:

Post a Comment