weakbox.c (7364B) download
1#define _GNU_SOURCE
2#include "arg.h"
3
4#include <errno.h>
5#include <fcntl.h>
6#include <limits.h>
7#include <sched.h>
8#include <stdarg.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/mount.h>
13#include <sys/wait.h>
14#include <unistd.h>
15
16#define LEN(arr) (sizeof(arr) / sizeof(*arr))
17#define mapping_t(type) \
18 struct { \
19 type source; \
20 type target; \
21 }
22
23#define DEBUG(...) (flagv ? fprintf(stderr, __VA_ARGS__) : 0)
24
25#define SET_MAPPING_WITH_DEMILITER(mapping, argf, temp, demiliter, source_, target_) \
26 if (((temp) = strchr(argf, demiliter))) { \
27 *(temp)++ = '\0'; \
28 (mapping).source = source_; \
29 (mapping).target = target_; \
30 } else { \
31 (mapping).source = source_; \
32 (mapping).target = source_; \
33 }
34
35#define MYSELF "weakbox"
36#define PATH_PROC_UIDMAP "/proc/self/uid_map"
37#define PATH_PROC_GIDMAP "/proc/self/gid_map"
38#define PATH_PROC_SETGROUPS "/proc/self/setgroups"
39#define SHELL_DEFAULT "/bin/bash"
40#define MAX_BINDS 64
41#define MAX_USERMAP 8
42#define MAX_GROUPMAP 16
43
44
45static int open_printf(const char* file, int openopt, const char* format, ...) {
46 static char buffer[1024];
47 int fd, size;
48 va_list va;
49
50 if ((fd = open(file, openopt)) == -1) {
51 return -1;
52 }
53
54 va_start(va, format);
55 size = vsnprintf(buffer, sizeof(buffer), format, va);
56 va_end(va);
57
58 if (write(fd, buffer, size) == -1)
59 return -1;
60
61 close(fd);
62 return 0;
63}
64
65static char* argv0;
66
67static __attribute__((noreturn)) void usage(int exitcode) {
68 printf("usage: %s [-hs] [-r path] [-b source[:target]] [-B source] [-u uid[:uid]] [-g gid[:gid]] [var=value] command ...\n", argv0);
69 exit(exitcode);
70}
71
72static int bind_count = 9;
73static mapping_t(const char*) bind[MAX_BINDS] = {
74 { "/dev", "/dev" },
75 { "/home", "/home" },
76 { "/proc", "/proc" },
77 { "/sys", "/sys" },
78 { "/tmp", "/tmp" },
79 { "/run", "/run" },
80 { "/etc/resolv.conf", "/etc/resolv.conf" },
81 { "/etc/passwd", "/etc/passwd" },
82 { "/etc/group", "/etc/group" }
83};
84
85static int usermap_count = 0;
86static mapping_t(uid_t) usermap[MAX_USERMAP];
87
88static int groupmap_count = 0;
89static mapping_t(gid_t) groupmap[MAX_GROUPMAP];
90
91static int remove_bind(const char* path) {
92 int found = 0;
93 for (int i = 0; i < bind_count; i++) {
94 if (!strcmp(bind[i].source, path))
95 found++, bind_count--;
96 if (found) {
97 bind[i].source = bind[i + 1].source;
98 bind[i].target = bind[i + 1].target;
99 }
100 }
101 return found;
102}
103
104int main(int argc, char** argv) {
105 const char* root = getenv("WEAKBOX");
106 const char* shell = getenv("SHELL");
107 int linkexec = 0, flagr = 0, flagv = 0;
108 char pwd[PATH_MAX];
109 char* temp;
110 char* argf;
111
112 (void) argc;
113 getcwd(pwd, sizeof(pwd));
114
115 argv0 = *argv;
116 linkexec = (temp = strrchr(argv0, '/')) ? strcmp(++temp, MYSELF) : strcmp(argv0, MYSELF);
117
118 if (!linkexec) {
119 ARGBEGIN
120 switch (OPT) {
121 case 'h':
122 usage(0);
123 case 'v':
124 flagv++;
125 break;
126 case 's':
127 flagr++;
128 break;
129 case 'r':
130 root = EARGF(usage(1));
131 break;
132 case 'b':
133 argf = EARGF(usage(1));
134 if (bind_count >= (int) LEN(bind)) {
135 printf("error: too many bindings\n");
136 return 1;
137 }
138 SET_MAPPING_WITH_DEMILITER(bind[bind_count], argf, temp, ':', argf, temp);
139 bind_count++;
140 break;
141 case 'u':
142 argf = EARGF(usage(1));
143 if (usermap_count >= (int) LEN(usermap)) {
144 printf("error: too many user-mappings\n");
145 return 1;
146 }
147
148 SET_MAPPING_WITH_DEMILITER(usermap[usermap_count], argf, temp, ':', atoi(argf), atoi(temp));
149 usermap_count++;
150 break;
151 case 'g':
152 argf = EARGF(usage(1));
153 if (groupmap_count >= (int) LEN(groupmap)) {
154 printf("error: too many group-mappings\n");
155 return 1;
156 }
157 SET_MAPPING_WITH_DEMILITER(groupmap[groupmap_count], argf, temp, ':', atoi(argf), atoi(temp));
158 groupmap_count++;
159 break;
160 case 'B':
161 argf = EARGF(usage(1));
162 if (!remove_bind(argf))
163 printf("warn: binding '%s' not found\n", argf);
164 break;
165 default:
166 printf("error: unknown option '-%c'\n", OPT);
167 usage(1);
168 }
169 ARGEND
170 }
171
172 usermap[usermap_count].source = flagr ? 0: geteuid() ;
173 usermap[usermap_count++].target = geteuid();
174 groupmap[groupmap_count].source = flagr ? 0: getegid() ;
175 groupmap[groupmap_count++].target = getegid();
176
177 if (!root) {
178 fprintf(stderr, "error: $WEAKBOX not set and option '-r' is not used\n");
179 return 1;
180 }
181
182 DEBUG("debug: unsharing filesystem-namespace and user-namespace\n");
183 if (unshare(CLONE_NEWNS | CLONE_NEWUSER)) {
184 fprintf(stderr, "error: unable to unshare for new filesystem and user-environment: %s\n", strerror(errno));
185 return 1;
186 }
187
188 for (int i = 0; i < usermap_count; i++) {
189 DEBUG("debug: mapping user %d to %d\n", usermap[i].source, usermap[i].target);
190 if (open_printf(PATH_PROC_UIDMAP, O_WRONLY, "%u %u 1", usermap[i].source, usermap[i].target)) {
191 fprintf(stderr, "error: unable to map user %d to %d: %s\n", usermap[i].source, usermap[i].target, strerror(errno));
192 return 1;
193 }
194 }
195
196 DEBUG("debug: setting setgroups-policy\n");
197 if (open_printf(PATH_PROC_SETGROUPS, O_WRONLY, "deny") && errno != ENOENT) {
198 fprintf(stderr, "error: unable to set setgroups-policy: %s\n", strerror(errno));
199 return 1;
200 }
201
202 for (int i = 0; i < groupmap_count; i++) {
203 DEBUG("debug: mapping group %d to %d\n", groupmap[i].source, groupmap[i].target);
204 if (open_printf(PATH_PROC_GIDMAP, O_WRONLY, "%u %u 1", groupmap[i].source, groupmap[i].target)) {
205 fprintf(stderr, "error: unable to map group %d to %d: %s\n", groupmap[i].source, groupmap[i].target, strerror(errno));
206 return 1;
207 }
208 }
209
210 DEBUG("debug: new current user: %d, group: %d\n", getuid(), getgid());
211
212 char target[PATH_MAX];
213 for (int i = 0; i < bind_count; i++) {
214 snprintf(target, sizeof(target), "%s/%s", root, bind[i].target);
215 DEBUG("debug: mount '%s' to '%s'\n", bind[i].source, target);
216 if (mount(bind[i].source, target, NULL, MS_BIND | MS_REC | MS_PRIVATE, NULL)) {
217 fprintf(stderr, "error: unable to bind '%s' to '%s': %s\n", bind[i].source, target, strerror(errno));
218 return 1;
219 }
220 }
221
222 DEBUG("debug: change root to '%s'\n", root);
223 if (chroot(root)) {
224 fprintf(stderr, "error: unable to set root to '%s': %s\n", root, strerror(errno));
225 return 1;
226 }
227
228 // if chdir(pwd) fails, chdir("/") cannot fail
229 DEBUG("debug: change directory to '%s'\n", pwd);
230 if (chdir(pwd)) {
231 DEBUG("debug: ... which failed (%s), change directory to '/'\n", strerror(errno));
232 (void) chdir("/");
233 }
234
235 if (*argv) {
236 while (*argv && strchr(*argv, '=')) {
237 putenv(*argv++);
238 }
239
240 DEBUG("debug: executing '%s'...\n", *argv);
241 execvp(*argv, argv);
242 fprintf(stderr, "error: unable to execute '%s': %s\n", *argv, strerror(errno));
243 } else if (shell) {
244 DEBUG("debug: executing '%s'...\n", shell);
245 execlp(shell, shell, NULL);
246 execlp(SHELL_DEFAULT, SHELL_DEFAULT, NULL);
247 fprintf(stderr, "error: unable to execute '%s' or '" SHELL_DEFAULT "': %s\n", shell, strerror(errno));
248 }
249 return 1;
250}