From 0d1e9ec217dc93a444c317faf9872817a1a212c5 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Tue, 12 Feb 2013 12:29:38 +0100 Subject: [PATCH 1/1] Initial version --- Makefile | 8 +++ suidgw.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 Makefile create mode 100644 suidgw.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b11b9e --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 + +all: suidgw + +suidgw: suidgw.c + +clean: + rm -f suidgw *.o diff --git a/suidgw.c b/suidgw.c new file mode 100644 index 0000000..e8196ac --- /dev/null +++ b/suidgw.c @@ -0,0 +1,176 @@ +/* + * A simple gateway for set-uid scripts + * + * (c) 2013 Martin Mares + */ + +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NONRET __attribute__((noreturn)) +#define UNUSED __attribute__((unused)) + +#ifdef DEBUG +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) do { } while (0) +#endif + +static char program_name[PATH_MAX]; +static char script_path[PATH_MAX]; +static uid_t use_uid; +static gid_t use_gid; + +static const char * const script_dirs[] = { + "/usr/local/lib/suidgw", + "/usr/lib/suidgw", +#ifdef DEBUG + "./scripts", +#endif + NULL +}; + +static char *sanitized_env[] = { + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + NULL +}; + +static void NONRET die(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + fprintf(stderr, "suidgw: "); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + exit(1); +} + +static void sanitize_fds(void) +{ + // Make sure that nobody is playing dirty games with our file descriptors + int fd; + do + { + fd = open("/dev/null", O_RDWR); + if (fd < 0) + die("Cannot open /dev/null: %m"); + } + while (fd <= 2); + close(fd); +} + +static bool get_program_name(const char *arg0) +{ + // If arg0 is a path, extract the last component + const char *p = strrchr(arg0, '/'); + if (p) + p++; + else + p = arg0; + + // Reject empty and oversized names + if (!p[0] || strlen(p) >= PATH_MAX) + return 0; + + // Reject invalid characters + for (const char *q = p; *q; q++) + { + int c = *q; + if (! (c >= 'a' && c <= 'z' || + c >= 'A' && c <= 'Z' || + c >= '0' && c <= '9' || + c == '-' || + c == '_')) + return 0; + } + + DBG("Program name: <%s>\n", p); + strcpy(program_name, p); + return 1; +} + +static void find_script(void) +{ + for (int i = 0; script_dirs[i]; i++) + { + int len = snprintf(script_path, PATH_MAX, "%s/%s", script_dirs[i], program_name); + if (len >= PATH_MAX) + continue; + DBG("Trying <%s>\n", script_path); + if (access(script_path, X_OK) >= 0) + { + DBG("Found <%s>\n", script_path); + return; + } + if (errno != ENOENT) + die("Cannot run %s: %m", script_path); + } + + die("Unable to find script %s", program_name); +} + +static void check_stat(void) +{ + struct stat st; + if (stat(script_path, &st) < 0) + die("Cannot stat %s: %m", script_path); + + if (!S_ISREG(st.st_mode)) + die("Not a regular file: %s", script_path); + if (!(st.st_mode & (S_ISUID | S_ISGID))) + die("Missing suid/sgid: %s", script_path); + + use_uid = (st.st_mode & S_ISUID) ? st.st_uid : getuid(); + use_gid = (st.st_mode & S_ISGID) ? st.st_gid : getgid(); + DBG("Will use uid=%d gid=%d\n", (int) use_uid, (int) use_gid); +} + +static void switch_ugid(void) +{ + if (setegid(use_gid) < 0) + die("Failed to set group id: %m"); + + if (seteuid(use_uid) < 0) + die("Failed to set user id: %m"); +} + +int main(int argc UNUSED, char **argv) +{ + sanitize_fds(); + + if (geteuid()) + die("Must be run setuid"); + + struct passwd *pwd = getpwuid(getuid()); + if (!pwd) + die("You don't exist"); + + openlog("suidgw", LOG_NDELAY | LOG_PID, LOG_AUTH); + + if (!get_program_name(argv[0])) + die("Unable to parse program name %s", argv[0]); + + find_script(); + check_stat(); + switch_ugid(); + + syslog(LOG_INFO, "User %s executes %s with uid=%d, gid=%d", + pwd->pw_name, script_path, (int) use_uid, (int) use_gid); + + if (execve(script_path, argv, sanitized_env) < 0) + die("Cannot execute %s: %m", script_path); + + die("This must never happen"); +} -- 2.39.2