github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/sandbox/main.c (about) 1 // please_sandbox is a very small binary to implement sandboxing 2 // of tests (and possibly other build actions) via cgroups. 3 // Essentially this is a very lightweight replacement for Docker 4 // where we would use it for tests to avoid port clashes etc. 5 // 6 // Note that this is a no-op on non-Linux OSs because they will not 7 // support namespaces / cgroups. We still behave similarly otherwise 8 // in order for it to be transparent to the rest of the system. 9 10 #define _GNU_SOURCE 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <unistd.h> 14 15 #ifdef __linux__ 16 #include <sched.h> 17 #include <string.h> 18 #include <net/if.h> 19 #include <sys/ioctl.h> 20 #include <sys/mount.h> 21 #include <sys/stat.h> 22 #include <sys/types.h> 23 24 // lo_up brings up the loopback interface in the new network namespace. 25 // By default the namespace is created with lo but it is down. 26 // Note that this can't be done with system() because it loses the 27 // required capabilities. 28 int lo_up() { 29 const int sock = socket(AF_INET, SOCK_DGRAM, 0); 30 if (sock < 0) { 31 perror("socket"); 32 return 1; 33 } 34 35 struct ifreq req; 36 memset(&req, 0, sizeof(req)); 37 strncpy(req.ifr_name, "lo", IFNAMSIZ); 38 if (ioctl(sock, SIOCGIFFLAGS, &req) < 0) { 39 perror("SIOCGIFFLAGS"); 40 return 1; 41 } 42 43 req.ifr_flags |= IFF_UP; 44 if (ioctl(sock, SIOCSIFFLAGS, &req) < 0) { 45 perror("SIOCSIFFLAGS"); 46 return 1; 47 } 48 close(sock); 49 return 0; 50 } 51 52 // deny_groups disables the ability to call setgroups(2). This is required 53 // before we can successfully write to gid_map in map_ids. 54 int deny_groups() { 55 FILE* f = fopen("/proc/self/setgroups", "w"); 56 if (!f) { 57 perror("fopen /proc/self/setgroups"); 58 return 1; 59 } 60 if (fputs("deny\n", f) < 0) { 61 perror("fputs"); 62 return 1; 63 } 64 return fclose(f); 65 } 66 67 // map_ids maps the user id or group id inside the namespace to those outside. 68 // Without this we fail to create directories in the tmpfs with an EOVERFLOW. 69 int map_ids(int out_id, const char* path) { 70 FILE* f = fopen(path, "w"); 71 if (!f) { 72 perror("fopen"); 73 return 1; 74 } 75 if (fprintf(f, "%d %d 1\n", out_id, out_id) < 0) { 76 perror("fprintf"); 77 return 1; 78 } 79 if (fclose(f) != 0) { 80 perror("fclose"); 81 return 1; 82 } 83 return 0; 84 } 85 86 // mount_tmp mounts a tmpfs on /tmp for the tests to muck about in. 87 int mount_tmp() { 88 // Remounting / as private is necessary so that the tmpfs mount isn't visible to anyone else. 89 if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) { 90 perror("remount"); 91 return 1; 92 } 93 const int flags = MS_LAZYTIME | MS_NOATIME | MS_NODEV | MS_NOSUID; 94 if (mount("tmpfs", "/tmp", "tmpfs", flags, NULL) != 0) { 95 perror("mount"); 96 return 1; 97 } 98 return setenv("TMPDIR", "/tmp", 1); 99 } 100 101 // mount_test bind mounts the test directory to 102 int mount_test() { 103 const char* d = "/tmp/plz_sandbox"; 104 const char* dir = getenv("TEST_DIR"); 105 if (!dir) { 106 fputs("TEST_DIR not set, will not bind-mount to /tmp/test\n", stderr); 107 return 0; 108 } 109 if (mkdir(d, S_IRWXU) != 0) { 110 perror("mkdir /tmp/test"); 111 return 1; 112 } 113 if (mount(dir, d, "", MS_BIND, NULL) != 0) { 114 perror("bind mount"); 115 return 1; 116 } 117 if (setenv("TEST_DIR", d, 1) != 0 || 118 setenv("TMP_DIR", d, 1) != 0 || 119 setenv("HOME", d, 1) != 0) { 120 perror("setenv"); 121 return 1; 122 } 123 return chdir(d); 124 } 125 126 // contain separates the process into new namespaces to sandbox it. 127 int contain(char* argv[]) { 128 const uid_t uid = getuid(); 129 const uid_t gid = getgid(); 130 if (unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNS) != 0) { 131 perror("unshare"); 132 fputs("Your user doesn't seem to have enough permissions to call unshare(2).\n", stderr); 133 fputs("please_sandbox requires support for user namespaces (usually >= Linux 3.10)\n", stderr); 134 return 1; 135 } 136 if (deny_groups() != 0) { 137 return 1; 138 } 139 if (map_ids(uid, "/proc/self/uid_map") != 0 || 140 map_ids(gid, "/proc/self/gid_map") != 0) { 141 return 1; 142 } 143 if (mount_tmp() != 0) { 144 return 1; 145 } 146 if (mount_test() != 0) { 147 return 1; 148 } 149 if (lo_up() != 0) { 150 return 1; 151 } 152 return execvp(argv[0], argv); 153 } 154 155 #else 156 157 // On non-Linux systems contain simply execs a subprocess. 158 // It's not really expected to be used there, this is simply to make it compile. 159 int contain(char* argv[]) { 160 return execvp(argv[0], argv); 161 } 162 163 #endif 164 165 166 int main(int argc, char* argv[]) { 167 if (argc < 2) { 168 fputs("please_sandbox implements sandboxing for Please.\n", stderr); 169 fputs("It takes no flags, it simply executes the command given as arguments.\n", stderr); 170 fputs("Usage: plz_sandbox command args...\n", stderr); 171 exit(1); 172 } 173 return contain(&argv[1]); 174 }