setuid-wrapper

This is example C source code for a setuid wrapper program. This can be useful if you have a script that you want unprivileged users to be able to run with root permissions. All the usual security disclaimers apply.

/*
 * setuid-wrapper.c
 * by Nathan Rosenquist
 * https://nathanrosenquist.com/setuid-wrapper/
 *
 * This is a setuid program that simply becomes root and calls the
 * given script / executable. You will need to adapt it to your
 * particular situation. In particular, you will probably want to change
 * the following #define statements:
 *
 *   PROGRAM_NAME   this should match whatever you rename this program to
 *                  (e.g. my-program)
 *
 *   CMD_PATH       this should point to the full path of your script
 *                  (e.g. /usr/local/bin/my-program.py)
 *
 * Running programs setuid introduces the possibility of a root compromise.
 * Please only use this program if you already know what you are doing, and
 * have a concrete plan to keep your system safe. Please make sure you
 * understand all of the concepts involved with this program and the
 * possible failure modes before using this in a production environment.
 * You have been warned!
 *
 * To compile:
 *
 * cc -Wall -Werror --pedantic --ansi --static setuid-wrapper.c -o my-program
 *
 * To install:
 *
 * sudo cp my-program /usr/local/bin/my-program
 * sudo chown root:my-allowed-group /usr/local/bin/my-program
 * sudo chmod 4750 /usr/local/bin/my-program
 *
 * This code is placed in the public domain.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

/* the name of this wrapper program */
#define PROGRAM_NAME "setuid-wrapper"

/* the command to execute */
#define CMD_PATH "/usr/bin/id"

/* exit code */
#define EXIT_FAIL 1

/* environment variable pointer */
extern char **environ;

/* function prototypes */
int setenv(const char *name, const char *value, int overwrite);
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
int execve(const char *filename, char *const argv[], char *const envp[]);
void perror(const char *s);

/* main program */
int main(int argc, char *argv[]) {
    /* declare variable to capture system call results */
    int result;

    /* construct the command that we want to execute */
    char *cmd[2];
    cmd[0] = CMD_PATH;
    cmd[1] = NULL;

    /* clear all existing environment variables */
    environ = NULL;

    /* overwrite the PATH environment variable with something sensible */
    result = setenv(
        "PATH",
        "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
        1
    );
    if (0 != result) {
        perror(PROGRAM_NAME);
        return EXIT_FAIL;
    }

    /* set real and effective user id to root */
    result = setreuid(0, 0);
    if (0 != result) {
        perror(PROGRAM_NAME);
        return EXIT_FAIL;
    }

    /* set real and effective group id to root */
    result = setregid(0, 0);
    if (0 != result) {
        perror(PROGRAM_NAME);
        return EXIT_FAIL;
    }

    /* exec command */
    result = execve(cmd[0], cmd, NULL);
    if (0 != result) {
        perror(PROGRAM_NAME);
        return EXIT_FAIL;
    }

    fprintf(stderr, "%s: could not execute command\n", PROGRAM_NAME);
    return EXIT_FAIL;
}

Download

setuid-wrapper.c