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, &times) == -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