util/weakbox

weakbox.c in master
Repositories | Summary | Log | Files | LICENSE

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}