~mqchael/pipelight/sandbox

« back to all changes in this revision

Viewing changes to src/sandbox.c

  • Committer: Michael Mueller
  • Date: 2013-09-14 20:54:09 UTC
  • Revision ID: git-v1:4326c753851f248386ab1db14df0a6053b76d3fd
Initial commit of sandbox

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#define _GNU_SOURCE
 
2
 
 
3
#include <sys/prctl.h>
 
4
#include <sys/mount.h>
 
5
#include <sys/socket.h>
 
6
#include <sys/stat.h>
 
7
#include <sys/un.h>
 
8
#include <sys/types.h>
 
9
#include <sys/mman.h>
 
10
#include <sys/ioctl.h>
 
11
#include <stdio.h>
 
12
#include <stdlib.h>
 
13
#include <stdbool.h>
 
14
#include <unistd.h>
 
15
#include <string.h>
 
16
#include <signal.h>
 
17
#include <sched.h>
 
18
#include <errno.h>
 
19
#include <limits.h>
 
20
#include <fcntl.h>
 
21
#include <inttypes.h>
 
22
#include <time.h>
 
23
#include <dirent.h>
 
24
#include <net/if.h>
 
25
 
 
26
#define CHROOTDIR               "/var/run/pipelight-sandbox-chroot"
 
27
#define SANDBOXSHM              "/pipelight-sandbox-shm"
 
28
#define ENVWRITEDIR             "SANDBOXWRITEDIR"
 
29
#define XSERVERSOCKET   "/tmp/.X11-unix/X0"
 
30
#define XSERVERDIR      "/tmp/.X11-unix"
 
31
#define STACK_SIZE              0x100000
 
32
#define SOCKET_FWD_SIZE 0x10000
 
33
#define MAXINTCHARLEN   21
 
34
 
 
35
#if !defined(PATH_MAX) || PATH_MAX < 0 || PATH_MAX > 4096
 
36
        #undef PATH_MAX
 
37
        #define PATH_MAX 4096
 
38
#endif
 
39
 
 
40
static char childStack[STACK_SIZE];
 
41
 
 
42
struct shmDataStruct{
 
43
        pid_t initPid;                                                  /* Pid of the init process in this sandbox */
 
44
        int refCount;                                                   /* refCount (number of processes started + 1) */
 
45
};
 
46
 
 
47
struct childDataStruct{
 
48
        char*                                   cwDir;                  /* current working dir */
 
49
        char*                                   writeDir;               /* directory where write access should be allowed */
 
50
        struct shmDataStruct    *shmData;               /* shared memory structure */
 
51
        int                                     server;                 /* xorg socket file handle */
 
52
};
 
53
 
 
54
/*
 
55
        Usage message
 
56
*/
 
57
static void usage(const char* argv0){
 
58
        fprintf(stdout, "Usage: %s EXECUTABLE [ARGS ...]\n", argv0);
 
59
        fprintf(stdout, " Runs a executable in a sandboxed environment.\n");
 
60
        fprintf(stdout, "\n");
 
61
        fprintf(stdout, " Arguments:\n");
 
62
        fprintf(stdout, "  --help                     shows this help\n");
 
63
        fprintf(stdout, "\n");
 
64
        fprintf(stdout, " Environment variables:\n");
 
65
        fprintf(stdout, "  " ENVWRITEDIR "            allow writing in this directory\n");
 
66
        fprintf(stdout, "\n");
 
67
        fprintf(stdout, " The sandbox uses the following techniques:\n");
 
68
        fprintf(stdout, "  - separate pid namespace\n");
 
69
        fprintf(stdout, "  - separate mount namespace\n");
 
70
        fprintf(stdout, "  - separate ipc namespace\n");
 
71
        fprintf(stdout, "  - separate network namespace      (no network allowed at all!)\n");
 
72
        fprintf(stdout, "  - chroot with minimal privileges  (nosuid, readonly, ...)\n");
 
73
        fprintf(stdout, "  - deny gaining root again         (only if PR_SET_NO_NEW_PRIVS is available)\n");
 
74
        fprintf(stdout, "\n");
 
75
        fprintf(stdout, " DISCLAIMER: Depending on what you are going to do these security methods\n");
 
76
        fprintf(stdout, "  might not be sufficient! For really evil stuff a virtual machine without\n");
 
77
        fprintf(stdout, "  network access should be always preferred!\n");
 
78
        fprintf(stdout, "\n");
 
79
        fprintf(stdout, "Report bugs to webmaster@fds-team.de\n");      
 
80
}
 
81
 
 
82
/*
 
83
        Forwards data between two sockets a <-> b and closes both socket handles after the connection is terminated
 
84
        (runs as regular user)
 
85
*/
 
86
static void socketForward(int a, int b){
 
87
        fd_set rfds, wfds;
 
88
        char bufa[SOCKET_FWD_SIZE]; /* data from b to a */
 
89
        char bufb[SOCKET_FWD_SIZE]; /* data from a to b */
 
90
        int buflena = 0;
 
91
        int buflenb = 0;
 
92
        int len;
 
93
 
 
94
        /* select maximum file descriptor */
 
95
        int nfds = ((a < b) ? b : a) + 1;
 
96
 
 
97
        while ( (a || buflenb) &&       /* either a still connected or data from a to b */
 
98
                        (b || buflena) ){       /* either b still connected or data from b to a */
 
99
 
 
100
                FD_ZERO(&rfds);
 
101
                if (a && buflenb < SOCKET_FWD_SIZE) FD_SET(a, &rfds);
 
102
                if (b && buflena < SOCKET_FWD_SIZE) FD_SET(b, &rfds);
 
103
 
 
104
                FD_ZERO(&wfds);
 
105
                if (a && buflena > 0) FD_SET(a, &wfds);
 
106
                if (b && buflenb > 0) FD_SET(b, &wfds);
 
107
 
 
108
                /* wait for socket events */
 
109
                if (select(nfds, &rfds, &wfds, NULL, NULL) < 0) break;
 
110
 
 
111
                /* read from a (to buffer b) */
 
112
                if (FD_ISSET(a, &rfds) && buflenb < SOCKET_FWD_SIZE){
 
113
                        len = recv(a, &bufb[buflenb], SOCKET_FWD_SIZE - buflenb, 0);
 
114
                        if ( len <= 0 ){ close(a); a = 0; }else{
 
115
                                buflenb += len;
 
116
                        }
 
117
                }
 
118
 
 
119
                /* read from b (to buffer a) */
 
120
                if (FD_ISSET(b, &rfds) && buflena < SOCKET_FWD_SIZE){
 
121
                        len = recv(b, &bufa[buflena], SOCKET_FWD_SIZE - buflena, 0);
 
122
                        if ( len <= 0 ){ close(b); b = 0; }else{
 
123
                                buflena += len;
 
124
                        }
 
125
                }
 
126
 
 
127
                /* write to b */
 
128
                if (FD_ISSET(b, &wfds) && buflenb > 0){
 
129
                        len = send(b, bufb, buflenb, 0);
 
130
                        if ( len <= 0 ){        close(b); b = 0; }else{
 
131
                                if (len < buflenb) memmove(bufb, &bufb[len], buflenb - len);
 
132
                                buflenb -= len;
 
133
                        }
 
134
                }
 
135
 
 
136
                /* write to a */
 
137
                if (FD_ISSET(a, &wfds) && buflena > 0){
 
138
                        len = send(a, bufa, buflena, 0);
 
139
                        if ( len <= 0 ){ close(a); a = 0; }else{
 
140
                                if (len < buflena) memmove(bufa, &bufa[len], buflena - len);
 
141
                                buflena -= len;
 
142
                        }
 
143
                }
 
144
 
 
145
        }
 
146
 
 
147
        if (a) close(a);
 
148
        if (b) close(b);
 
149
}
 
150
 
 
151
 
 
152
/*
 
153
        Opens a connection to the xserver and starts forwarding packets until the connectionis terminated by one side.
 
154
        (runs as regular user)
 
155
*/
 
156
static void socketForwardXorg(int client){
 
157
        struct sockaddr_un xorgAddress;
 
158
        int xorg;
 
159
 
 
160
        xorgAddress.sun_family = AF_UNIX;
 
161
        strncpy(xorgAddress.sun_path, XSERVERSOCKET, sizeof(xorgAddress.sun_path));
 
162
 
 
163
        xorg = socket(AF_UNIX, SOCK_STREAM, 0);
 
164
        if (xorg == -1){
 
165
                return;
 
166
        }
 
167
 
 
168
        if (connect(xorg, (struct sockaddr *)&xorgAddress, sizeof(struct sockaddr_un)) != 0){
 
169
                return;
 
170
        }
 
171
 
 
172
        /* forwards between both sockets */
 
173
        socketForward(client, xorg);
 
174
}
 
175
 
 
176
/*
 
177
        Chroots into the specified directory and ensures that no write access is possible!
 
178
        (RUNS AS ROOT!)
 
179
*/
 
180
static bool root_doChroot(char *cwd){
 
181
        #ifdef SANDBOXDEBUG
 
182
                fprintf(stderr, "[SANDBOX:%d] root_doChroot()\n", getpid());
 
183
        #endif
 
184
 
 
185
        /* switch to chroot */
 
186
        if (chroot(CHROOTDIR) != 0){
 
187
                perror("Failed to chroot to " CHROOTDIR ".");
 
188
                return false;
 
189
        }
 
190
 
 
191
        /* set current dir (required to ensure that the curdir is not outside) */
 
192
        if (chdir("/") != 0){
 
193
                perror("Failed to chdir to /.");
 
194
                return false;           
 
195
        }
 
196
 
 
197
        /* try to switch back to the original wd after chroot */
 
198
        if(cwd) chdir(cwd);
 
199
 
 
200
        return true;
 
201
}
 
202
 
 
203
/*
 
204
        Drops the rights to the specified realUser:realGroup
 
205
        (RUNS AS ROOT!)
 
206
*/
 
207
static bool root_doDropRights(int realUser, int realGroup){
 
208
        #ifdef SANDBOXDEBUG
 
209
                fprintf(stderr, "[SANDBOX:%d] root_doDropRights(%d, %d)\n", getpid(), realUser, realGroup);
 
210
        #endif
 
211
 
 
212
        /* Remove groups */
 
213
        if (setgroups(0, NULL) != 0){
 
214
                perror("Failed to set empty group list.");
 
215
                return false;
 
216
        }
 
217
 
 
218
        /* Try to set exactly the required rights */
 
219
        if (setuid(realUser) != 0 || setgid(realGroup) != 0){
 
220
                perror("Failed to change user/group.");
 
221
                return false;
 
222
        }
 
223
 
 
224
        /* Ensure that this really worked! (if not this would be fatal) */
 
225
        if (geteuid() != realUser || getegid() != realGroup ){
 
226
                fprintf(stderr, "Dropping root rights failed.\n");
 
227
                return false;
 
228
        }
 
229
 
 
230
        /* 
 
231
                prevent any child process from changing the user (i.e. get root rights) 
 
232
                (only available since kernel 3.5)
 
233
        */
 
234
        if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0){
 
235
                fprintf(stderr, "[SANDBOX] WARNING: PR_SET_NO_NEW_PRIVS could not be set, the sandbox may be less secure.\n");
 
236
        }
 
237
 
 
238
        /* Ensure that we don't have write access to the root directory */
 
239
        if (access("/", W_OK) == 0){
 
240
                fprintf(stderr, "You have write access to the root directory, sandbox not possible!\n");
 
241
                return false;
 
242
        }
 
243
 
 
244
        return true;
 
245
}
 
246
 
 
247
/*
 
248
        Setup mount namespace for chroot
 
249
        (RUNS AS ROOT!)
 
250
*/
 
251
static bool root_doSetupChrootMnt(char *writeDir){
 
252
        char pathBuffer[PATH_MAX + 1];
 
253
        int tmp;
 
254
 
 
255
        #ifdef SANDBOXDEBUG
 
256
                fprintf(stderr, "[SANDBOX:%d] root_doSetupChrootMnt('%s')\n", getpid(), writeDir);
 
257
        #endif
 
258
 
 
259
        if (geteuid() != 0){
 
260
                fprintf(stderr, "Unable to setup chroot mnt without root rights.\n");
 
261
                return false;
 
262
        }
 
263
 
 
264
        if (mkdir(CHROOTDIR, 700) != 0 && errno != EEXIST){
 
265
                perror("Failed to create chroot directory " CHROOTDIR ".");
 
266
                return false;
 
267
        }
 
268
 
 
269
        /* NOTE: all the following mounts don't affect the space outside! */
 
270
 
 
271
        /* readonly root */
 
272
        if (mount("/", CHROOTDIR, 0, MS_BIND | MS_NOSUID, NULL) != 0){
 
273
                perror("Failed to bind mount /.");
 
274
                return false;
 
275
        }
 
276
 
 
277
        if (mount("/", CHROOTDIR, 0, MS_BIND | MS_REMOUNT | MS_RDONLY | MS_NOSUID, NULL) != 0){
 
278
                perror("Failed to rebind mount / with readonly+nosuid rights.");
 
279
                return false;
 
280
        }       
 
281
 
 
282
        /* writeable path (if given) */
 
283
        if (writeDir){
 
284
                tmp = snprintf(pathBuffer, PATH_MAX + 1, "%s%s", CHROOTDIR, writeDir);
 
285
                if (tmp < 0 || tmp > PATH_MAX){
 
286
                        fprintf(stderr, "Your " ENVWRITEDIR " is too long.\n");
 
287
                        return false;
 
288
                }
 
289
 
 
290
                if (mount(writeDir, pathBuffer, 0, MS_BIND | MS_NOSUID | MS_NODEV, NULL) != 0){
 
291
                        perror("Failed to bind mount " ENVWRITEDIR ".");
 
292
                        return false;
 
293
                }
 
294
        }
 
295
 
 
296
        /* mount proc, so that we will only see processes inside */
 
297
        if (mount("proc", CHROOTDIR "/proc", "proc", 0, NULL) != 0){
 
298
                perror("Failed to mount /proc.");
 
299
                return false;
 
300
        }
 
301
 
 
302
        /* mount /dev and /dev/pts */
 
303
        if (mount("/dev", CHROOTDIR "/dev", 0, MS_BIND | MS_NOSUID | MS_NOEXEC, NULL) != 0){
 
304
                perror("Failed to bind mount /dev.");
 
305
                return false;
 
306
        }
 
307
 
 
308
        if (mount("/dev/pts", CHROOTDIR "/dev/pts", 0, MS_BIND | MS_NOSUID | MS_NOEXEC, NULL) != 0){
 
309
                perror("Failed to bind mount /dev/pts.");
 
310
                return false;
 
311
        }
 
312
 
 
313
        /* we need a /tmp directory, so we'll mount a tmpfs inside the sandbox */
 
314
        if (mount("tmpfs", CHROOTDIR "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) != 0){
 
315
                perror("Failed to mount /tmp.");
 
316
                return false;
 
317
        }
 
318
 
 
319
        /* create directory for the xserver redirection */
 
320
        if (mkdir(CHROOTDIR XSERVERDIR, 777) != 0){
 
321
                perror("Failed to mkdir " XSERVERDIR ".");
 
322
                return false;
 
323
        }
 
324
 
 
325
        return true;
 
326
}
 
327
 
 
328
/*
 
329
        Binds the xserver socket
 
330
        (RUNS AS ROOT!)
 
331
*/
 
332
static bool root_doSetupXServer(int server){
 
333
        struct sockaddr_un xorgAddress;
 
334
 
 
335
        #ifdef SANDBOXDEBUG
 
336
                fprintf(stderr, "[SANDBOX:%d] root_doSetupXServer(%d)\n", getpid(), server);
 
337
        #endif
 
338
 
 
339
        xorgAddress.sun_family = AF_UNIX;
 
340
        strncpy(xorgAddress.sun_path, XSERVERSOCKET, sizeof(xorgAddress.sun_path));
 
341
 
 
342
        /* bind the unix socket */
 
343
        if (bind(server, (struct sockaddr *)&xorgAddress, sizeof(struct sockaddr_un)) != 0){
 
344
                perror("Error while binding unix Xorg socket.");
 
345
                return false;
 
346
        }       
 
347
 
 
348
        /* start listening */
 
349
        if (listen(server, 5) != 0){
 
350
                perror("Error while listen on unix Xorg socket.");
 
351
                return false;
 
352
        }
 
353
 
 
354
        return true;
 
355
}
 
356
 
 
357
/*
 
358
        Switches to a namespace by PID and name of the corresponding namespace
 
359
        (RUNS AS ROOT!)
 
360
*/
 
361
static bool root_doSwitchNS( pid_t pid , char* ns, int nstype ){
 
362
        char pathBuffer[PATH_MAX + 1];
 
363
        int nsfd;
 
364
        int tmp;
 
365
 
 
366
        #ifdef SANDBOXDEBUG
 
367
                fprintf(stderr, "[SANDBOX:%d] root_doSwitchNS(%d, '%s', %d)\n", getpid(), pid, ns, nstype);
 
368
        #endif
 
369
 
 
370
        tmp = snprintf(pathBuffer, PATH_MAX + 1, "/proc/%d/ns/%s", pid, ns);
 
371
        if (tmp < 0 || tmp > PATH_MAX){
 
372
                fprintf(stderr, "Incorrect namespace name given.\n");
 
373
                return false;
 
374
        }
 
375
 
 
376
        nsfd = open(pathBuffer, O_RDONLY | O_CLOEXEC);
 
377
        if (nsfd == -1){
 
378
                perror("Unable to open namespace file handle.");
 
379
                return false;
 
380
        }
 
381
 
 
382
        if (setns(nsfd, nstype) != 0){
 
383
                perror("Unable to switch to namespace.");
 
384
                close(nsfd);
 
385
                return false;
 
386
        }
 
387
 
 
388
        close(nsfd);
 
389
        return true;
 
390
}
 
391
 
 
392
/*
 
393
        Checks if a process is really a chroot namespaced sandbox
 
394
        (RUNS AS ROOT!)
 
395
*/
 
396
static bool root_checkIsSandboxedProcess( pid_t pid ){
 
397
        char pathBuffer[PATH_MAX + 1];
 
398
        char resBuffer[PATH_MAX + 1];
 
399
        size_t resLength;
 
400
        char *canonChrootDir;
 
401
        bool res;
 
402
        int tmp;
 
403
 
 
404
        #ifdef SANDBOXDEBUG
 
405
                fprintf(stderr, "[SANDBOX:%d] root_checkIsSandboxedProcess(%d)", getpid(), pid);
 
406
        #endif
 
407
 
 
408
        tmp = snprintf(pathBuffer, PATH_MAX + 1, "/proc/%ld/root", (long)pid);
 
409
        if (tmp < 0 || tmp > PATH_MAX){
 
410
                fprintf(stderr, "Incorrect pid given.\n");
 
411
                return false;
 
412
        }
 
413
 
 
414
        resLength = readlink(pathBuffer, resBuffer, PATH_MAX);
 
415
        if (resLength <= 0 || resLength >= PATH_MAX){
 
416
                perror("Unable to read root directory of process.");
 
417
                return false;
 
418
        }
 
419
 
 
420
        /* make it nullterminated */
 
421
        resBuffer[resLength] = 0;
 
422
 
 
423
        /* get the full path */
 
424
        canonChrootDir = canonicalize_file_name(CHROOTDIR);
 
425
        if (!canonChrootDir){
 
426
                fprintf(stderr, "Unable to canonicalize path " CHROOTDIR ".\n");
 
427
                return NULL;
 
428
        }
 
429
 
 
430
        /* NOTE: we have to free canonChrootDir */
 
431
 
 
432
        res = (strcmp(resBuffer, canonChrootDir) == 0);
 
433
        if (!res){
 
434
                fprintf(stderr, "Hacking attempt! Sandbox initPID points to wrong process.\n");
 
435
        }
 
436
 
 
437
        free(canonChrootDir);
 
438
 
 
439
        return res;
 
440
}
 
441
 
 
442
/*
 
443
        Check privileges
 
444
        (RUNS AS ROOT)
 
445
*/
 
446
static bool root_checkPrivileges(int realUser, int realGroup){
 
447
        #ifdef SANDBOXDEBUG
 
448
                fprintf(stderr, "[SANDBOX:%d] root_checkPrivileges(%d, %d)\n", getpid(), realUser, realGroup);
 
449
        #endif
 
450
 
 
451
        /* check our privileges */
 
452
        if (geteuid() != 0){
 
453
                fprintf(stderr, "This program needs to be setuid and owned by root.\n");
 
454
                return false;
 
455
        }
 
456
 
 
457
        if (realUser == 0 || realGroup == 0){
 
458
                fprintf(stderr, "It is not allowed to run this program with root rights.\n");
 
459
                return false;
 
460
        }
 
461
 
 
462
        return true;
 
463
}
 
464
 
 
465
/*
 
466
        Reads the environment variable WRITEDIR and checks if everything is okay
 
467
        (RUNS AS ROOT)
 
468
*/
 
469
static char* root_getWriteDir(int realUser, int realGroup){
 
470
        char* writeDir = NULL;
 
471
        struct stat fileInfo;
 
472
        char* tmpDir;
 
473
 
 
474
        #ifdef SANDBOXDEBUG
 
475
                fprintf(stderr, "[SANDBOX:%d] root_getWriteDir(%d, %d)\n", getpid(), realUser, realGroup);
 
476
        #endif
 
477
 
 
478
        /* check for environment variable */
 
479
        tmpDir = getenv(ENVWRITEDIR);
 
480
        if (!tmpDir){
 
481
                fprintf(stderr, "No environment variable " ENVWRITEDIR " specified.\n");
 
482
                return NULL;
 
483
        }
 
484
 
 
485
        /* get the full path */
 
486
        writeDir = canonicalize_file_name(tmpDir);
 
487
        if (!writeDir){
 
488
                fprintf(stderr, "Unable to canonicalize path " ENVWRITEDIR ".\n");
 
489
                return NULL;
 
490
        }
 
491
 
 
492
        /* NOTE: we have to free the writeDir ptr when not successful */
 
493
 
 
494
        if (stat(writeDir, &fileInfo) != 0){
 
495
                perror("Failed to stat " ENVWRITEDIR ".");
 
496
                goto err;
 
497
        }
 
498
 
 
499
        /* this should be a directory */
 
500
        if (!S_ISDIR(fileInfo.st_mode)){
 
501
                fprintf(stderr, ENVWRITEDIR " must be a directory.\n");
 
502
                goto err;
 
503
        }
 
504
 
 
505
        /* 
 
506
                although this is technically not really a security enhancement, since filesystem rights
 
507
                are still enforced, but to prevent wrong usage we check if the user owns the directory
 
508
        */
 
509
        if (fileInfo.st_uid != realUser || fileInfo.st_gid != realGroup){
 
510
                fprintf(stderr, ENVWRITEDIR " must be a owned by you.\n");
 
511
                goto err;
 
512
        }
 
513
 
 
514
        return writeDir;
 
515
 
 
516
err:
 
517
        if(writeDir) free(writeDir);
 
518
        return NULL;
 
519
}
 
520
 
 
521
/*
 
522
        Returns a memory location with the mapped shm
 
523
        (RUNS AS ROOT)
 
524
*/
 
525
static struct shmDataStruct* root_getShm(int realUser, int realGroup, char *writeDir){
 
526
        struct shmDataStruct* shmData;
 
527
        char shmBuffer[PATH_MAX + 1];
 
528
        struct stat fileInfo;
 
529
        int shmfd;
 
530
        int tmp;
 
531
 
 
532
        #ifdef SANDBOXDEBUG
 
533
                fprintf(stderr, "[SANDBOX:%d] root_getShm()\n", getpid());
 
534
        #endif
 
535
 
 
536
        /* NOTE: this code assumes that the newly created shm is initialized with zeros! */
 
537
 
 
538
        if (writeDir){
 
539
                if (stat(writeDir, &fileInfo) != 0){
 
540
                        perror("Failed to stat " ENVWRITEDIR ".");
 
541
                        return false;
 
542
                }
 
543
 
 
544
                tmp = snprintf(shmBuffer, PATH_MAX + 1, SANDBOXSHM ".%d.%d.%" PRIu64 ".%" PRIu64 ".rw", realUser, realGroup, \
 
545
                                (uint64_t)fileInfo.st_dev, (uint64_t)fileInfo.st_ino);
 
546
 
 
547
        }else{
 
548
 
 
549
                tmp = snprintf(shmBuffer, PATH_MAX + 1, SANDBOXSHM ".%d.%d.ro", realUser, realGroup);
 
550
        }
 
551
 
 
552
        if (tmp < 0 || tmp > PATH_MAX){
 
553
                fprintf(stderr, "Your sandbox directory " SANDBOXSHM " is too long.\n");
 
554
                return false;
 
555
        }
 
556
 
 
557
        /* open shared memory - has FD_CLOEXEC set automatically */
 
558
        shmfd = shm_open(shmBuffer, O_RDWR | O_CREAT, 0600);
 
559
        if (shmfd == -1){
 
560
                perror("Unable to connect to shm IPC channel for " ENVWRITEDIR);
 
561
                return NULL;
 
562
        }
 
563
 
 
564
        /* ensure that the channel has the required size */
 
565
        if (ftruncate(shmfd, sizeof(struct shmDataStruct)) != 0){
 
566
                perror("Unable to set size of shm IPC channel for " ENVWRITEDIR);
 
567
                close(shmfd);
 
568
                return NULL;
 
569
        }
 
570
 
 
571
        /* map it! */
 
572
        shmData = mmap(NULL, sizeof(struct shmDataStruct), PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
 
573
        if (shmData == MAP_FAILED){
 
574
                perror("Unable to map shm IPC channel for " ENVWRITEDIR);
 
575
                close(shmfd);
 
576
                return NULL;
 
577
        }
 
578
 
 
579
        /* close file descriptor, mmap has incremented the refcount */
 
580
        close(shmfd);
 
581
        return shmData;
 
582
}
 
583
 
 
584
/*
 
585
        Brings a network device up
 
586
        (RUNS AS ROOT)
 
587
*/
 
588
static bool root_interfaceUp(char *name){
 
589
        struct ifreq ifr;
 
590
        int sock;
 
591
        bool res;
 
592
 
 
593
        /* create socket */
 
594
        sock = socket(AF_INET, SOCK_DGRAM, 0);
 
595
        if (sock == -1){
 
596
                perror("Error while creating interface socket.\n");
 
597
                return false;
 
598
        }
 
599
 
 
600
        /* create and send command */
 
601
        memset(&ifr, 0, sizeof ifr);
 
602
        strncpy(ifr.ifr_name, name, IFNAMSIZ);
 
603
        ifr.ifr_flags |= IFF_UP;
 
604
 
 
605
        res = (ioctl(sock, SIOCSIFFLAGS, &ifr) == 0);
 
606
        if (!res){
 
607
                perror("Unable to bring up loopback device.");
 
608
        }
 
609
 
 
610
        /* close socket */
 
611
        close(sock);
 
612
 
 
613
        return res;
 
614
}
 
615
 
 
616
/*
 
617
        Increments the value only if it is nonzero
 
618
*/
 
619
static bool __sync_inc_if_nonzero( int *ptr ){
 
620
        int value;
 
621
 
 
622
        while (1){
 
623
                value = *ptr;
 
624
                if (!value || __sync_bool_compare_and_swap(ptr, value, value+1)) break;
 
625
        }
 
626
 
 
627
        return (value != 0);
 
628
}
 
629
 
 
630
/*
 
631
        Decrements the value only if it is one
 
632
*/
 
633
static bool __sync_dec_if_one( int *ptr ){
 
634
        return __sync_bool_compare_and_swap(ptr, 1, 0);
 
635
}
 
636
 
 
637
/*
 
638
        Several functions to wait for child termination
 
639
*/
 
640
bool processChildTerminated(int pid){
 
641
        int res;
 
642
        int status;
 
643
 
 
644
        res = waitpid(pid, &status, WNOHANG);
 
645
        if(res == -1){
 
646
                perror("Waiting for pid failed.");
 
647
                return true;
 
648
        }
 
649
        return (res == pid) && WIFEXITED(status);
 
650
}
 
651
 
 
652
bool processNonchildTerminated(int pid){
 
653
        return (kill(pid, 0) != 0);
 
654
}
 
655
 
 
656
bool processAllchildsTerminated(){
 
657
        int res = waitpid(-1, NULL, WNOHANG);
 
658
        return (res == -1 && errno == ECHILD);
 
659
}
 
660
 
 
661
 
 
662
/*
 
663
        Closes all file descriptors above a threshold
 
664
        (runs as regular user)
 
665
*/
 
666
static bool closeFileDescriptorsAbove(int minfd){
 
667
        char pathBuffer[PATH_MAX + 1];
 
668
        char *endp;
 
669
        int fd;
 
670
        int tmp;
 
671
 
 
672
        tmp = snprintf(pathBuffer, PATH_MAX + 1, "/proc/%ld/fd", (long)getpid());
 
673
        if (tmp < 0 || tmp > PATH_MAX){
 
674
                fprintf(stderr, "Incorrect pid given.\n");
 
675
                return false;
 
676
        }
 
677
 
 
678
        DIR *dir = opendir(pathBuffer);
 
679
        struct dirent *dirEnt;
 
680
 
 
681
        if (!dir){
 
682
                perror("Failed to open list of file descriptors.");
 
683
                return false;
 
684
        }
 
685
 
 
686
        while (dirEnt = readdir(dir)){
 
687
                fd = strtol(dirEnt->d_name, &endp, 10);
 
688
 
 
689
                if (dirEnt->d_name == endp || *endp != 0)
 
690
                        continue;
 
691
 
 
692
                if (fd < 0 || fd > INT_MAX )
 
693
                        continue;
 
694
 
 
695
                if (fd < minfd || fd == dirfd(dir))
 
696
                        continue;
 
697
 
 
698
                close(fd);
 
699
        }
 
700
 
 
701
        closedir(dir);
 
702
        return true;
 
703
}
 
704
 
 
705
/*
 
706
        Starts the xserver daemon
 
707
        (runs as normal user)
 
708
*/
 
709
static bool startXserverDaemon(int realUser, int realGroup, int server, int initPid){
 
710
        pid_t daemonPid;
 
711
 
 
712
        #ifdef SANDBOXDEBUG
 
713
                fprintf(stderr, "[SANDBOX:%d] startXserverDaemon(%d, %d, %d, %d)\n", getpid(), realUser, realGroup, server, initPid);
 
714
        #endif
 
715
 
 
716
        daemonPid = fork();
 
717
        if (daemonPid == 0){ /* daemon */
 
718
                struct timeval timeout;
 
719
                fd_set fds;
 
720
                int status;
 
721
                int fd;
 
722
 
 
723
                /* Drop rights */
 
724
                if (!root_doDropRights(realUser, realGroup)){
 
725
                        fprintf(stderr, "Failed to drop rights.\n");
 
726
                        exit(1);
 
727
                }
 
728
 
 
729
                /* we don't need child notifications, otherwise we will get [defunct] zombies */
 
730
                signal(SIGCHLD, SIG_IGN);
 
731
 
 
732
                while ( !processNonchildTerminated(initPid) ){
 
733
 
 
734
                        FD_ZERO(&fds);
 
735
                        FD_SET(server, &fds);
 
736
                        timeout.tv_sec  = 1;
 
737
                        timeout.tv_usec = 0;
 
738
                        
 
739
                        /* check for events */
 
740
                        if (select(server+1, &fds, NULL, NULL, &timeout) < 0) break;
 
741
                        if (!FD_ISSET(server, &fds)) continue;
 
742
 
 
743
                        /* accept incoming sockets */
 
744
                        int client = accept(server, NULL, 0);
 
745
                        if(client < 0){
 
746
                                continue;
 
747
                        }
 
748
 
 
749
                        pid_t forwardPid = fork();
 
750
                        if(forwardPid == 0){
 
751
                                socketForwardXorg(client);
 
752
                                exit(0);
 
753
                        }
 
754
 
 
755
                        /* close the client here */
 
756
                        close(client);
 
757
                }
 
758
 
 
759
                exit(0);
 
760
 
 
761
        }else if (daemonPid == -1){ /* error */
 
762
                perror("Unable to fork Xorg socket redirect process.");
 
763
                return false;
 
764
        }
 
765
 
 
766
        return true;
 
767
}
 
768
 
 
769
/*
 
770
        init process inside the sandbox
 
771
        (RUNS AS ROOT!)
 
772
*/
 
773
static int initProcess(struct childDataStruct *data){
 
774
        int realUser    = getuid();
 
775
        int realGroup   = getgid();
 
776
        int res         = 1;
 
777
        time_t killTime = 0;
 
778
        struct timespec curTime;
 
779
        int fd;
 
780
 
 
781
        #ifdef SANDBOXDEBUG
 
782
                fprintf(stderr, "[SANDBOX:%d] initProcess(%p)\n", getpid(), data);
 
783
        #endif
 
784
 
 
785
        /* check root privileges */
 
786
        if (!root_checkPrivileges(realUser, realGroup)){
 
787
                fprintf(stderr, "Aborting because of incorrect privileges.\n");
 
788
                goto out;
 
789
        }
 
790
 
 
791
        /* check that we're the init process */
 
792
        if (getpid() != 1){
 
793
                fprintf(stderr, "You kernel does not support PID namespaces, exiting.\n");
 
794
                goto out;
 
795
        }
 
796
 
 
797
        /* setup the chroot mnt */
 
798
        if (!root_doSetupChrootMnt( data->writeDir )){
 
799
                fprintf(stderr, "Unable to setup chroot.\n");
 
800
                goto out;
 
801
        }
 
802
 
 
803
        /* do the actual chroot */
 
804
        if (!root_doChroot(data->cwDir)){
 
805
                fprintf(stderr, "Unable to chroot.\n");
 
806
                goto out;
 
807
        }
 
808
 
 
809
        /* bind xserver */
 
810
        if (!root_doSetupXServer(data->server)){
 
811
                fprintf(stderr, "Unable to setup Xorg socket redirection.\n");
 
812
                goto out;
 
813
        }
 
814
 
 
815
        /* close the file descriptor */
 
816
        close(data->server);
 
817
        data->server = (-1);
 
818
 
 
819
        /* start up loopback device */
 
820
        root_interfaceUp("lo");
 
821
 
 
822
        /* Drop rights */
 
823
        if (!root_doDropRights(realUser, realGroup)){
 
824
                fprintf(stderr, "Unable to drop root rights.\n");
 
825
                goto out;
 
826
        }
 
827
 
 
828
        /* close all file handles above 3 */
 
829
        if (!closeFileDescriptorsAbove(3)){
 
830
                fprintf(stderr, "Failed to close file descriptor.\n");
 
831
                goto out;
 
832
        }
 
833
 
 
834
        /* let the others in! */
 
835
        data->shmData->refCount = 2;
 
836
 
 
837
        /* wait until our refcount reaches zero */
 
838
        while (1){
 
839
                sleep(1);
 
840
 
 
841
                clock_gettime(CLOCK_MONOTONIC, &curTime);
 
842
 
 
843
                /* more processes left */
 
844
                if (data->shmData->refCount != 1){
 
845
                        killTime = 0;
 
846
                        continue;
 
847
                }
 
848
 
 
849
                /* give other processes 30 sec to terminate */
 
850
                if (killTime == 0) killTime = curTime.tv_sec + 30;
 
851
                
 
852
                /* processes running, but time remaining */
 
853
                if (!processAllchildsTerminated() && curTime.tv_sec < killTime)
 
854
                        continue;
 
855
 
 
856
                /* try to terminate, if failed: new process */
 
857
                if (!__sync_dec_if_one(&data->shmData->refCount)){
 
858
                        killTime = 0;
 
859
                        continue;
 
860
                }
 
861
 
 
862
                /* terminate */
 
863
                break;
 
864
        }
 
865
 
 
866
        /* open the way for some new init processes */
 
867
        data->shmData->initPid = 0;
 
868
 
 
869
        /* normal termination */
 
870
        res = 0;
 
871
 
 
872
out:
 
873
        if (data->cwDir)                        free(data->cwDir);
 
874
        if (data->writeDir)             free(data->writeDir);
 
875
        if (data->shmData)                      munmap(data->shmData, sizeof(struct shmDataStruct));
 
876
        if (data->server != -1)         close(data->server);
 
877
        return res;
 
878
}
 
879
 
 
880
/*
 
881
        Switches to the namespace
 
882
        (RUNS AS ROOT!)
 
883
*/
 
884
int main(int argc, char *argv[]){
 
885
        int realUser    = getuid();
 
886
        int realGroup   = getgid();
 
887
        int res         = 1;
 
888
 
 
889
        struct childDataStruct  data;
 
890
        pid_t sandboxPid;
 
891
        pid_t clientPid;
 
892
        int status;
 
893
        int fd;
 
894
 
 
895
        /* initialize struct */
 
896
        data.cwDir              = getcwd(NULL, 0);
 
897
        data.writeDir   = NULL;
 
898
        data.shmData    = NULL;
 
899
        data.server     = -1;
 
900
 
 
901
        /* ensure that we got at least the program filename */
 
902
        if (argc < 1 || !argv){
 
903
                fprintf(stderr, "Missing argument array, terminating.\n");
 
904
                goto out;
 
905
        }
 
906
 
 
907
        /* show usage information */
 
908
        if (argc < 2 || !strcmp(argv[1], "--help")){
 
909
                usage(argv[0]);
 
910
                res = 0;
 
911
                goto out;
 
912
        }
 
913
 
 
914
        /* TODO: can we execute the given file? if not, it makes no sense to continue! */
 
915
 
 
916
        /* check root privileges */
 
917
        if (!root_checkPrivileges(realUser, realGroup)){
 
918
                fprintf(stderr, "Aborting because of incorrect privileges.\n");
 
919
                goto out;
 
920
        }
 
921
 
 
922
        /* get write dir */
 
923
        data.writeDir = root_getWriteDir(realUser, realGroup);
 
924
        if (data.writeDir == NULL){
 
925
                fprintf(stderr, "[SANDBOX] WARNING: You will not have write access to anything except temp dirs!\n");
 
926
        }
 
927
 
 
928
        /* get shm data */
 
929
        data.shmData = root_getShm(realUser, realGroup, data.writeDir);
 
930
        if (data.shmData == NULL){
 
931
                fprintf(stderr, "Aborting because incorrect IPC.\n");
 
932
                goto out;
 
933
        }
 
934
 
 
935
        /* maybe several attempts required */
 
936
        while(1){
 
937
 
 
938
                /* no sandbox available, should we create it? */
 
939
                if (__sync_bool_compare_and_swap( &data.shmData->initPid, 0, (pid_t)-1 ) ){
 
940
                        fprintf(stderr, "[SANDBOX] Creating new sandbox.\n");
 
941
 
 
942
                        /* reset refcounter */
 
943
                        data.shmData->refCount = 0;
 
944
 
 
945
                        /* create socket which will be used for the xserver in the chroot */
 
946
                        data.server = socket(AF_UNIX, SOCK_STREAM, 0);
 
947
                        if (data.server == -1){
 
948
                                perror("Error while creating Xorg unix socket.");
 
949
                                goto err_unlock_pid;
 
950
                        }
 
951
 
 
952
                        /* switch to the new namespace */
 
953
                        sandboxPid = clone(     (int (*)(void *))initProcess, childStack + STACK_SIZE, 
 
954
                                                                CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWNET | SIGCHLD, &data);
 
955
                        if (sandboxPid == -1){
 
956
                                perror("Failed to clone into new namespace.");
 
957
                                goto err_unlock_pid;
 
958
                        }
 
959
 
 
960
                        /* refcounter == 0 means not yet started or crashed */
 
961
                        while( data.shmData->refCount == 0 ){
 
962
 
 
963
                                if(processChildTerminated(sandboxPid)){
 
964
                                        fprintf(stderr, "Failed to initialize cloned process, terminating.\n");
 
965
                                        goto err_unlock_pid;
 
966
                                }
 
967
 
 
968
                                usleep(100);
 
969
                        }
 
970
 
 
971
                        /* announce the PID */
 
972
                        data.shmData->initPid = sandboxPid;
 
973
 
 
974
                        /* start xserver daemon (will terminate as soon as the init process terminates) */
 
975
                        if (!startXserverDaemon(realUser, realGroup, data.server, sandboxPid)){
 
976
                                fprintf(stderr, "Xserver daemon not running properly, DISPLAY outwill will not work.\n");
 
977
                                goto out_decref;
 
978
                        }
 
979
 
 
980
                        /* close socket to xserver */
 
981
                        close(data.server);
 
982
                        data.server = -1;
 
983
 
 
984
                        fprintf(stderr, "[SANDBOX] Created sandbox with pid %d.\n", sandboxPid);
 
985
 
 
986
                /* there exists a sandbox, try to connect */
 
987
                }else if (data.shmData->initPid > 0 && __sync_inc_if_nonzero(&data.shmData->refCount) ){
 
988
                        sandboxPid = data.shmData->initPid;
 
989
 
 
990
                        fprintf(stderr, "[SANDBOX] Joining existing sandbox with pid %d.\n", sandboxPid);
 
991
 
 
992
                        /*  we have to ensure that this pid is really the right sandbox, */
 
993
                        if (    sandboxPid <= 0 || \
 
994
                                        processNonchildTerminated(sandboxPid) || \
 
995
                                        !root_checkIsSandboxedProcess(sandboxPid) ){
 
996
 
 
997
                                fprintf(stderr, "Sandbox structure contains invalid pid - hacking attempt?\n");
 
998
                                
 
999
                                /* unlock this and retry with new sandbox */
 
1000
                                __sync_bool_compare_and_swap(&data.shmData->initPid, sandboxPid, 0);
 
1001
                                continue;
 
1002
                        }
 
1003
 
 
1004
                        /* 
 
1005
                                NOTE: we do not explicitly check if its really the right sandbox - in case a virus is running
 
1006
                                inside the worst thing it can do is modify initPid, such that new processes are either spawned
 
1007
                                in a wrong or new sandbox. As the file system attributes are checked this isn't really a big deal
 
1008
                        */
 
1009
 
 
1010
                }else{
 
1011
                        /* wait and retry */
 
1012
                        usleep(100);
 
1013
                        continue;
 
1014
                }
 
1015
 
 
1016
                /* we got a parent process */
 
1017
                if(     !root_doSwitchNS(sandboxPid, "pid", 0) ||
 
1018
                                !root_doSwitchNS(sandboxPid, "mnt", 0) ||
 
1019
                                !root_doSwitchNS(sandboxPid, "ipc", CLONE_NEWIPC) ||
 
1020
                                !root_doSwitchNS(sandboxPid, "net", CLONE_NEWNET) ){
 
1021
                        fprintf(stderr, "Unable to join namespaces!\n");
 
1022
                        goto out_decref;
 
1023
                }
 
1024
 
 
1025
                if (!root_doChroot(data.cwDir)){
 
1026
                        fprintf(stderr, "Unable to chroot.\n");
 
1027
                        goto out_decref;
 
1028
                }
 
1029
 
 
1030
                /* Drop rights */
 
1031
                if (!root_doDropRights(realUser, realGroup)){
 
1032
                        fprintf(stderr, "Unable to drop root rights.\n");
 
1033
                        goto out;
 
1034
                }
 
1035
 
 
1036
                clientPid = fork();
 
1037
                if (clientPid == 0){
 
1038
 
 
1039
                        /* ensure that we're not leaking anything */
 
1040
                        if (data.cwDir)                 free(data.cwDir);
 
1041
                        if (data.writeDir)              free(data.writeDir);
 
1042
                        if (data.shmData)               munmap(data.shmData, sizeof(struct shmDataStruct));
 
1043
                        if (data.server != -1)  close(data.server);
 
1044
 
 
1045
                        /* close all file handles above 3 */
 
1046
                        if (!closeFileDescriptorsAbove(3)){
 
1047
                                fprintf(stderr, "Failed to close file descriptor.\n");
 
1048
                                exit(1);
 
1049
                        }
 
1050
 
 
1051
                        /* check that argv array is nullterminated */
 
1052
                        if (argv[argc] != NULL){
 
1053
                                fprintf(stderr, "Argument array is not nullterminated.\n");
 
1054
                                exit(1);
 
1055
                        }
 
1056
 
 
1057
                        /* set environment variable */
 
1058
                        setenv("debian_chroot", "SANDBOX", true);
 
1059
 
 
1060
                        /* otherwise execute the program */
 
1061
                        execvp(argv[1], &argv[1]);
 
1062
 
 
1063
                        /* in case something goes wrong */
 
1064
                        perror("Error while execvp.");
 
1065
                        exit(1);
 
1066
 
 
1067
                }else if (clientPid == -1){
 
1068
                        fprintf(stderr, "Failed to fork and run actual client process.\n");
 
1069
                        goto out_decref;
 
1070
                }
 
1071
 
 
1072
                /* close file descriptors */
 
1073
                /* for (fd = 0; fd < 3; fd++) close(fd); */
 
1074
 
 
1075
                waitpid(clientPid, &status, 0);
 
1076
 
 
1077
                res = (WIFEXITED(status) ? WEXITSTATUS(status) : 1);
 
1078
                goto out_decref;
 
1079
        }
 
1080
 
 
1081
        /* will never be reached */
 
1082
        return 1;
 
1083
 
 
1084
err_unlock_pid:
 
1085
        if (data.shmData) data.shmData->initPid = (pid_t)0;
 
1086
        goto out;
 
1087
 
 
1088
out_decref:
 
1089
        if (data.shmData) __sync_fetch_and_add(&data.shmData->refCount, -1);
 
1090
        /*goto out;*/
 
1091
 
 
1092
out:
 
1093
        if (data.cwDir)                 free(data.cwDir);
 
1094
        if (data.writeDir)              free(data.writeDir);
 
1095
        if (data.shmData)               munmap(data.shmData, sizeof(struct shmDataStruct));
 
1096
        if (data.server != -1)  close(data.server);
 
1097
        return res;
 
1098
}