]> mj.ucw.cz Git - suidgw.git/commitdiff
Initial version
authorMartin Mares <mj@ucw.cz>
Tue, 12 Feb 2013 11:29:38 +0000 (12:29 +0100)
committerMartin Mares <mj@ucw.cz>
Tue, 12 Feb 2013 11:29:38 +0000 (12:29 +0100)
Makefile [new file with mode: 0644]
suidgw.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..e8196ac
--- /dev/null
+++ b/suidgw.c
@@ -0,0 +1,176 @@
+/*
+ *  A simple gateway for set-uid scripts
+ *
+ *  (c) 2013 Martin Mares <mj@ucw.cz>
+ */
+
+#undef DEBUG
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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");
+}