├── .gitignore ├── Makefile ├── LICENSE ├── browser-container-launcher.sh ├── README.md └── home-container.c /.gitignore: -------------------------------------------------------------------------------- 1 | home-container.nosuid 2 | home-container 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-Wall -Os -g 3 | 4 | home-container: home-container.c 5 | $(CC) -std=c99 $(CFLAGS) home-container.c -o home-container 6 | 7 | clean: 8 | rm -f home-container 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors 2 | Licensed under the MIT License: 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /browser-container-launcher.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # This is a shell script which runs a web browser in a home-container, complete with a simple UI 4 | # profile manager constructed using Zenity. Some directories from your real homedir are shared 5 | # with the browser: 6 | # Downloads (writable) 7 | # Uploads (read-only) 8 | # Pictures (read-only; this is my most common upload source) 9 | # .local/share (read-only) 10 | # .config/fontconfig (read-only) 11 | # 12 | # You need to specify the browser you want (e.g. "google-chrome", "chromium", "firefox") as 13 | # a parameter to the script. 14 | 15 | if [ "$#" -lt 1 ]; then 16 | echo "usage: $0 BROWSER_COMMAND" >&2 17 | echo "example: $0 chromium" >&2 18 | exit 1 19 | fi 20 | 21 | LIST=$(cd ~/.home-container && echo browser-* | sed -e 's/\`, which actually maps to `/home//.home-container/` in the real filesystem. The rest of `/home` is hidden. 48 | 49 | Additionally, you can set up mappings from the container homedir to your real home directory. The included script `browser-container-launcher.sh` sets up the following mappings: 50 | 51 | * `Downloads` (read-write) 52 | * `Uploads` (read-only) 53 | * `Pictures` (read-only) 54 | * `.config/fontconfig` (read-only) 55 | * `.local/share` (read-only) 56 | 57 | ### Won't my cookies still be vulnerable? 58 | 59 | Yes. But, you can reduce risk by using different profiles to log into sensitive sites vs. suspicious sites. Each profile will only be able to see its own cookies. 60 | 61 | ### Why not use a VM? 62 | 63 | You could do that, but a VM is much more costly in terms of resources, will likely run slower, and tends to disrupt things like OpenGL, which you probably want to work in your browser. The namespace approach has essentially zero performance overhead. Also, see the next question. 64 | 65 | ### Why not use Docker/Rocket/OCI/LXC/etc.? 66 | 67 | Those container engines are meant for containerizing the _entire_ filesystem, not just your home directory. For desktop apps, that's not normally what you want: you actually want the app to communicate with the rest of your desktop, use the correct graphics drivers, etc. 68 | 69 | ### Why not use SELinux? 70 | 71 | SELinux implements access control, not containerization. You could perhaps use it to limit what apps can see your SSH keys, but you cannot use it to create an entirely new, empty workspace for an app or implement "profiles". 72 | 73 | You can, of course, stack SELinux policies on top of home-container for even more protection. 74 | 75 | ### Why not use Bubblewrap, Firejail, etc.? 76 | 77 | [Bubblewrap](https://github.com/containers/bubblewrap) and [Firejail](https://github.com/netblue30/firejail) are much more advanced attempts to solve similar problems. 78 | 79 | Both of those projects are likely to provide much stronger security protections than home-container. They have far more features and are actively maintained. 80 | 81 | The main benefit of home-container is that it is extremely simple. It is entirely feasible for you to review the entire source code of home-container in 15 minutes or so before using it. Those other projects are far too large to review. This does not by any means make home-container "better", but it's an interesting property for people who want to learn about containers, want to understand what their tools are doing, or want to keep things simple. 82 | 83 | ### Doesn't my browser have its own sandbox? 84 | 85 | Yes. But, the web platform is large and complicated. 86 | 87 | For example, scripts loaded by HTML files on your hard drive (`file:` scheme) have the ability to read other files from the local drive. That means that your browser's sandbox has to implement the ability for scripts to read files, and then has to implement policy to decide when they are allowed to do so. That policy could have bugs. 88 | 89 | Or perhaps more likely, an extension might request local file access permission. If that extension has bugs, a malicious web site might trick it into uploading files from your system. 90 | 91 | `home-container` is only a few hundred lines of C code with no dependencies. It implements a simple policy which is easily understood and can easily be reviewed in full. Using it adds an additional layer of defense in case of certain types of bugs in the browser itself. It cannot defend against all browser bugs, but it can defend against at least some kinds of bugs actually seen in the wild. 92 | 93 | ### Is home-container a sandbox? 94 | 95 | No! 96 | 97 | Malicious native code can escape the container in a number of ways: 98 | 99 | * Hijacking your X session and spoofing keypresses to other apps. 100 | * Making desktop requests over dbus. 101 | * Talking to your ssh-agent. 102 | * Exploiting a bug in the Linux kernel for privilege escalation (new bugs are typically found monthly or even weekly; when did you last reboot?). 103 | * Probably many other ways. 104 | 105 | Fundamaentally, the Linux desktop platform is not designed to be sandbox-able as-is, and a platform that isn't designed to be sandbox-able likely cannot be forced into any usable sandbox transparently. home-container therefore only aims to protect against bugs in which the app is **not** executing outright malicious code, but rather trusted-but-buggy code is tricked into accessing files it is not supposed to. 106 | -------------------------------------------------------------------------------- /home-container.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors 2 | // Licensed under the MIT License: 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | #define _GNU_SOURCE 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | // ======================================================================================= 41 | // generic error handling 42 | 43 | void stack_trace(int skip) { 44 | void* trace[32]; 45 | int size = backtrace(trace, 32); 46 | 47 | char exe[128]; 48 | snprintf(exe, 128, "/proc/%d/exe", getpid()); 49 | 50 | pid_t child = fork(); 51 | if (child == 0) { 52 | char* cmd[36]; 53 | cmd[0] = "addr2line"; 54 | cmd[1] = "-e"; 55 | cmd[2] = exe; 56 | 57 | char addrs[64][32]; 58 | int i; 59 | for (i = 0; i < size - skip; i++) { 60 | snprintf(addrs[i], 64, "%p", trace[i + skip]); 61 | cmd[3 + i] = addrs[i]; 62 | } 63 | cmd[3 + size - skip] = NULL; 64 | 65 | execvp("addr2line", cmd); 66 | perror("addr2line"); 67 | exit(1); 68 | } 69 | 70 | int status; 71 | waitpid(child, &status, 0); 72 | 73 | if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { 74 | fprintf(stderr, "raw trace:"); 75 | for (int i = skip; i < size; i++) { 76 | fprintf(stderr, " %p", (char*)trace[i] - 1); 77 | } 78 | fprintf(stderr, "\n"); 79 | } 80 | } 81 | 82 | void fail_errno_except_eintr(const char* code) { 83 | if (errno == EINTR) return; // interrupted; return and try again 84 | perror(code); 85 | stack_trace(2); 86 | abort(); 87 | } 88 | 89 | #define sys(code) while ((int)(code) == -1) fail_errno_except_eintr(#code); 90 | // Run the given system call and abort if it fails. 91 | 92 | void die(const char* why, ...) __attribute__((noreturn)); 93 | void die(const char* why, ...) { 94 | // Abort with the given error message. 95 | va_list args; 96 | va_start(args, why); 97 | vfprintf(stderr, why, args); 98 | putc('\n', stderr); 99 | stack_trace(2); 100 | abort(); 101 | } 102 | 103 | // ======================================================================================= 104 | // helpers for setting up mount tree 105 | 106 | unsigned long writable_mount_flags = 0; 107 | 108 | enum file_type { 109 | NONEXISTENT, 110 | NON_DIRECTORY, 111 | DIRECTORY, 112 | }; 113 | 114 | enum file_type get_file_type(const char* path) { 115 | // Determine if the path is a directory, a non-directory file, or doesn't exist. 116 | 117 | struct stat stats; 118 | if (stat(path, &stats) < 0) { 119 | if (errno == ENOENT || errno == ENOTDIR) { 120 | return NONEXISTENT; 121 | } else { 122 | perror(path); 123 | abort(); 124 | } 125 | } 126 | return S_ISDIR(stats.st_mode) ? DIRECTORY : NON_DIRECTORY; 127 | } 128 | 129 | enum bind_type { 130 | EMPTY, // just make an empty node of the same type (file or dir) 131 | READONLY, // bind the destination to the source, read-only 132 | FULL, // bind the destination to the source, read-write 133 | }; 134 | 135 | void bind(enum bind_type type, const char* src, const char* dst) { 136 | // Bind-mount src to dst, such that dst becomes an alias for src. 137 | 138 | switch (get_file_type(src)) { 139 | case NONEXISTENT: 140 | // Skip files that don't exist. 141 | return; 142 | case DIRECTORY: 143 | while (mkdir(dst, 0777) < 0 && errno != EEXIST) { 144 | fail_errno_except_eintr("mkdir(dst, 0777)"); 145 | } 146 | break; 147 | case NON_DIRECTORY: 148 | // Make an empty regular file to bind over. 149 | sys(mknod(dst, S_IFREG | 0777, 0)); 150 | break; 151 | } 152 | 153 | if (type == EMPTY) { 154 | // Don't bind, just copy permissions. 155 | struct stat stats; 156 | sys(stat(src, &stats)); 157 | sys(chown(dst, stats.st_uid, stats.st_gid)); 158 | sys(chmod(dst, stats.st_mode)); 159 | } else { 160 | // Bind the source file over the destination. 161 | sys(mount(src, dst, NULL, MS_BIND | MS_REC, NULL)); 162 | if (type == READONLY) { 163 | // Setting the READONLY flag requires a remount. (If we tried to set it in the 164 | // first mount it would be silently ignored.) 165 | sys(mount(src, dst, NULL, MS_REMOUNT | MS_BIND | MS_REC | MS_RDONLY, NULL)); 166 | } else if (writable_mount_flags) { 167 | // Need to remount to apply writable_mount_flags. 168 | sys(mount(src, dst, NULL, MS_REMOUNT | MS_BIND | MS_REC | writable_mount_flags, NULL)); 169 | } 170 | } 171 | } 172 | 173 | void hide(const char* dst) { 174 | // If the given path exists, hide it by overmounting it with an empty file/dir. 175 | 176 | switch (get_file_type(dst)) { 177 | case NONEXISTENT: 178 | return; 179 | case DIRECTORY: 180 | // Empty tmpfs. 181 | sys(mount("tmpfs", dst, "tmpfs", writable_mount_flags, "size=2M,nr_inodes=4096,mode=755")); 182 | break; 183 | case NON_DIRECTORY: 184 | sys(mount("/dev/null", dst, NULL, MS_BIND | MS_REC, NULL)); 185 | break; 186 | } 187 | } 188 | 189 | void bind_in_container(enum bind_type type, const char* path) { 190 | // Assuming the current directory is where we're setting up the container, bind the 191 | // given absolute path from outside the container to the same path inside. 192 | 193 | assert(path[0] == '/'); 194 | 195 | // Verify parent has been bound, or bind it "empty". 196 | char parent[strlen(path + 1)]; 197 | strcpy(parent, path); 198 | char* slashPos = strrchr(parent + 1, '/'); 199 | if (slashPos != NULL) { 200 | *slashPos = '\0'; 201 | if (access(parent + 1, F_OK) != 0) { 202 | bind_in_container(EMPTY, parent); 203 | } 204 | } 205 | 206 | // OK, bind child. 207 | bind(type, path, path + 1); 208 | } 209 | 210 | void hide_in_container(const char* path) { 211 | // Assuming the current directory is where we're setting up the container, hide the given 212 | // absolute path inside the container. 213 | 214 | assert(path[0] == '/'); 215 | hide(path + 1); 216 | } 217 | 218 | int mkdir_user_owned(const char* path, mode_t mode, struct passwd* user) { 219 | int result = mkdir(path, mode); 220 | if (result >= 0) { 221 | sys(chown(path, user->pw_uid, user->pw_gid)); 222 | } 223 | return result; 224 | } 225 | 226 | const char* home_path(struct passwd* user, const char* path) { 227 | static char result[512]; 228 | if (path == NULL) { 229 | snprintf(result, 512, "/home/%s", user->pw_name); 230 | } else { 231 | snprintf(result, 512, "/home/%s/%s", user->pw_name, path); 232 | } 233 | return result; 234 | } 235 | 236 | // ======================================================================================= 237 | 238 | void usage(const char* self) { 239 | fprintf(stderr, 240 | "usage: %1$s NAME OPTIONS COMMAND\n" 241 | "\n" 242 | "Runs COMMAND inside the home directory container with the given name.\n" 243 | "Within the container, your real home directory will be invisible (modulo\n" 244 | "options below), replaced by a directory that starts out empty, but which\n" 245 | "persists across runs with the same container name.\n" 246 | "\n" 247 | "Hint: You can maintain multiple \"profiles\" (different configurations\n" 248 | "of the same app) by running the same app in multiple containers.\n" 249 | "\n" 250 | "Options:\n" 251 | " --nx Prevent executing files from locations that are writable.\n" 252 | " -r Make from your real homedir accessible in the\n" 253 | " container read-only.\n" 254 | " -w Make from your real homedir accessible in the\n" 255 | " container with full access.\n" 256 | " -h Hide , a subdirectory of a passed to a previous\n" 257 | " -w or -r. This makes the directory inaccessible in the\n" 258 | " container (it will appear empty and unwritable).\n" 259 | "\n" 260 | "Example:\n" 261 | " %1$s browser -w Downloads google-chrome\n" 262 | " Runs Google Chrome in a container but lets it put downloads in\n" 263 | " your real \"Downloads\" directory.\n", self); 264 | } 265 | 266 | void validate_map_path_piece(const char* path, const char* piece) { 267 | if (strcmp(piece, "") == 0 || 268 | strcmp(piece, ".") == 0 || 269 | strcmp(piece, "..") == 0) { 270 | die("invalid: %s", path); 271 | } 272 | } 273 | 274 | void validate_map_path(const char* path) { 275 | // Disallow path injection (., .., absolute paths) in mappings. 276 | 277 | if (strlen(path) >= 256) { 278 | die("too long: %s", path); 279 | } 280 | 281 | char copy[256]; 282 | strcpy(copy, path); 283 | 284 | const char* piece = copy; 285 | for (char* pos = copy; *pos != '\0'; ++pos) { 286 | if (*pos == '/') { 287 | *pos = '\0'; 288 | // Now `piece` points to just one path component. 289 | validate_map_path_piece(path, piece); 290 | piece = pos + 1; 291 | } 292 | } 293 | validate_map_path_piece(path, piece); 294 | } 295 | 296 | void write_file(const char* filename, const char* content) { 297 | int fd = open(filename, O_WRONLY); 298 | ssize_t n; 299 | sys(n = write(fd, content, strlen(content))); 300 | if (n < strlen(content)) { 301 | die("incomplete write"); 302 | } 303 | sys(close(fd)); 304 | } 305 | 306 | int main(int argc, const char* argv[]) { 307 | if (argc < 1) die("no argv[0]?"); // shouldn't happen 308 | const char* self = argv[0]; 309 | --argc; 310 | ++argv; 311 | 312 | if (argc < 1 || argv[0][0] == '-') { 313 | usage(self); 314 | return strcmp(argv[0], "--help"); 315 | } 316 | const char* container_name = argv[0]; 317 | --argc; 318 | ++argv; 319 | 320 | if (argc > 0 && strcmp(argv[0], "--nx") == 0) { 321 | writable_mount_flags = MS_NOEXEC; 322 | --argc; 323 | ++argv; 324 | } 325 | 326 | // Disallow path injection in container name (., .., or anything with a /). 327 | // 328 | // Also disallow overly long values as this is C and I'm too lazy to dynamically allocate 329 | // strings. 330 | if (*container_name == '\0' || strchr(container_name, '/') != NULL || 331 | strcmp(container_name, ".") == 0 || strcmp(container_name, "..") == 0 || 332 | strlen(container_name) > 128) { 333 | die("invalid: %s", container_name); 334 | } 335 | 336 | // Check that we are suid-root, but were not executed by root. 337 | // TODO: Once Chrome supports uid namespaces rather than using a setuid sandbox, we 338 | // should also switch to using uid namespaces and not require setuid. See: 339 | // https://code.google.com/p/chromium/issues/detail?id=312380 340 | uid_t ruid, euid, suid; 341 | sys(getresuid(&ruid, &euid, &suid)); 342 | if (ruid == 0) { 343 | die("please run as non-root"); 344 | } 345 | if (euid == 0 || suid == 0) { 346 | die("please don't use setuid-root binary anymore"); 347 | } 348 | 349 | gid_t gid = getgid(); 350 | 351 | // Get username of the user who executed us. 352 | struct passwd* user = getpwuid(ruid); 353 | if (user == NULL) die("getpwuid() failed"); 354 | if (strlen(user->pw_name) > 128) { 355 | // This is C and I'm too lazy to allocate strings dynamically so let's just prevent ridiculous 356 | // usernames. 357 | die("username too long"); 358 | } 359 | 360 | // Enter a private mount namespace. 361 | sys(unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC)); 362 | 363 | char user_map[64]; 364 | write_file("/proc/self/setgroups", "deny\n"); 365 | snprintf(user_map, 64, "1000 %d 1\n", ruid); 366 | write_file("/proc/self/uid_map", user_map); 367 | snprintf(user_map, 64, "1000 %d 1\n", gid); 368 | write_file("/proc/self/gid_map", user_map); 369 | 370 | // To really get our own private mount tree, we have to remount root as "private". Otherwise 371 | // our changes may be propagated to the original mount namespace and ruin everything. 372 | sys(mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)); 373 | 374 | // Start building our new tree under /tmp. First, bind-mount / to /tmp and make it read-only. 375 | sys(mount("/", "/tmp", NULL, MS_BIND | MS_REC, NULL)); 376 | sys(mount("/", "/tmp", NULL, MS_REMOUNT | MS_BIND | MS_REC | MS_RDONLY, NULL)); 377 | 378 | // We'll set the container root as our current directory so that the _in_container() helpers 379 | // work. 380 | sys(chdir("/tmp")); 381 | 382 | // Stuff in /var probably shouldn't be visible in the container, except /var/tmp. 383 | hide_in_container("/var"); 384 | bind_in_container(FULL, "/var/tmp"); 385 | 386 | // Mount /tmp into the sandbox with full access. 387 | char tmp_dir[512]; 388 | snprintf(tmp_dir, 512, "/var/tmp/home-container.%s.%s", user->pw_name, container_name); 389 | mkdir_user_owned(tmp_dir, 0700, user); 390 | bind(FULL, tmp_dir, "tmp"); 391 | 392 | // Hide /home, then we'll bring back the specific things we need. 393 | hide_in_container("/home"); 394 | 395 | // Make the container directory if it doesn't exist, then bind it as the home directory. 396 | mkdir_user_owned(home_path(user, ".home-container"), 0700, user); 397 | char container_dir[512]; 398 | snprintf(container_dir, 512, "/home/%s/.home-container/%s", user->pw_name, container_name); 399 | mkdir_user_owned(container_dir, 0700, user); 400 | bind(FULL, container_dir, home_path(user, NULL) + 1); 401 | 402 | // Interpret options. 403 | while (argc > 0 && argv[0][0] == '-') { 404 | if (strcmp(argv[0], "-w") == 0 && argc > 1) { 405 | validate_map_path(argv[1]); 406 | bind_in_container(FULL, home_path(user, argv[1])); 407 | argc -= 2; 408 | argv += 2; 409 | } else if (strcmp(argv[0], "-r") == 0 && argc > 1) { 410 | validate_map_path(argv[1]); 411 | bind_in_container(READONLY, home_path(user, argv[1])); 412 | argc -= 2; 413 | argv += 2; 414 | } else if (strcmp(argv[0], "-h") == 0 && argc > 1) { 415 | validate_map_path(argv[1]); 416 | hide_in_container(home_path(user, argv[1])); 417 | argc -= 2; 418 | argv += 2; 419 | } else if (strcmp(argv[0], "--nx") == 0) { 420 | die("--nx must be specified before other flags"); 421 | } else if (strcmp(argv[0], "--help") == 0) { 422 | usage(self); 423 | return 0; 424 | } else { 425 | usage(self); 426 | return 1; 427 | } 428 | } 429 | 430 | if (argc == 0) { 431 | fprintf(stderr, "missing command"); 432 | usage(self); 433 | return 1; 434 | } 435 | 436 | // Use pivot_root() to replace our root directory with the tree we built in /tmp. This is 437 | // more secure than chroot(). 438 | sys(syscall(SYS_pivot_root, "/tmp", "/tmp/tmp")); 439 | sys(umount2("/tmp", MNT_DETACH)); 440 | chdir("/"); 441 | 442 | 443 | assert(argv[argc] == NULL); 444 | 445 | // Fork 446 | pid_t child = fork(); 447 | if (child == 0) { 448 | // We are the child. 449 | 450 | // Mount procfs that reflects our pid namespace. 451 | sys(mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL)); 452 | 453 | if (access("/usr/bin/nvidia-modprobe", F_OK) == 0) { 454 | // If this program exists, the nvidia drivers will attempt to run it, in particular when 455 | // setting up a Vulkan context. Normally, it is setuid-root, and will not work correctly 456 | // in the sandbox, leading Vulkan setup to fail. Apparently, though, if we simply replace 457 | // the program with /usr/bin/true, this is enough to trick the nvidia driver into working 458 | // correctly. 459 | // 460 | // See: https://github.com/containers/bubblewrap/issues/328#issuecomment-571162188 461 | sys(mount("/usr/bin/true", "/usr/bin/nvidia-modprobe", NULL, MS_BIND, NULL)); 462 | } 463 | 464 | // Drop privileges. 465 | sys(setresuid(ruid, ruid, ruid)); 466 | 467 | // Execute! 468 | sys(execvp(argv[0], (char**)argv)); 469 | die("can't get here"); 470 | } 471 | 472 | int status; 473 | sys(waitpid(child, &status, 0)); 474 | 475 | if (WIFEXITED(status) || WEXITSTATUS(status) == 0) { 476 | _exit(0); 477 | } else { 478 | _exit(1); 479 | } 480 | } 481 | --------------------------------------------------------------------------------