1/* 2 * Copyright (C) 2013 University of Szeged 3 * Copyright (C) 2013 Renata Hodovan <reni@inf.u-szeged.hu> 4 * All rights reserved. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 */ 21 22#include "SandboxEnvironmentLinux.h" 23 24#include <dirent.h> 25#include <dlfcn.h> 26#include <err.h> 27#include <errno.h> 28#include <fcntl.h> 29#include <grp.h> 30#include <limits.h> 31#include <link.h> 32#include <pwd.h> 33#include <sched.h> 34#include <signal.h> 35#include <stdio.h> 36#include <stdlib.h> 37#include <string.h> 38#include <sys/capability.h> 39#include <sys/mount.h> 40#include <sys/prctl.h> 41#include <sys/resource.h> 42#include <sys/socket.h> 43#include <sys/stat.h> 44#include <sys/syscall.h> 45#include <sys/time.h> 46#include <sys/types.h> 47#include <sys/wait.h> 48#include <unistd.h> 49#include <utime.h> 50#include <vector> 51 52static const unsigned maximumPathLength = 512; 53static char sandboxDirectory[maximumPathLength]; 54static uid_t sandboxUserUID; 55static uid_t sandboxUserGID; 56 57static inline void strlcpy(char *destination, const char* source, int maxLength) 58{ 59 destination[0] = '\0'; 60 strncat(destination, source, maxLength - 1); 61} 62 63static inline void strlcat(char* destination, const char* source, int maxLength) 64{ 65 strncat(destination, source, maxLength - 1 - strnlen(destination, maxLength - 1)); 66} 67 68static inline void appendDirectoryComponent(char* fullPath, const char* directoryPath, const char* fileName) 69{ 70 strlcpy(fullPath, directoryPath, maximumPathLength); 71 strlcat(fullPath, fileName, maximumPathLength); 72} 73 74// This function runs in a cloned process and it is waiting for a request message 75// from WebProcess to perform the chroot(). If the operation was successful the function 76// never returns. So this function has no return value. 77static void launchChangeRootHelper(int helperSocket, int webProcessSocket) 78{ 79 // We need to restrict the resources available to our process to avoid opening 80 // a file by mistake. However, CAP_SYS_RESOURCE capability should be dropped 81 // otherwise it won't work. 82 struct rlimit restrictedResource = { 0, 0 }; 83 if (setrlimit(RLIMIT_NOFILE, &restrictedResource) == -1) { 84 fprintf(stderr, "Helper couldn't set the resource limit: %s.\n", strerror(errno)); 85 return; 86 } 87 88 if (close(webProcessSocket) == -1) { 89 fprintf(stderr, "Failed to close socket %d: %s.\n", webProcessSocket, strerror(errno)); 90 return; 91 } 92 93 char message; 94 // We expect a 'C' (ChrootMe) message from the WebProcess. 95 if (read(helperSocket, &message, 1) != 1) { 96 fprintf(stderr, "Failed to read message from the web process: %s %d.\n", strerror(errno), errno); 97 return; 98 } 99 100 if (message != MSG_CHROOTME) { 101 fprintf(stderr, "Wrong message recieved: %x.\n", message); 102 return; 103 } 104 105 struct stat sandboxDirectoryInfo; 106 if (lstat(sandboxDirectory, &sandboxDirectoryInfo) == -1) { 107 fprintf(stderr, "Sandbox directory (%s) is not available: %s.\n", sandboxDirectory, strerror(errno)); 108 return; 109 } 110 111 if (!S_ISDIR(sandboxDirectoryInfo.st_mode)) { 112 fprintf(stderr, "%s is not a directory!\n", sandboxDirectory); 113 return; 114 } 115 116 if (chroot(sandboxDirectory) == -1) { 117 fprintf(stderr, "Chrooting failed: %s.\n", strerror(errno)); 118 return; 119 } 120 121 // Chroot only changes the root directory of the calling process but doesn't change 122 // the current working directory. Therefore, if we don't do it manually a malicious user 123 // could break out the jail with relative paths. 124 if (chdir("/") == -1) { 125 fprintf(stderr, "Couldn't change the working directory to /.: %s\n", strerror(errno)); 126 return; 127 } 128 129 // Sending acknowledgement to the WebProcess that the sandboxing was successfull. 130 message = MSG_CHROOTED; 131 if (write(helperSocket, &message, 1) != 1) { 132 fprintf(stderr, "Couldn't send acknowledgement to WebProcess: %s.\n", strerror(errno)); 133 return; 134 } 135 exit(EXIT_SUCCESS); 136} 137 138static bool setEnvironmentVariablesForChangeRootHelper(pid_t pid, int helperSocket, int webProcessSocket) 139{ 140 const int descriptorSize = 32; 141 char socketDescriptor[descriptorSize]; 142 char sandboxHelperPID[descriptorSize]; 143 144 int length = snprintf(sandboxHelperPID, sizeof(sandboxHelperPID), "%u", pid); 145 if (length < 0 || length >= sizeof(sandboxHelperPID)) { 146 fprintf(stderr, "Failed to convert the sandbox helper PID to a string.\n"); 147 return false; 148 } 149 150 if (setenv(SANDBOX_HELPER_PID, sandboxHelperPID, 1) == -1) { 151 fprintf(stderr, "Couldn't set the SBX_HELPER_PID environment variable: %s\n", strerror(errno)); 152 return false; 153 } 154 155 length = snprintf(socketDescriptor, sizeof(socketDescriptor), "%u", webProcessSocket); 156 if (length < 0 || length >= sizeof(socketDescriptor)) { 157 fprintf(stderr, "Failed to convert the sandbox helper file descriptor to a string.\n"); 158 return false; 159 } 160 161 if (setenv(SANDBOX_DESCRIPTOR, socketDescriptor, 1) == -1) { 162 fprintf(stderr, "Failed to store the helper's file descriptor into an environment variable: %s.\n", strerror(errno)); 163 return false; 164 } 165 166 if (close(helperSocket) == -1) { 167 fprintf(stderr, "Closing of %d failed: %s.\n", helperSocket, strerror(errno)); 168 return false; 169 } 170 171 return true; 172} 173 174static bool prepareAndStartChangeRootHelper() 175{ 176 int socketPair[2]; 177 if (socketpair(AF_UNIX, SOCK_STREAM, 0, socketPair) == -1) { 178 fprintf(stderr, "Couldn't create socketpair: %s\n", strerror(errno)); 179 return false; 180 } 181 182 pid_t pid = syscall(SYS_clone, CLONE_FS | SIGCHLD, 0, 0, 0); 183 if (pid == -1) { 184 fprintf(stderr, "Clone failed: %s\n", strerror(errno)); 185 return false; 186 } 187 if (!pid) { 188 // Child process: we start the chroot helper which waits for the "ChrootMe" 189 // message from the WebProcess. If we are successed, then we won't return. 190 launchChangeRootHelper(socketPair[0], socketPair[1]); 191 // We reach this part only if launchChrootHelper() failed, instead it should have exited. 192 exit(EXIT_FAILURE); 193 return false; 194 } 195 196 // Parent process: exports the pid of the helper and the socket id so the 197 // helper and the WebProcess can communicate. 198 return setEnvironmentVariablesForChangeRootHelper(pid, socketPair[0], socketPair[1]); 199} 200 201// Setting linux capabilities (permitted, effective and inheritable) for the current process. 202// Permitted set indicates the capabilities what could be set for the process. 203// Effective set is a subset of permitted set, they are actually effective. 204// Inheritable set indicates the capabilities what the children will inherit from the current process. 205static bool setCapabilities(cap_value_t* capabilityList, int length) 206{ 207 // Capabilities should be initialized without flags. 208 cap_t capabilities = cap_init(); 209 if (!capabilities) { 210 fprintf(stderr, "Failed to initialize process capabilities: %s.\n", strerror(errno)); 211 return false; 212 } 213 214 if (cap_clear(capabilities) == -1) { 215 fprintf(stderr, "Failed to clear process capabilities: %s.\n", strerror(errno)); 216 return false; 217 } 218 219 if (capabilityList && length) { 220 if (cap_set_flag(capabilities, CAP_EFFECTIVE, length, capabilityList, CAP_SET) == -1 221 || cap_set_flag(capabilities, CAP_INHERITABLE, length, capabilityList, CAP_SET) == -1 222 || cap_set_flag(capabilities, CAP_PERMITTED, length, capabilityList, CAP_SET) == -1) { 223 fprintf(stderr, "Failed to set process capability flags: %s.\n", strerror(errno)); 224 cap_free(capabilities); 225 return false; 226 } 227 } 228 229 if (cap_set_proc(capabilities) == -1) { 230 fprintf(stderr, "Failed to set process capabilities: %s.\n", strerror(errno)); 231 cap_free(capabilities); 232 return false; 233 } 234 235 cap_free(capabilities); 236 return true; 237} 238 239static bool dropPrivileges() 240{ 241 // We become explicitely non dumpable. 242 if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1) { 243 fprintf(stderr, "Setting dumpable is failed: %s\n", strerror(errno)); 244 return false; 245 } 246 247 if (setresgid(sandboxUserGID, sandboxUserGID, sandboxUserGID) == -1) { 248 fprintf(stderr, "Failed to fallback to group: %d.\n", sandboxUserGID); 249 return false; 250 } 251 252 if (setresuid(sandboxUserUID, sandboxUserUID, sandboxUserUID) == -1) { 253 fprintf(stderr, "Failed to fallback to user: %d.\n", sandboxUserUID); 254 return false; 255 } 256 257 // Drop all capabilities. Again, setuid() normally takes care of this if we had euid 0. 258 return setCapabilities(0, 0); 259} 260 261static bool fileExists(const char* path) 262{ 263 struct stat fileStat; 264 if (lstat(path, &fileStat) == -1) { 265 if (errno == ENOENT) 266 return false; 267 } 268 return true; 269} 270 271static mode_t directoryPermissions(const char* directory) 272{ 273 struct stat fileStat; 274 if (lstat(directory, &fileStat) == -1) { 275 fprintf(stderr, "Failed to obtain information about directory (%s): %s\n", directory, strerror(errno)); 276 return false; 277 } 278 return fileStat.st_mode; 279} 280 281static bool createDirectory(char* pathToCreate, const char* nextDirectoryToCreate) 282{ 283 strlcat(pathToCreate, nextDirectoryToCreate, maximumPathLength); 284 285 char pathToCreateInSandbox[maximumPathLength]; 286 appendDirectoryComponent(pathToCreateInSandbox, sandboxDirectory, pathToCreate); 287 288 mode_t mode = directoryPermissions(pathToCreate); 289 if (mkdir(pathToCreateInSandbox, mode) == -1) { 290 if (errno != EEXIST) { 291 fprintf(stderr, "Creation of %s failed: %s\n", pathToCreateInSandbox, strerror(errno)); 292 return false; 293 } 294 } 295 296 struct stat fileInfo; 297 if (lstat(pathToCreate, &fileInfo) == -1) { 298 fprintf(stderr, "Couldn't obtain information about directory (%s): %s\n", pathToCreate, strerror(errno)); 299 return false; 300 } 301 if (fileInfo.st_uid == getuid()) { 302 if (chown(pathToCreateInSandbox, sandboxUserUID, sandboxUserGID) == -1) { 303 fprintf(stderr, "Failed to assign the ownership of %s to the sandbox user: %s.\n", pathToCreateInSandbox, strerror(errno)); 304 return false; 305 } 306 } 307 if (chmod(pathToCreateInSandbox, fileInfo.st_mode) == -1) { 308 fprintf(stderr, "Failed to set the permissions of %s: %s.\n", pathToCreateInSandbox, strerror(errno)); 309 return false; 310 } 311 return true; 312} 313 314// This function creates a directory chain with the given path. 315// First, it splits up the path by '/'-s and walks through the chunks from the base directory. 316// It checks the existance of the actual path and creates it if it doesn't exist yet. 317static bool createDirectoryChain(const char* path) 318{ 319 char fullPathInSandbox[maximumPathLength]; 320 appendDirectoryComponent(fullPathInSandbox, sandboxDirectory, path); 321 322 if (fileExists(fullPathInSandbox)) 323 return true; 324 325 char alreadyCreatedPath[maximumPathLength]; 326 alreadyCreatedPath[0] = '\0'; 327 // startPos is (path + 1) because we skip the first '/'. 328 const char* startPos = path + 1; 329 const char* endPos; 330 while ((endPos = strchr(startPos, '/'))) { 331 char nextDirectoryToCreate[maximumPathLength]; 332 strlcpy(nextDirectoryToCreate, startPos - 1, strnlen(startPos - 1, endPos - startPos + 1) + 1); 333 334 if (!createDirectory(alreadyCreatedPath, nextDirectoryToCreate)) 335 return false; 336 startPos = endPos + 1; 337 } 338 // Create the last directory of the directory path. 339 alreadyCreatedPath[0] = '\0'; 340 return createDirectory(alreadyCreatedPath, path); 341} 342 343static bool createDeviceFiles() 344{ 345 const char* devDirectory = "/dev"; 346 if (!createDirectoryChain(devDirectory)) 347 return false; 348 349 const char* devices[2] = { "/dev/random", "/dev/urandom" }; 350 for (int i = 0; i < sizeof(devices) / sizeof(devices[0]); ++i) { 351 struct stat status; 352 if (lstat(devices[i], &status)) { 353 fprintf(stderr, "Failed to stat device file (%s): %s\n", devices[i], strerror(errno)); 354 return false; 355 } 356 dev_t dev = status.st_rdev; 357 358 // Both needed device files (/dev/random and /dev/urandom) are character m_devices and their permissions should be: rw-rw-rw-. 359 char device[maximumPathLength]; 360 appendDirectoryComponent(device, sandboxDirectory, devices[i]); 361 362 if (mknod(device, S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, makedev(major(dev), minor(dev))) == -1) { 363 if (errno != EEXIST) { 364 fprintf(stderr, "Couldn't create device file %s: %s\n", device, strerror(errno)); 365 return false; 366 } 367 } 368 } 369 return true; 370} 371 372static bool mountFileSystems() 373{ 374 const char* procPath = "/proc"; 375 if (!createDirectoryChain(procPath)) 376 return false; 377 char procPathInSandbox[maximumPathLength]; 378 appendDirectoryComponent(procPathInSandbox, sandboxDirectory, procPath); 379 380 if (mount(procPath, procPathInSandbox, "proc", 0, 0) == -1) { 381 if (errno != EBUSY) { 382 fprintf(stderr, "Failed to mount '%s': %s\n", procPath, strerror(errno)); 383 return false; 384 } 385 } 386 387 const char* sharedMemoryPath = "/run/shm"; 388 if (!createDirectoryChain(sharedMemoryPath)) { 389 fprintf(stderr, "Failed to create directory for /run/shm in the sandbox: %s.\n", strerror(errno)); 390 return false; 391 } 392 char sharedMemoryPathInSandbox[maximumPathLength]; 393 appendDirectoryComponent(sharedMemoryPathInSandbox, sandboxDirectory, sharedMemoryPath); 394 395 if (mount(sharedMemoryPath, sharedMemoryPathInSandbox, "tmpfs", 0, 0) == -1) { 396 if (errno != EBUSY) { 397 fprintf(stderr, "Failed to mount '%s': %s.\n", sharedMemoryPath, strerror(errno)); 398 return false; 399 } 400 } 401 return true; 402} 403 404static bool linkFile(const char* sourceFile, const char* targetFile) 405{ 406 char oldPath[maximumPathLength]; 407 char targetPath[maximumPathLength]; 408 strlcpy(oldPath, sourceFile, maximumPathLength); 409 strlcpy(targetPath, targetFile, maximumPathLength); 410 411 while (true) { 412 struct stat fileInfo; 413 if (lstat(oldPath, &fileInfo) == -1) { 414 if (errno != ENOENT) { 415 fprintf(stderr, "Couldn't obtain information about %s: %s\n", oldPath, strerror(errno)); 416 return false; 417 } 418 // If the original file doesn't exist (e.g. dangling links) then we can ignore it 419 // in the sandbox too. 420 return true; 421 } 422 const char* endOfBaseDirectoryInSource = strrchr(oldPath, '/'); 423 if (!endOfBaseDirectoryInSource) { 424 fprintf(stderr, "Invalid source: %s.\n", oldPath); 425 return false; 426 } 427 428 char baseDirectoryOfSource[maximumPathLength]; 429 // To determine the length of the base directory we have to consider the tailing 430 // slash (+1) and adding plus one because strlcpy() copies (maxLength - 1) characters 431 // from the source. 432 strlcpy(baseDirectoryOfSource, oldPath, endOfBaseDirectoryInSource - oldPath + 2); 433 434 if (!createDirectoryChain(baseDirectoryOfSource)) { 435 fprintf(stderr, "Creating %s failed: %s.\n", baseDirectoryOfSource, strerror(errno)); 436 return false; 437 } 438 439 if (link(oldPath, targetPath) == -1) { 440 if (errno != EEXIST && errno != ENOENT) { 441 fprintf(stderr, "Linking %s failed: %s.\n", oldPath, strerror(errno)); 442 return false; 443 } 444 } 445 446 // Handle symlinks. We don't want to have dangling links in the sandbox. So we have to 447 // follow them and put the whole link chain into the sandbox. 448 if ((fileInfo.st_mode & S_IFMT) != S_IFLNK) 449 break; 450 451 char symlinkTarget[maximumPathLength]; 452 int lengthOfTheLink = readlink(oldPath, symlinkTarget, sizeof(symlinkTarget) - 1); 453 if (lengthOfTheLink > 0) 454 symlinkTarget[lengthOfTheLink] = '\0'; 455 456 char symlinkTargetInRealWorld[maximumPathLength]; 457 char symlinkTargetInSandbox[maximumPathLength]; 458 459 // Making difference between relative and absolute paths. 460 // If the symlinks target starts with '/' then we have nothing to do with it. 461 // Otherwise it's a relative path and we have to concatenate it to the current 462 // path to obtain the target. 463 if (symlinkTarget[0] == '/') { 464 strlcpy(symlinkTargetInRealWorld, symlinkTarget, maximumPathLength); 465 appendDirectoryComponent(symlinkTargetInSandbox, sandboxDirectory, symlinkTarget); 466 } else { 467 appendDirectoryComponent(symlinkTargetInRealWorld, baseDirectoryOfSource, symlinkTarget); 468 appendDirectoryComponent(symlinkTargetInSandbox, sandboxDirectory, symlinkTargetInRealWorld); 469 } 470 471 // Initialize oldPath and targetPath variables for the next loop of while. 472 oldPath[0] = '\0'; 473 targetPath[0] = '\0'; 474 strlcat(oldPath, symlinkTargetInRealWorld, maximumPathLength); 475 strlcat(targetPath, symlinkTargetInSandbox, maximumPathLength); 476 } 477 return true; 478} 479 480// This function extends the standard link function by linking directories and all their contents 481// and subdirectories recursively. 482static bool linkDirectory(const char* sourceDirectoryPath, const char* targetDirectoryPath) 483{ 484 if (!createDirectoryChain(sourceDirectoryPath)) 485 return false; 486 DIR* directory = opendir(sourceDirectoryPath); 487 if (!directory) { 488 fprintf(stderr, "Couldn't open directory %s: %s\n", sourceDirectoryPath, strerror(errno)); 489 return false; 490 } 491 492 while (struct dirent *directoryInfo = readdir(directory)) { 493 char* fileName = directoryInfo->d_name; 494 // We must not link '.' and ".." into the sandbox. 495 if (!strcmp(fileName, ".") || !strcmp(fileName, "..")) 496 continue; 497 char sourceFile[maximumPathLength]; 498 char targetFile[maximumPathLength]; 499 appendDirectoryComponent(sourceFile, sourceDirectoryPath, fileName); 500 appendDirectoryComponent(targetFile, targetDirectoryPath, fileName); 501 502 bool returnValue; 503 if (directoryInfo->d_type == DT_DIR) { 504 strncat(sourceFile, "/", 1); 505 strncat(targetFile, "/", 1); 506 returnValue = linkDirectory(sourceFile, targetFile); 507 } else 508 returnValue = linkFile(sourceFile, targetFile); 509 if (!returnValue) 510 return false; 511 } 512 513 // Restore the original modification time of the directories because 514 // it could have meaning e.g. in the hash generation of cache files. 515 struct stat fileStat; 516 if (lstat(sourceDirectoryPath, &fileStat) == -1) { 517 fprintf(stderr, "Failed to obtain information about the directory '%s': %s\n", sourceDirectoryPath, strerror(errno)); 518 return false; 519 } 520 struct utimbuf times; 521 times.actime = fileStat.st_atime; 522 times.modtime = fileStat.st_mtime; 523 if (utime(targetDirectoryPath, ×) == -1) { 524 fprintf(stderr, "Couldn't set back the last modification time of '%s': %s\n", targetDirectoryPath, strerror(errno)); 525 return false; 526 } 527 return true; 528} 529 530static bool collectRunTimeDependencies() 531{ 532 // The list of empirically gathered library dependencies. 533 const char* runtimeDependencies[] = { 534 "libnss_dns.so", 535 "libresolv.so", 536 "libssl.so", 537 "libcrypto.so" 538 }; 539 540 for (int i = 0; i < sizeof(runtimeDependencies) / sizeof(runtimeDependencies[0]); ++i) { 541 // To obtain the path of the runtime dependencies we open them with dlopen. 542 // With the handle supplied by dlopen we can obtain information about the dynamically 543 // linked libraries, so the path where are they installed. 544 void* handle = dlopen(runtimeDependencies[i], RTLD_LAZY); 545 if (!handle) { 546 fprintf(stderr, "Couldn't get the handler of %s: %s\n", runtimeDependencies[i], dlerror()); 547 return false; 548 } 549 550 struct link_map* linkMap; 551 if (dlinfo(handle, RTLD_DI_LINKMAP, &linkMap) == -1) { 552 fprintf(stderr, "Couldn't get information about %s: %s\n", runtimeDependencies[i], dlerror()); 553 return false; 554 } 555 556 if (!linkMap) { 557 fprintf(stderr, "Couldn't get the linkmap of %s: %s.\n", runtimeDependencies[i], strerror(errno)); 558 return false; 559 } 560 561 char pathOfTheLibraryInSandbox[maximumPathLength]; 562 appendDirectoryComponent(pathOfTheLibraryInSandbox, sandboxDirectory, linkMap->l_name); 563 if (!linkFile(linkMap->l_name, pathOfTheLibraryInSandbox)) { 564 fprintf(stderr, "Linking runtime dependency: %s failed: %s\n", linkMap->l_name, strerror(errno)); 565 dlclose(handle); 566 return false; 567 } 568 dlclose(handle); 569 } 570 return true; 571} 572 573static bool setupXauthorityForNobodyUser() 574{ 575 // To be able use X inside the sandbox an .Xauthority file must be exist inside it, 576 // owned by the sandboxuser. Furthermore, XAUTHORITY environment variable must point to it. 577 char buffer[BUFSIZ]; 578 size_t size; 579 struct passwd* realUser = getpwuid(getuid()); 580 if (!realUser) { 581 fprintf(stderr, "Couldn't obtain the current user: %s\n", strerror(errno)); 582 return false; 583 } 584 585 char xauthorityOfRealUser[maximumPathLength]; 586 char xauthorityInSandbox[maximumPathLength]; 587 appendDirectoryComponent(xauthorityOfRealUser, realUser->pw_dir, "/.Xauthority"); 588 appendDirectoryComponent(xauthorityInSandbox, sandboxDirectory, xauthorityOfRealUser); 589 590 FILE* source = fopen(xauthorityOfRealUser, "rb"); 591 if (!source) { 592 fprintf(stderr, "Couldn't open %s: %s\n", xauthorityOfRealUser, strerror(errno)); 593 return false; 594 } 595 596 FILE* dest = fopen(xauthorityInSandbox, "wb"); 597 if (!dest) { 598 fprintf(stderr, "Couldn't open %s: %s\n", xauthorityInSandbox, strerror(errno)); 599 return false; 600 } 601 602 // We copy the .Xauthority file of the real user (instead of linking) because 'nobody' user 603 // should own it but we don't want to change the permissions of the original file. 604 while ((size = fread(buffer, 1, BUFSIZ, source))) { 605 if (fwrite(buffer, 1, size, dest) != size) { 606 fprintf(stderr, "Failed to copy .Xauthority to the sandbox: %s.\n", strerror(errno)); 607 return false; 608 } 609 } 610 611 if (fclose(source)) { 612 fprintf(stderr, "Closing the .Xauthority file of the real user failed: %s\n", strerror(errno)); 613 return false; 614 } 615 616 if (fclose(dest)) { 617 fprintf(stderr, "Closing the .Xauthority file of the sandbox user failed: %s\n", strerror(errno)); 618 return false; 619 } 620 621 if (chown(xauthorityInSandbox, sandboxUserUID, sandboxUserGID) == -1) { 622 fprintf(stderr, "Chowning .Xauthority (%s) failed: %s.\n", xauthorityInSandbox, strerror(errno)); 623 return false; 624 } 625 626 if (setenv("XAUTHORITY", xauthorityInSandbox, 1) == -1) { 627 fprintf(stderr, "Couldn't set the XAUTHORITY envrionment variable: %s\n", strerror(errno)); 628 return false; 629 } 630 return true; 631} 632 633static bool initializeSandbox() 634{ 635 // Create the sandbox directory. We only need to enter it, so 636 // the executable permission is needed only. 637 if (mkdir(sandboxDirectory, S_IFDIR | S_IXUSR | S_IXOTH) == -1) { 638 if (errno != EEXIST) { 639 fprintf(stderr, "Couldn't create the sandbox directory: %s\n", strerror(errno)); 640 return false; 641 } 642 } 643 644 if (!createDeviceFiles()) 645 return false; 646 647 if (!mountFileSystems()) 648 return false; 649 650 // Hard link cache and font directories into the sandbox environment. 651 struct passwd* userInfo = getpwuid(getuid()); 652 const char* home = userInfo->pw_dir; 653 654 char localDirectory[maximumPathLength]; 655 char cacheDirectory[maximumPathLength]; 656 char fontDirectory[maximumPathLength]; 657 658 appendDirectoryComponent(localDirectory, home, "/.local/share/"); 659 appendDirectoryComponent(cacheDirectory, home, "/.cache/"); 660 appendDirectoryComponent(fontDirectory, home, "/.fontconfig/"); 661 662 const char* linkedDirectories[] = { 663 cacheDirectory, 664 fontDirectory, 665 localDirectory, 666 "/etc/fonts/", 667 "/etc/ssl/certs/", 668 "/var/cache/fontconfig/", 669 "/usr/share/fonts/" 670 }; 671 672 for (int i = 0; i < sizeof(linkedDirectories) / sizeof(linkedDirectories[0]); ++i) { 673 char linkedDirectoryInSandbox[maximumPathLength]; 674 appendDirectoryComponent(linkedDirectoryInSandbox, sandboxDirectory, linkedDirectories[i]); 675 676 if (!linkDirectory(linkedDirectories[i], linkedDirectoryInSandbox)) 677 return false; 678 } 679 680 if (!setupXauthorityForNobodyUser()) 681 return false; 682 683 return collectRunTimeDependencies(); 684} 685 686static bool restrictCapabilities() 687{ 688 // Capabilities we need. 689 // CAP_SYS_ADMIN capabilty is added because cloning with CLONE_NEWPID flag later will need it. 690 cap_value_t capabilityList[] = { CAP_SETUID, CAP_SETGID, CAP_SYS_ADMIN, CAP_SYS_CHROOT}; 691 692 // Reduce capabilities to what we need. 693 // Although we still have root euid and we keep root equivalent capabilities, 694 // we removed (= didn't add) CAP_SYS_RESSOURCE capabilites and this resulted that 695 // the setrlimit function with RLIMIT_NOFILE will be effective later. 696 if (!setCapabilities(capabilityList, sizeof(capabilityList) / sizeof(capabilityList[0]))) { 697 fprintf(stderr, "Could not adjust process capabilities: %s.\n", strerror(errno)); 698 return false; 699 } 700 return true; 701} 702 703static bool moveToNewPIDNamespace() 704{ 705 // CLONE_NEWPID and CLONE_FS should be in that order. 706 // We can't share filesystems accross namespaces. 707 int status; 708 pid_t expectedPID; 709 pid_t pid = syscall(SYS_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); 710 711 if (pid == -1) { 712 fprintf(stderr, "Cloning is failed: %s\n", strerror(errno)); 713 return false; 714 } 715 if (!pid) { 716 // Child should run with pid number 1 in the new namespace. 717 if (getpid() != 1) { 718 fprintf(stderr, "Couldn't create a new PID namespace.\n"); 719 return false; 720 } 721 return true; 722 } 723 724 // We are waiting for our child (WebProcess). 725 // If this wait is successful it means that our child is terminated. 726 expectedPID = waitpid(pid, &status, 0); 727 if (expectedPID != pid) { 728 fprintf(stderr, "Process with PID %d terminated instead of the expected one with PID %d: %s.\n", expectedPID, pid, strerror(errno)); 729 exit(EXIT_FAILURE); 730 } 731 if (WIFEXITED(status)) 732 exit(WEXITSTATUS(status)); 733 exit(EXIT_SUCCESS); 734} 735 736static bool run(int argc, char *const argv[]) 737{ 738 struct passwd* userInfo = getpwuid(getuid()); 739 if (!userInfo) { 740 fprintf(stderr, "Couldn't get the current user: %s.\n", strerror(errno)); 741 return false; 742 } 743 appendDirectoryComponent(sandboxDirectory, userInfo->pw_dir, "/.wk2-sandbox"); 744 745 // Currently we use 'nobody' user as the sandbox user and fall back to the real user 746 // if we failed to get it (we could extend this in the future with a specific restricted user). 747 if (struct passwd* nobodyUser = getpwnam("nobody")) { 748 sandboxUserUID = nobodyUser->pw_uid; 749 sandboxUserGID = nobodyUser->pw_gid; 750 } else { 751 sandboxUserUID = getuid(); 752 sandboxUserGID = getgid(); 753 } 754 755 // We should have three parameters: 756 // path_of_this_binary path_of_the_webprocess socket_to_communicate_with_uiprocess 757 if (argc != 3) { 758 fprintf(stderr, "Starting SandboxProcess requires 3 parameters!\n"); 759 return false; 760 } 761 762 // SandboxProcess should be run with suid flag ... 763 if (geteuid()) { 764 fprintf(stderr, "The sandbox is not seteuid root.\n"); 765 return false; 766 } 767 768 // ... but not as root (not with sudo). 769 if (!getuid()) { 770 fprintf(stderr, "The sandbox is not designed to be run by root.\n"); 771 return false; 772 } 773 774 if (!initializeSandbox()) 775 return false; 776 777 if (!restrictCapabilities()) 778 return false; 779 780 // We move ourself and our children into a new PID namespace, 781 // where process IDs start from 0 again. 782 if (!moveToNewPIDNamespace()) 783 return false; 784 785 // Starting a helper what will waiting for the "chrootme" message from WebProcess. 786 if (!prepareAndStartChangeRootHelper()) 787 return false; 788 789 // We don't need any special privileges anymore. 790 if (!dropPrivileges()) 791 return false; 792 793 // Sanity check: if our effective or real uid/gid is still 0 (root) or 794 // we can set any of them to 0, then the dropping of privileges is failed. 795 // We ensure here that we cannot set root id after here. 796 if (!geteuid() || !getegid() || !setuid(0) || !setgid(0)) { 797 fprintf(stderr, "Dropping privileges failed!\n"); 798 return false; 799 } 800 801 // Start the WebProcess. 802 if (execl(argv[1], argv[1], argv[2], reinterpret_cast<char*>(0)) == -1) { 803 fprintf(stderr, "Couldn't start WebProcess: %s\n", strerror(errno)); 804 return false; 805 } 806 return true; 807} 808 809int main(int argc, char *const argv[]) 810{ 811 return run(argc, argv) ? 0 : 1; 812} 813