weakbox.c (7364B)
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 45 static 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 65 static char* argv0; 66 67 static __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 72 static int bind_count = 9; 73 static 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 85 static int usermap_count = 0; 86 static mapping_t(uid_t) usermap[MAX_USERMAP]; 87 88 static int groupmap_count = 0; 89 static mapping_t(gid_t) groupmap[MAX_GROUPMAP]; 90 91 static 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 104 int 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 }