See Set-uid root program that runs a program as the user restrict for context.I've written a very short program that is intended to run the program specified in its arguments (argv) as the user named restrict (which exists). I want to make sure that there are no subtle bugs in the program, and that the program is as simple as possible.Is this program doing what I intend?Can someone use this program to do anything malicious? Assume that anything running as the user restrict is not able to do anything malicious, but anything running as the user root obviously can.Can this code be simplified, or (assuming the code is correct) can this code's correctness be made more obvious?The code (compiled with gcc --std=c11):#define _GNU_SOURCE#include <sys/types.h>#include <unistd.h>#include <pwd.h>#include <grp.h>int main (int argc, char **argv) {    if (argc < 2) return 1;    struct passwd *r = getpwnam(restrict);    gid_t supp[] = {};    // De-escalate privileges.    if (setgroups(0, supp)) return 1; // Remove supplementary groups.    if (setgid(r->pw_gid)) return 1; // Switch group to restrict.    if (setuid(r->pw_uid)) return 1; // Switch user to restrict.    char *env[] = {NULL}; // Delete all environment variables.    // Finally, attempt to replace this process.    execvpe(argv[1], &argv[1], env);    return 1; // Getting here means we failed.}The final binary will be owned by root, and will have the set-uid bit set, and will be executable by users in a particular group.The ultimate goal is to allow users in a particular group to run any executable as the user restrict.Alternatively, is there a better way to accomplish this goal?
Set-uid root program that runs a program as the user restrict (follow-up)
c;security;linux;unix
You should really check the return value of getpwnam(). If, for whatever reason, the user restrict is not found, your program will crash. I'm not sure whether a null dereference can be exploited (I think not), but it's a bad thing anyways.As already mentioned, giving a descriptive error message when execv fails is a good idea, too. In fact, do this on every possible error; no need for extra verbosity where you don't exepect an error -- in this case a perror with the failed function name should be enough.You probably don't want the user name configurable in any way for security reasons, but just to make it easier to configure at compile time, I'd suggest to #define it.All in all somehow like this:#define _GNU_SOURCE#include <stdio.h>#include <string.h>#include <errno.h>#include <sys/types.h>#include <unistd.h>#include <pwd.h>#include <grp.h>#define USERNAME restrictint main (int argc, char **argv){    if (argc < 2) return 1;    struct passwd *r = getpwnam(USERNAME);    if (!r)    {        perror(User  USERNAME  not found);        return 1;    }    gid_t supp[] = {};    if (setgroups(0, supp))    {        perror(setgroups);        return 1;    }    if (setgid(r->pw_gid))    {        perror(setgid);        return 1;    }    if (setuid(r->pw_uid))    {        perror(setuid);        return 1;    }    char *env[] = {NULL};    execvpe(argv[1], &argv[1], env);    /* the following is only reached on error */    fprintf(stderr, Cannot execute `%s': %s\n, argv[1],            strerror(errno));            return 1;}