]> mj.ucw.cz Git - suidgw.git/blob - suidgw.c
Makefile: clean is also a phony target
[suidgw.git] / suidgw.c
1 /*
2  *  A simple gateway for set-uid scripts
3  *
4  *  (c) 2013-2022 Martin Mares <mj@ucw.cz>
5  */
6
7 #undef DEBUG
8
9 #define _GNU_SOURCE
10
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <limits.h>
14 #include <pwd.h>
15 #include <stdarg.h>
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <syslog.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23
24 #define NONRET __attribute__((noreturn))
25 #define UNUSED __attribute__((unused))
26
27 #ifdef DEBUG
28 #define DBG(...) printf(__VA_ARGS__)
29 #else
30 #define DBG(...) do { } while (0)
31 #endif
32
33 static char program_name[PATH_MAX];
34 static char script_path[PATH_MAX];
35 static uid_t use_uid;
36 static gid_t use_gid;
37
38 static const char * const script_dirs[] = {
39   "/usr/local/lib/suidgw",
40   "/usr/lib/suidgw",
41 #ifdef DEBUG
42   "./tests/scripts",
43 #endif
44   NULL
45 };
46
47 static char env_orig_uid[32], env_orig_gid[32];
48
49 static char *sanitized_env[] = {
50   "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
51   env_orig_uid,
52   env_orig_gid,
53   NULL
54 };
55
56 static void NONRET die(const char *fmt, ...)
57 {
58   va_list args;
59   va_start(args, fmt);
60   fprintf(stderr, "suidgw: ");
61   vfprintf(stderr, fmt, args);
62   fputc('\n', stderr);
63   exit(1);
64 }
65
66 static void sanitize_fds(void)
67 {
68   // Make sure that nobody is playing dirty games with our file descriptors
69   int fd;
70   do
71     {
72       fd = open("/dev/null", O_RDWR);
73       if (fd < 0)
74         die("Cannot open /dev/null: %m");
75     }
76   while (fd <= 2);
77   close(fd);
78 }
79
80 static void sanitize_env(void)
81 {
82   snprintf(env_orig_uid, sizeof(env_orig_uid), "ORIG_UID=%d", (int) getuid());
83   snprintf(env_orig_gid, sizeof(env_orig_gid), "ORIG_GID=%d", (int) getgid());
84 }
85
86 static void get_script_name(const char *arg0)
87 {
88   if (!arg0)
89     die("Program name must be given");
90
91   // If arg0 is a path, extract the last component
92   const char *p = strrchr(arg0, '/');
93   if (p)
94     p++;
95   else
96     p = arg0;
97
98   // Reject empty and oversized names
99   if (!p[0])
100     die("Script name is empty");
101   if (strlen(p) >= PATH_MAX)
102     die("Script name too long");
103
104   // Reject invalid characters
105   for (const char *q = p; *q; q++)
106     {
107       int c = *q;
108       if (! (c >= 'a' && c <= 'z' ||
109              c >= 'A' && c <= 'Z' ||
110              c >= '0' && c <= '9' ||
111              c == '.' ||
112              c == '-' ||
113              c == '_'))
114         die("Script name contains an invalid character 0x%02x", c);
115     }
116
117   // Reject "." and ".."
118   if (!strcmp(p, ".") || !strcmp(p, ".."))
119     die("Script name must not be `.' or `..'");
120
121   DBG("Program name: <%s>\n", p);
122   strcpy(program_name, p);
123 }
124
125 static void find_script(void)
126 {
127   for (int i = 0; script_dirs[i]; i++)
128     {
129       int len = snprintf(script_path, PATH_MAX, "%s/%s", script_dirs[i], program_name);
130       if (len >= PATH_MAX)
131         continue;
132       DBG("Trying <%s>\n", script_path);
133       if (access(script_path, X_OK) >= 0)
134         {
135           DBG("Found <%s>\n", script_path);
136           return;
137         }
138       if (errno != ENOENT)
139         die("Cannot run %s: %m", script_path);
140     }
141
142   die("Unable to find script %s", program_name);
143 }
144
145 static void check_stat(void)
146 {
147   struct stat st;
148   if (stat(script_path, &st) < 0)
149     die("Cannot stat %s: %m", script_path);
150
151   if (!S_ISREG(st.st_mode))
152     die("Not a regular file: %s", script_path);
153   if (!(st.st_mode & (S_ISUID | S_ISGID)))
154     die("Missing suid/sgid: %s", script_path);
155
156   use_uid = (st.st_mode & S_ISUID) ? st.st_uid : getuid();
157   use_gid = (st.st_mode & S_ISGID) ? st.st_gid : getgid();
158   DBG("Will use uid=%d gid=%d\n", (int) use_uid, (int) use_gid);
159 }
160
161 static void switch_ugid(void)
162 {
163   if (setresgid(use_gid, use_gid, use_gid) < 0)
164     die("Failed to set group id: %m");
165
166   if (setresuid(use_uid, use_uid, use_uid) < 0)
167     die("Failed to set user id: %m");
168 }
169
170 int main(int argc UNUSED, char **argv)
171 {
172   sanitize_fds();
173   sanitize_env();
174   umask(0077);
175
176   if (geteuid())
177     die("Must be run setuid");
178
179   struct passwd *pwd = getpwuid(getuid());
180   if (!pwd)
181     die("You don't exist");
182
183   openlog("suidgw", LOG_NDELAY | LOG_PID, LOG_AUTH);
184
185   get_script_name(argv[0]);
186   find_script();
187   check_stat();
188   switch_ugid();
189
190   syslog(LOG_INFO, "User %s executes %s with uid=%d, gid=%d",
191     pwd->pw_name, script_path, (int) use_uid, (int) use_gid);
192
193   if (execve(script_path, argv, sanitized_env) < 0)
194     die("Cannot execute %s: %m", script_path);
195
196   die("This must never happen");
197 }