Description:
Merge pull request #8 from nattee/master
add a new modified 64bit sandbox "box"
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
r201:9b414977da63 - - 1 file changed: 1760 inserted, 0 deleted
This diff has been collapsed as it changes many lines, (1760 lines changed) Show them Hide them | |||
@@ -0,0 +1,1760 | |||
|
1 | + /* | |
|
2 | + * A Simple Sandbox for Moe | |
|
3 | + * | |
|
4 | + * (c) 2001--2010 Martin Mares <mj@ucw.cz> | |
|
5 | + */ | |
|
6 | + | |
|
7 | + #define _LARGEFILE64_SOURCE | |
|
8 | + #define _GNU_SOURCE | |
|
9 | + | |
|
10 | + /* Generated automatically by ./configure, please don't touch manually. */ | |
|
11 | + #define CONFIG_BOX_KERNEL_AMD64 1 | |
|
12 | + #define CONFIG_BOX_USER_AMD64 1 | |
|
13 | + #define CONFIG_DIR "cf" | |
|
14 | + #define CONFIG_DIRECT_IO 1 | |
|
15 | + #define CONFIG_ISOLATE_BOX_DIR "/tmp/box" | |
|
16 | + #define CONFIG_ISOLATE_CGROUP_ROOT "/sys/fs/cgroup" | |
|
17 | + #define CONFIG_ISOLATE_FIRST_GID 60000 | |
|
18 | + #define CONFIG_ISOLATE_FIRST_UID 60000 | |
|
19 | + #define CONFIG_ISOLATE_NUM_BOXES 100 | |
|
20 | + #define CONFIG_LARGE_FILES 1 | |
|
21 | + #define CONFIG_LFS 1 | |
|
22 | + #define CONFIG_LINUX 1 | |
|
23 | + #define CONFIG_LOCAL 1 | |
|
24 | + #define CONFIG_UCW_PARTMAP_IS_MMAP 1 | |
|
25 | + #define CONFIG_UCW_PERL 1 | |
|
26 | + #define CONFIG_UCW_POOL_IS_MMAP 1 | |
|
27 | + #define CONFIG_UCW_RADIX_SORTER_BITS 10 | |
|
28 | + #define CONFIG_UCW_SHELL_UTILS 1 | |
|
29 | + #define CPU_64BIT_POINTERS 1 | |
|
30 | + #define CPU_ALLOW_UNALIGNED 1 | |
|
31 | + #define CPU_AMD64 1 | |
|
32 | + #define CPU_ARCH "default" | |
|
33 | + #define CPU_LITTLE_ENDIAN 1 | |
|
34 | + #define CPU_PAGE_SIZE 4096 | |
|
35 | + #define CPU_STRUCT_ALIGN 8 | |
|
36 | + #define CWARNS_OFF " -Wno-pointer-sign" | |
|
37 | + #define HAVE_ASCII_DOC "none" | |
|
38 | + #define INSTALL_BIN_DIR "bin" | |
|
39 | + #define INSTALL_CONFIG_DIR "cf" | |
|
40 | + #define INSTALL_DOC_DIR "share/doc" | |
|
41 | + #define INSTALL_INCLUDE_DIR "include" | |
|
42 | + #define INSTALL_LIB_DIR "lib" | |
|
43 | + #define INSTALL_LOG_DIR "log" | |
|
44 | + #define INSTALL_MAN_DIR "share/man" | |
|
45 | + #define INSTALL_PERL_DIR "lib/perl5" | |
|
46 | + #define INSTALL_PKGCONFIG_DIR "lib/pkgconfig" | |
|
47 | + #define INSTALL_PREFIX | |
|
48 | + #define INSTALL_RUN_DIR "run" | |
|
49 | + #define INSTALL_SBIN_DIR "sbin" | |
|
50 | + #define INSTALL_SHARE_DIR "share" | |
|
51 | + #define INSTALL_STATE_DIR "lib" | |
|
52 | + #define INSTALL_USR_PREFIX | |
|
53 | + #define INSTALL_VAR_PREFIX | |
|
54 | + #define SHERLOCK_VERSION "3.99.2" | |
|
55 | + #define SHERLOCK_VERSION_CODE 3099002 | |
|
56 | + #define SONAME_PREFIX "lib/" | |
|
57 | + #define UCW_VERSION "3.99.2" | |
|
58 | + #define UCW_VERSION_CODE 3099002 | |
|
59 | + | |
|
60 | + #include <errno.h> | |
|
61 | + #include <stdio.h> | |
|
62 | + #include <fcntl.h> | |
|
63 | + #include <stdlib.h> | |
|
64 | + #include <string.h> | |
|
65 | + #include <stdarg.h> | |
|
66 | + #include <stdint.h> | |
|
67 | + #include <unistd.h> | |
|
68 | + #include <getopt.h> | |
|
69 | + #include <time.h> | |
|
70 | + #include <sys/wait.h> | |
|
71 | + #include <sys/user.h> | |
|
72 | + #include <sys/time.h> | |
|
73 | + #include <sys/ptrace.h> | |
|
74 | + #include <sys/signal.h> | |
|
75 | + #include <sys/sysinfo.h> | |
|
76 | + #include <sys/resource.h> | |
|
77 | + #include <sys/utsname.h> | |
|
78 | + //#include <linux/ptrace.h> | |
|
79 | + | |
|
80 | + #if defined(CONFIG_BOX_KERNEL_AMD64) && !defined(CONFIG_BOX_USER_AMD64) | |
|
81 | + #include <asm/unistd_32.h> | |
|
82 | + #define NATIVE_NR_execve 59 /* 64-bit execve */ | |
|
83 | + #else | |
|
84 | + #include <asm/unistd.h> | |
|
85 | + #define NATIVE_NR_execve __NR_execve | |
|
86 | + #endif | |
|
87 | + | |
|
88 | + #define NONRET __attribute__((noreturn)) | |
|
89 | + #define UNUSED __attribute__((unused)) | |
|
90 | + #define ARRAY_SIZE(a) (int)(sizeof(a)/sizeof(a[0])) | |
|
91 | + | |
|
92 | + static int filter_syscalls; /* 0=off, 1=liberal, 2=totalitarian */ | |
|
93 | + static int timeout; /* milliseconds */ | |
|
94 | + static int wall_timeout; | |
|
95 | + static int extra_timeout; | |
|
96 | + static int pass_environ; | |
|
97 | + static int file_access; | |
|
98 | + static int verbose; | |
|
99 | + static int memory_limit; | |
|
100 | + static int stack_limit; | |
|
101 | + static char *redir_stdin, *redir_stdout, *redir_stderr; | |
|
102 | + static char *set_cwd; | |
|
103 | + | |
|
104 | + static pid_t box_pid; | |
|
105 | + static int is_ptraced; | |
|
106 | + static volatile int timer_tick; | |
|
107 | + static struct timeval start_time; | |
|
108 | + static int ticks_per_sec; | |
|
109 | + static int exec_seen; | |
|
110 | + static int partial_line; | |
|
111 | + | |
|
112 | + static int mem_peak_kb; | |
|
113 | + static int total_ms, wall_ms, sys_ms; | |
|
114 | + | |
|
115 | + static void die(char *msg, ...) NONRET; | |
|
116 | + static void sample_mem_peak(void); | |
|
117 | + | |
|
118 | + /*** Meta-files ***/ | |
|
119 | + | |
|
120 | + static FILE *metafile; | |
|
121 | + | |
|
122 | + static void | |
|
123 | + meta_open(const char *name) | |
|
124 | + { | |
|
125 | + if (!strcmp(name, "-")) | |
|
126 | + { | |
|
127 | + metafile = stdout; | |
|
128 | + return; | |
|
129 | + } | |
|
130 | + metafile = fopen(name, "w"); | |
|
131 | + if (!metafile) | |
|
132 | + die("Failed to open metafile '%s'",name); | |
|
133 | + } | |
|
134 | + | |
|
135 | + static void | |
|
136 | + meta_close(void) | |
|
137 | + { | |
|
138 | + if (metafile && metafile != stdout) | |
|
139 | + fclose(metafile); | |
|
140 | + } | |
|
141 | + | |
|
142 | + static void __attribute__((format(printf,1,2))) | |
|
143 | + meta_printf(const char *fmt, ...) | |
|
144 | + { | |
|
145 | + if (!metafile) | |
|
146 | + return; | |
|
147 | + | |
|
148 | + va_list args; | |
|
149 | + va_start(args, fmt); | |
|
150 | + vfprintf(metafile, fmt, args); | |
|
151 | + va_end(args); | |
|
152 | + } | |
|
153 | + | |
|
154 | + | |
|
155 | + static void print_running_stat(double wall_time, | |
|
156 | + double user_time, | |
|
157 | + double system_time, | |
|
158 | + int mem_usage) | |
|
159 | + { | |
|
160 | + //total is user | |
|
161 | + //wall is wall | |
|
162 | + // | |
|
163 | + fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dkbytes\n", | |
|
164 | + wall_time, user_time, system_time, mem_usage); | |
|
165 | + } | |
|
166 | + | |
|
167 | + static void | |
|
168 | + final_stats(struct rusage *rus) | |
|
169 | + { | |
|
170 | + struct timeval total, now, wall; | |
|
171 | + timeradd(&rus->ru_utime, &rus->ru_stime, &total); | |
|
172 | + total_ms = total.tv_sec*1000 + total.tv_usec/1000; | |
|
173 | + gettimeofday(&now, NULL); | |
|
174 | + timersub(&now, &start_time, &wall); | |
|
175 | + wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000; | |
|
176 | + sys_ms = rus->ru_stime.tv_sec * 1000 + rus->ru_stime.tv_usec / 1000; | |
|
177 | + | |
|
178 | + meta_printf("time:%d.%03d\n", total_ms/1000, total_ms%1000); | |
|
179 | + meta_printf("time-wall:%d.%03d\n", wall_ms/1000, wall_ms%1000); | |
|
180 | + meta_printf("mem:%llu\n", (unsigned long long) mem_peak_kb * 1024); | |
|
181 | + } | |
|
182 | + | |
|
183 | + /*** Messages and exits ***/ | |
|
184 | + | |
|
185 | + static void NONRET | |
|
186 | + box_exit(int rc) | |
|
187 | + { | |
|
188 | + if (box_pid > 0) | |
|
189 | + { | |
|
190 | + sample_mem_peak(); | |
|
191 | + if (is_ptraced) | |
|
192 | + ptrace(PTRACE_KILL, box_pid); | |
|
193 | + kill(-box_pid, SIGKILL); | |
|
194 | + kill(box_pid, SIGKILL); | |
|
195 | + meta_printf("killed:1\n"); | |
|
196 | + | |
|
197 | + struct rusage rus; | |
|
198 | + int p, stat; | |
|
199 | + do | |
|
200 | + p = wait4(box_pid, &stat, 0, &rus); | |
|
201 | + while (p < 0 && errno == EINTR); | |
|
202 | + if (p < 0) | |
|
203 | + fprintf(stderr, "UGH: Lost track of the process (%m)\n"); | |
|
204 | + else { | |
|
205 | + final_stats(&rus); | |
|
206 | + } | |
|
207 | + } | |
|
208 | + print_running_stat( | |
|
209 | + (double)wall_ms/1000, | |
|
210 | + (double)total_ms/1000, | |
|
211 | + (double)sys_ms/1000, | |
|
212 | + mem_peak_kb); | |
|
213 | + meta_close(); | |
|
214 | + exit(rc); | |
|
215 | + } | |
|
216 | + | |
|
217 | + static void | |
|
218 | + flush_line(void) | |
|
219 | + { | |
|
220 | + if (partial_line) | |
|
221 | + fputc('\n', stderr); | |
|
222 | + partial_line = 0; | |
|
223 | + } | |
|
224 | + | |
|
225 | + /* Report an error of the sandbox itself */ | |
|
226 | + static void NONRET __attribute__((format(printf,1,2))) | |
|
227 | + die(char *msg, ...) | |
|
228 | + { | |
|
229 | + va_list args; | |
|
230 | + va_start(args, msg); | |
|
231 | + flush_line(); | |
|
232 | + char buf[1024]; | |
|
233 | + vsnprintf(buf, sizeof(buf), msg, args); | |
|
234 | + meta_printf("status:XX\nmessage:%s\n", buf); | |
|
235 | + fputs(buf, stderr); | |
|
236 | + fputc('\n', stderr); | |
|
237 | + box_exit(2); | |
|
238 | + } | |
|
239 | + | |
|
240 | + /* Report an error of the program inside the sandbox */ | |
|
241 | + static void NONRET __attribute__((format(printf,1,2))) | |
|
242 | + err(char *msg, ...) | |
|
243 | + { | |
|
244 | + va_list args; | |
|
245 | + va_start(args, msg); | |
|
246 | + flush_line(); | |
|
247 | + if (msg[0] && msg[1] && msg[2] == ':' && msg[3] == ' ') | |
|
248 | + { | |
|
249 | + meta_printf("status:%c%c\n", msg[0], msg[1]); | |
|
250 | + msg += 4; | |
|
251 | + } | |
|
252 | + char buf[1024]; | |
|
253 | + vsnprintf(buf, sizeof(buf), msg, args); | |
|
254 | + meta_printf("message:%s\n", buf); | |
|
255 | + fputs(buf, stderr); | |
|
256 | + fputc('\n', stderr); | |
|
257 | + box_exit(1); | |
|
258 | + } | |
|
259 | + | |
|
260 | + /* Write a message, but only if in verbose mode */ | |
|
261 | + static void __attribute__((format(printf,1,2))) | |
|
262 | + msg(char *msg, ...) | |
|
263 | + { | |
|
264 | + va_list args; | |
|
265 | + va_start(args, msg); | |
|
266 | + if (verbose) | |
|
267 | + { | |
|
268 | + int len = strlen(msg); | |
|
269 | + if (len > 0) | |
|
270 | + partial_line = (msg[len-1] != '\n'); | |
|
271 | + vfprintf(stderr, msg, args); | |
|
272 | + fflush(stderr); | |
|
273 | + } | |
|
274 | + va_end(args); | |
|
275 | + } | |
|
276 | + | |
|
277 | + static void * | |
|
278 | + xmalloc(size_t size) | |
|
279 | + { | |
|
280 | + void *p = malloc(size); | |
|
281 | + if (!p) | |
|
282 | + die("Out of memory"); | |
|
283 | + return p; | |
|
284 | + } | |
|
285 | + | |
|
286 | + /*** Syscall rules ***/ | |
|
287 | + | |
|
288 | + static const char * const syscall_names[] = { | |
|
289 | + | |
|
290 | + /* Syscall table automatically generated by mk-syscall-table */ | |
|
291 | + | |
|
292 | + /* 0 */ [ __NR_read ] = "read", | |
|
293 | + /* 1 */ [ __NR_write ] = "write", | |
|
294 | + /* 2 */ [ __NR_open ] = "open", | |
|
295 | + /* 3 */ [ __NR_close ] = "close", | |
|
296 | + /* 4 */ [ __NR_stat ] = "stat", | |
|
297 | + /* 5 */ [ __NR_fstat ] = "fstat", | |
|
298 | + /* 6 */ [ __NR_lstat ] = "lstat", | |
|
299 | + /* 7 */ [ __NR_poll ] = "poll", | |
|
300 | + /* 8 */ [ __NR_lseek ] = "lseek", | |
|
301 | + /* 9 */ [ __NR_mmap ] = "mmap", | |
|
302 | + /* 10 */ [ __NR_mprotect ] = "mprotect", | |
|
303 | + /* 11 */ [ __NR_munmap ] = "munmap", | |
|
304 | + /* 12 */ [ __NR_brk ] = "brk", | |
|
305 | + /* 13 */ [ __NR_rt_sigaction ] = "rt_sigaction", | |
|
306 | + /* 14 */ [ __NR_rt_sigprocmask ] = "rt_sigprocmask", | |
|
307 | + /* 15 */ [ __NR_rt_sigreturn ] = "rt_sigreturn", | |
|
308 | + /* 16 */ [ __NR_ioctl ] = "ioctl", | |
|
309 | + /* 17 */ [ __NR_pread64 ] = "pread64", | |
|
310 | + /* 18 */ [ __NR_pwrite64 ] = "pwrite64", | |
|
311 | + /* 19 */ [ __NR_readv ] = "readv", | |
|
312 | + /* 20 */ [ __NR_writev ] = "writev", | |
|
313 | + /* 21 */ [ __NR_access ] = "access", | |
|
314 | + /* 22 */ [ __NR_pipe ] = "pipe", | |
|
315 | + /* 23 */ [ __NR_select ] = "select", | |
|
316 | + /* 24 */ [ __NR_sched_yield ] = "sched_yield", | |
|
317 | + /* 25 */ [ __NR_mremap ] = "mremap", | |
|
318 | + /* 26 */ [ __NR_msync ] = "msync", | |
|
319 | + /* 27 */ [ __NR_mincore ] = "mincore", | |
|
320 | + /* 28 */ [ __NR_madvise ] = "madvise", | |
|
321 | + /* 29 */ [ __NR_shmget ] = "shmget", | |
|
322 | + /* 30 */ [ __NR_shmat ] = "shmat", | |
|
323 | + /* 31 */ [ __NR_shmctl ] = "shmctl", | |
|
324 | + /* 32 */ [ __NR_dup ] = "dup", | |
|
325 | + /* 33 */ [ __NR_dup2 ] = "dup2", | |
|
326 | + /* 34 */ [ __NR_pause ] = "pause", | |
|
327 | + /* 35 */ [ __NR_nanosleep ] = "nanosleep", | |
|
328 | + /* 36 */ [ __NR_getitimer ] = "getitimer", | |
|
329 | + /* 37 */ [ __NR_alarm ] = "alarm", | |
|
330 | + /* 38 */ [ __NR_setitimer ] = "setitimer", | |
|
331 | + /* 39 */ [ __NR_getpid ] = "getpid", | |
|
332 | + /* 40 */ [ __NR_sendfile ] = "sendfile", | |
|
333 | + /* 41 */ [ __NR_socket ] = "socket", | |
|
334 | + /* 42 */ [ __NR_connect ] = "connect", | |
|
335 | + /* 43 */ [ __NR_accept ] = "accept", | |
|
336 | + /* 44 */ [ __NR_sendto ] = "sendto", | |
|
337 | + /* 45 */ [ __NR_recvfrom ] = "recvfrom", | |
|
338 | + /* 46 */ [ __NR_sendmsg ] = "sendmsg", | |
|
339 | + /* 47 */ [ __NR_recvmsg ] = "recvmsg", | |
|
340 | + /* 48 */ [ __NR_shutdown ] = "shutdown", | |
|
341 | + /* 49 */ [ __NR_bind ] = "bind", | |
|
342 | + /* 50 */ [ __NR_listen ] = "listen", | |
|
343 | + /* 51 */ [ __NR_getsockname ] = "getsockname", | |
|
344 | + /* 52 */ [ __NR_getpeername ] = "getpeername", | |
|
345 | + /* 53 */ [ __NR_socketpair ] = "socketpair", | |
|
346 | + /* 54 */ [ __NR_setsockopt ] = "setsockopt", | |
|
347 | + /* 55 */ [ __NR_getsockopt ] = "getsockopt", | |
|
348 | + /* 56 */ [ __NR_clone ] = "clone", | |
|
349 | + /* 57 */ [ __NR_fork ] = "fork", | |
|
350 | + /* 58 */ [ __NR_vfork ] = "vfork", | |
|
351 | + /* 59 */ [ __NR_execve ] = "execve", | |
|
352 | + /* 60 */ [ __NR_exit ] = "exit", | |
|
353 | + /* 61 */ [ __NR_wait4 ] = "wait4", | |
|
354 | + /* 62 */ [ __NR_kill ] = "kill", | |
|
355 | + /* 63 */ [ __NR_uname ] = "uname", | |
|
356 | + /* 64 */ [ __NR_semget ] = "semget", | |
|
357 | + /* 65 */ [ __NR_semop ] = "semop", | |
|
358 | + /* 66 */ [ __NR_semctl ] = "semctl", | |
|
359 | + /* 67 */ [ __NR_shmdt ] = "shmdt", | |
|
360 | + /* 68 */ [ __NR_msgget ] = "msgget", | |
|
361 | + /* 69 */ [ __NR_msgsnd ] = "msgsnd", | |
|
362 | + /* 70 */ [ __NR_msgrcv ] = "msgrcv", | |
|
363 | + /* 71 */ [ __NR_msgctl ] = "msgctl", | |
|
364 | + /* 72 */ [ __NR_fcntl ] = "fcntl", | |
|
365 | + /* 73 */ [ __NR_flock ] = "flock", | |
|
366 | + /* 74 */ [ __NR_fsync ] = "fsync", | |
|
367 | + /* 75 */ [ __NR_fdatasync ] = "fdatasync", | |
|
368 | + /* 76 */ [ __NR_truncate ] = "truncate", | |
|
369 | + /* 77 */ [ __NR_ftruncate ] = "ftruncate", | |
|
370 | + /* 78 */ [ __NR_getdents ] = "getdents", | |
|
371 | + /* 79 */ [ __NR_getcwd ] = "getcwd", | |
|
372 | + /* 80 */ [ __NR_chdir ] = "chdir", | |
|
373 | + /* 81 */ [ __NR_fchdir ] = "fchdir", | |
|
374 | + /* 82 */ [ __NR_rename ] = "rename", | |
|
375 | + /* 83 */ [ __NR_mkdir ] = "mkdir", | |
|
376 | + /* 84 */ [ __NR_rmdir ] = "rmdir", | |
|
377 | + /* 85 */ [ __NR_creat ] = "creat", | |
|
378 | + /* 86 */ [ __NR_link ] = "link", | |
|
379 | + /* 87 */ [ __NR_unlink ] = "unlink", | |
|
380 | + /* 88 */ [ __NR_symlink ] = "symlink", | |
|
381 | + /* 89 */ [ __NR_readlink ] = "readlink", | |
|
382 | + /* 90 */ [ __NR_chmod ] = "chmod", | |
|
383 | + /* 91 */ [ __NR_fchmod ] = "fchmod", | |
|
384 | + /* 92 */ [ __NR_chown ] = "chown", | |
|
385 | + /* 93 */ [ __NR_fchown ] = "fchown", | |
|
386 | + /* 94 */ [ __NR_lchown ] = "lchown", | |
|
387 | + /* 95 */ [ __NR_umask ] = "umask", | |
|
388 | + /* 96 */ [ __NR_gettimeofday ] = "gettimeofday", | |
|
389 | + /* 97 */ [ __NR_getrlimit ] = "getrlimit", | |
|
390 | + /* 98 */ [ __NR_getrusage ] = "getrusage", | |
|
391 | + /* 99 */ [ __NR_sysinfo ] = "sysinfo", | |
|
392 | + /* 100 */ [ __NR_times ] = "times", | |
|
393 | + /* 101 */ [ __NR_ptrace ] = "ptrace", | |
|
394 | + /* 102 */ [ __NR_getuid ] = "getuid", | |
|
395 | + /* 103 */ [ __NR_syslog ] = "syslog", | |
|
396 | + /* 104 */ [ __NR_getgid ] = "getgid", | |
|
397 | + /* 105 */ [ __NR_setuid ] = "setuid", | |
|
398 | + /* 106 */ [ __NR_setgid ] = "setgid", | |
|
399 | + /* 107 */ [ __NR_geteuid ] = "geteuid", | |
|
400 | + /* 108 */ [ __NR_getegid ] = "getegid", | |
|
401 | + /* 109 */ [ __NR_setpgid ] = "setpgid", | |
|
402 | + /* 110 */ [ __NR_getppid ] = "getppid", | |
|
403 | + /* 111 */ [ __NR_getpgrp ] = "getpgrp", | |
|
404 | + /* 112 */ [ __NR_setsid ] = "setsid", | |
|
405 | + /* 113 */ [ __NR_setreuid ] = "setreuid", | |
|
406 | + /* 114 */ [ __NR_setregid ] = "setregid", | |
|
407 | + /* 115 */ [ __NR_getgroups ] = "getgroups", | |
|
408 | + /* 116 */ [ __NR_setgroups ] = "setgroups", | |
|
409 | + /* 117 */ [ __NR_setresuid ] = "setresuid", | |
|
410 | + /* 118 */ [ __NR_getresuid ] = "getresuid", | |
|
411 | + /* 119 */ [ __NR_setresgid ] = "setresgid", | |
|
412 | + /* 120 */ [ __NR_getresgid ] = "getresgid", | |
|
413 | + /* 121 */ [ __NR_getpgid ] = "getpgid", | |
|
414 | + /* 122 */ [ __NR_setfsuid ] = "setfsuid", | |
|
415 | + /* 123 */ [ __NR_setfsgid ] = "setfsgid", | |
|
416 | + /* 124 */ [ __NR_getsid ] = "getsid", | |
|
417 | + /* 125 */ [ __NR_capget ] = "capget", | |
|
418 | + /* 126 */ [ __NR_capset ] = "capset", | |
|
419 | + /* 127 */ [ __NR_rt_sigpending ] = "rt_sigpending", | |
|
420 | + /* 128 */ [ __NR_rt_sigtimedwait ] = "rt_sigtimedwait", | |
|
421 | + /* 129 */ [ __NR_rt_sigqueueinfo ] = "rt_sigqueueinfo", | |
|
422 | + /* 130 */ [ __NR_rt_sigsuspend ] = "rt_sigsuspend", | |
|
423 | + /* 131 */ [ __NR_sigaltstack ] = "sigaltstack", | |
|
424 | + /* 132 */ [ __NR_utime ] = "utime", | |
|
425 | + /* 133 */ [ __NR_mknod ] = "mknod", | |
|
426 | + /* 134 */ [ __NR_uselib ] = "uselib", | |
|
427 | + /* 135 */ [ __NR_personality ] = "personality", | |
|
428 | + /* 136 */ [ __NR_ustat ] = "ustat", | |
|
429 | + /* 137 */ [ __NR_statfs ] = "statfs", | |
|
430 | + /* 138 */ [ __NR_fstatfs ] = "fstatfs", | |
|
431 | + /* 139 */ [ __NR_sysfs ] = "sysfs", | |
|
432 | + /* 140 */ [ __NR_getpriority ] = "getpriority", | |
|
433 | + /* 141 */ [ __NR_setpriority ] = "setpriority", | |
|
434 | + /* 142 */ [ __NR_sched_setparam ] = "sched_setparam", | |
|
435 | + /* 143 */ [ __NR_sched_getparam ] = "sched_getparam", | |
|
436 | + /* 144 */ [ __NR_sched_setscheduler ] = "sched_setscheduler", | |
|
437 | + /* 145 */ [ __NR_sched_getscheduler ] = "sched_getscheduler", | |
|
438 | + /* 146 */ [ __NR_sched_get_priority_max ] = "sched_get_priority_max", | |
|
439 | + /* 147 */ [ __NR_sched_get_priority_min ] = "sched_get_priority_min", | |
|
440 | + /* 148 */ [ __NR_sched_rr_get_interval ] = "sched_rr_get_interval", | |
|
441 | + /* 149 */ [ __NR_mlock ] = "mlock", | |
|
442 | + /* 150 */ [ __NR_munlock ] = "munlock", | |
|
443 | + /* 151 */ [ __NR_mlockall ] = "mlockall", | |
|
444 | + /* 152 */ [ __NR_munlockall ] = "munlockall", | |
|
445 | + /* 153 */ [ __NR_vhangup ] = "vhangup", | |
|
446 | + /* 154 */ [ __NR_modify_ldt ] = "modify_ldt", | |
|
447 | + /* 155 */ [ __NR_pivot_root ] = "pivot_root", | |
|
448 | + /* 156 */ [ __NR__sysctl ] = "_sysctl", | |
|
449 | + /* 157 */ [ __NR_prctl ] = "prctl", | |
|
450 | + /* 158 */ [ __NR_arch_prctl ] = "arch_prctl", | |
|
451 | + /* 159 */ [ __NR_adjtimex ] = "adjtimex", | |
|
452 | + /* 160 */ [ __NR_setrlimit ] = "setrlimit", | |
|
453 | + /* 161 */ [ __NR_chroot ] = "chroot", | |
|
454 | + /* 162 */ [ __NR_sync ] = "sync", | |
|
455 | + /* 163 */ [ __NR_acct ] = "acct", | |
|
456 | + /* 164 */ [ __NR_settimeofday ] = "settimeofday", | |
|
457 | + /* 165 */ [ __NR_mount ] = "mount", | |
|
458 | + /* 166 */ [ __NR_umount2 ] = "umount2", | |
|
459 | + /* 167 */ [ __NR_swapon ] = "swapon", | |
|
460 | + /* 168 */ [ __NR_swapoff ] = "swapoff", | |
|
461 | + /* 169 */ [ __NR_reboot ] = "reboot", | |
|
462 | + /* 170 */ [ __NR_sethostname ] = "sethostname", | |
|
463 | + /* 171 */ [ __NR_setdomainname ] = "setdomainname", | |
|
464 | + /* 172 */ [ __NR_iopl ] = "iopl", | |
|
465 | + /* 173 */ [ __NR_ioperm ] = "ioperm", | |
|
466 | + /* 174 */ [ __NR_create_module ] = "create_module", | |
|
467 | + /* 175 */ [ __NR_init_module ] = "init_module", | |
|
468 | + /* 176 */ [ __NR_delete_module ] = "delete_module", | |
|
469 | + /* 177 */ [ __NR_get_kernel_syms ] = "get_kernel_syms", | |
|
470 | + /* 178 */ [ __NR_query_module ] = "query_module", | |
|
471 | + /* 179 */ [ __NR_quotactl ] = "quotactl", | |
|
472 | + /* 180 */ [ __NR_nfsservctl ] = "nfsservctl", | |
|
473 | + /* 181 */ [ __NR_getpmsg ] = "getpmsg", | |
|
474 | + /* 182 */ [ __NR_putpmsg ] = "putpmsg", | |
|
475 | + /* 183 */ [ __NR_afs_syscall ] = "afs_syscall", | |
|
476 | + /* 184 */ [ __NR_tuxcall ] = "tuxcall", | |
|
477 | + /* 185 */ [ __NR_security ] = "security", | |
|
478 | + /* 186 */ [ __NR_gettid ] = "gettid", | |
|
479 | + /* 187 */ [ __NR_readahead ] = "readahead", | |
|
480 | + /* 188 */ [ __NR_setxattr ] = "setxattr", | |
|
481 | + /* 189 */ [ __NR_lsetxattr ] = "lsetxattr", | |
|
482 | + /* 190 */ [ __NR_fsetxattr ] = "fsetxattr", | |
|
483 | + /* 191 */ [ __NR_getxattr ] = "getxattr", | |
|
484 | + /* 192 */ [ __NR_lgetxattr ] = "lgetxattr", | |
|
485 | + /* 193 */ [ __NR_fgetxattr ] = "fgetxattr", | |
|
486 | + /* 194 */ [ __NR_listxattr ] = "listxattr", | |
|
487 | + /* 195 */ [ __NR_llistxattr ] = "llistxattr", | |
|
488 | + /* 196 */ [ __NR_flistxattr ] = "flistxattr", | |
|
489 | + /* 197 */ [ __NR_removexattr ] = "removexattr", | |
|
490 | + /* 198 */ [ __NR_lremovexattr ] = "lremovexattr", | |
|
491 | + /* 199 */ [ __NR_fremovexattr ] = "fremovexattr", | |
|
492 | + /* 200 */ [ __NR_tkill ] = "tkill", | |
|
493 | + /* 201 */ [ __NR_time ] = "time", | |
|
494 | + /* 202 */ [ __NR_futex ] = "futex", | |
|
495 | + /* 203 */ [ __NR_sched_setaffinity ] = "sched_setaffinity", | |
|
496 | + /* 204 */ [ __NR_sched_getaffinity ] = "sched_getaffinity", | |
|
497 | + /* 205 */ [ __NR_set_thread_area ] = "set_thread_area", | |
|
498 | + /* 206 */ [ __NR_io_setup ] = "io_setup", | |
|
499 | + /* 207 */ [ __NR_io_destroy ] = "io_destroy", | |
|
500 | + /* 208 */ [ __NR_io_getevents ] = "io_getevents", | |
|
501 | + /* 209 */ [ __NR_io_submit ] = "io_submit", | |
|
502 | + /* 210 */ [ __NR_io_cancel ] = "io_cancel", | |
|
503 | + /* 211 */ [ __NR_get_thread_area ] = "get_thread_area", | |
|
504 | + /* 212 */ [ __NR_lookup_dcookie ] = "lookup_dcookie", | |
|
505 | + /* 213 */ [ __NR_epoll_create ] = "epoll_create", | |
|
506 | + /* 214 */ [ __NR_epoll_ctl_old ] = "epoll_ctl_old", | |
|
507 | + /* 215 */ [ __NR_epoll_wait_old ] = "epoll_wait_old", | |
|
508 | + /* 216 */ [ __NR_remap_file_pages ] = "remap_file_pages", | |
|
509 | + /* 217 */ [ __NR_getdents64 ] = "getdents64", | |
|
510 | + /* 218 */ [ __NR_set_tid_address ] = "set_tid_address", | |
|
511 | + /* 219 */ [ __NR_restart_syscall ] = "restart_syscall", | |
|
512 | + /* 220 */ [ __NR_semtimedop ] = "semtimedop", | |
|
513 | + /* 221 */ [ __NR_fadvise64 ] = "fadvise64", | |
|
514 | + /* 222 */ [ __NR_timer_create ] = "timer_create", | |
|
515 | + /* 223 */ [ __NR_timer_settime ] = "timer_settime", | |
|
516 | + /* 224 */ [ __NR_timer_gettime ] = "timer_gettime", | |
|
517 | + /* 225 */ [ __NR_timer_getoverrun ] = "timer_getoverrun", | |
|
518 | + /* 226 */ [ __NR_timer_delete ] = "timer_delete", | |
|
519 | + /* 227 */ [ __NR_clock_settime ] = "clock_settime", | |
|
520 | + /* 228 */ [ __NR_clock_gettime ] = "clock_gettime", | |
|
521 | + /* 229 */ [ __NR_clock_getres ] = "clock_getres", | |
|
522 | + /* 230 */ [ __NR_clock_nanosleep ] = "clock_nanosleep", | |
|
523 | + /* 231 */ [ __NR_exit_group ] = "exit_group", | |
|
524 | + /* 232 */ [ __NR_epoll_wait ] = "epoll_wait", | |
|
525 | + /* 233 */ [ __NR_epoll_ctl ] = "epoll_ctl", | |
|
526 | + /* 234 */ [ __NR_tgkill ] = "tgkill", | |
|
527 | + /* 235 */ [ __NR_utimes ] = "utimes", | |
|
528 | + /* 236 */ [ __NR_vserver ] = "vserver", | |
|
529 | + /* 237 */ [ __NR_mbind ] = "mbind", | |
|
530 | + /* 238 */ [ __NR_set_mempolicy ] = "set_mempolicy", | |
|
531 | + /* 239 */ [ __NR_get_mempolicy ] = "get_mempolicy", | |
|
532 | + /* 240 */ [ __NR_mq_open ] = "mq_open", | |
|
533 | + /* 241 */ [ __NR_mq_unlink ] = "mq_unlink", | |
|
534 | + /* 242 */ [ __NR_mq_timedsend ] = "mq_timedsend", | |
|
535 | + /* 243 */ [ __NR_mq_timedreceive ] = "mq_timedreceive", | |
|
536 | + /* 244 */ [ __NR_mq_notify ] = "mq_notify", | |
|
537 | + /* 245 */ [ __NR_mq_getsetattr ] = "mq_getsetattr", | |
|
538 | + /* 246 */ [ __NR_kexec_load ] = "kexec_load", | |
|
539 | + /* 247 */ [ __NR_waitid ] = "waitid", | |
|
540 | + /* 248 */ [ __NR_add_key ] = "add_key", | |
|
541 | + /* 249 */ [ __NR_request_key ] = "request_key", | |
|
542 | + /* 250 */ [ __NR_keyctl ] = "keyctl", | |
|
543 | + /* 251 */ [ __NR_ioprio_set ] = "ioprio_set", | |
|
544 | + /* 252 */ [ __NR_ioprio_get ] = "ioprio_get", | |
|
545 | + /* 253 */ [ __NR_inotify_init ] = "inotify_init", | |
|
546 | + /* 254 */ [ __NR_inotify_add_watch ] = "inotify_add_watch", | |
|
547 | + /* 255 */ [ __NR_inotify_rm_watch ] = "inotify_rm_watch", | |
|
548 | + /* 256 */ [ __NR_migrate_pages ] = "migrate_pages", | |
|
549 | + /* 257 */ [ __NR_openat ] = "openat", | |
|
550 | + /* 258 */ [ __NR_mkdirat ] = "mkdirat", | |
|
551 | + /* 259 */ [ __NR_mknodat ] = "mknodat", | |
|
552 | + /* 260 */ [ __NR_fchownat ] = "fchownat", | |
|
553 | + /* 261 */ [ __NR_futimesat ] = "futimesat", | |
|
554 | + /* 262 */ [ __NR_newfstatat ] = "newfstatat", | |
|
555 | + /* 263 */ [ __NR_unlinkat ] = "unlinkat", | |
|
556 | + /* 264 */ [ __NR_renameat ] = "renameat", | |
|
557 | + /* 265 */ [ __NR_linkat ] = "linkat", | |
|
558 | + /* 266 */ [ __NR_symlinkat ] = "symlinkat", | |
|
559 | + /* 267 */ [ __NR_readlinkat ] = "readlinkat", | |
|
560 | + /* 268 */ [ __NR_fchmodat ] = "fchmodat", | |
|
561 | + /* 269 */ [ __NR_faccessat ] = "faccessat", | |
|
562 | + /* 270 */ [ __NR_pselect6 ] = "pselect6", | |
|
563 | + /* 271 */ [ __NR_ppoll ] = "ppoll", | |
|
564 | + /* 272 */ [ __NR_unshare ] = "unshare", | |
|
565 | + /* 273 */ [ __NR_set_robust_list ] = "set_robust_list", | |
|
566 | + /* 274 */ [ __NR_get_robust_list ] = "get_robust_list", | |
|
567 | + /* 275 */ [ __NR_splice ] = "splice", | |
|
568 | + /* 276 */ [ __NR_tee ] = "tee", | |
|
569 | + /* 277 */ [ __NR_sync_file_range ] = "sync_file_range", | |
|
570 | + /* 278 */ [ __NR_vmsplice ] = "vmsplice", | |
|
571 | + /* 279 */ [ __NR_move_pages ] = "move_pages", | |
|
572 | + /* 280 */ [ __NR_utimensat ] = "utimensat", | |
|
573 | + /* 281 */ [ __NR_epoll_pwait ] = "epoll_pwait", | |
|
574 | + /* 282 */ [ __NR_signalfd ] = "signalfd", | |
|
575 | + /* 283 */ [ __NR_timerfd_create ] = "timerfd_create", | |
|
576 | + /* 284 */ [ __NR_eventfd ] = "eventfd", | |
|
577 | + /* 285 */ [ __NR_fallocate ] = "fallocate", | |
|
578 | + /* 286 */ [ __NR_timerfd_settime ] = "timerfd_settime", | |
|
579 | + /* 287 */ [ __NR_timerfd_gettime ] = "timerfd_gettime", | |
|
580 | + /* 288 */ [ __NR_accept4 ] = "accept4", | |
|
581 | + /* 289 */ [ __NR_signalfd4 ] = "signalfd4", | |
|
582 | + /* 290 */ [ __NR_eventfd2 ] = "eventfd2", | |
|
583 | + /* 291 */ [ __NR_epoll_create1 ] = "epoll_create1", | |
|
584 | + /* 292 */ [ __NR_dup3 ] = "dup3", | |
|
585 | + /* 293 */ [ __NR_pipe2 ] = "pipe2", | |
|
586 | + /* 294 */ [ __NR_inotify_init1 ] = "inotify_init1", | |
|
587 | + /* 295 */ [ __NR_preadv ] = "preadv", | |
|
588 | + /* 296 */ [ __NR_pwritev ] = "pwritev", | |
|
589 | + /* 297 */ [ __NR_rt_tgsigqueueinfo ] = "rt_tgsigqueueinfo", | |
|
590 | + /* 298 */ [ __NR_perf_event_open ] = "perf_event_open", | |
|
591 | + /* 299 */ [ __NR_recvmmsg ] = "recvmmsg", | |
|
592 | + /* 300 */ [ __NR_fanotify_init ] = "fanotify_init", | |
|
593 | + /* 301 */ [ __NR_fanotify_mark ] = "fanotify_mark", | |
|
594 | + /* 302 */ [ __NR_prlimit64 ] = "prlimit64", | |
|
595 | + /* 303 */ [ __NR_name_to_handle_at ] = "name_to_handle_at", | |
|
596 | + /* 304 */ [ __NR_open_by_handle_at ] = "open_by_handle_at", | |
|
597 | + /* 305 */ [ __NR_clock_adjtime ] = "clock_adjtime", | |
|
598 | + /* 306 */ [ __NR_syncfs ] = "syncfs", | |
|
599 | + /* 307 */ [ __NR_sendmmsg ] = "sendmmsg", | |
|
600 | + /* 308 */ [ __NR_setns ] = "setns", | |
|
601 | + /* 309 */ [ __NR_getcpu ] = "getcpu", | |
|
602 | + /* 310 */ [ __NR_process_vm_readv ] = "process_vm_readv", | |
|
603 | + /* 311 */ [ __NR_process_vm_writev ] = "process_vm_writev", | |
|
604 | + /* 312 */ [ __NR_kcmp ] = "kcmp", | |
|
605 | + /* 313 */ [ __NR_finit_module ] = "finit_module", | |
|
606 | + }; | |
|
607 | + #define NUM_SYSCALLS ARRAY_SIZE(syscall_names) | |
|
608 | + #define NUM_ACTIONS (NUM_SYSCALLS+64) | |
|
609 | + | |
|
610 | + enum action { | |
|
611 | + A_DEFAULT, // Use the default action | |
|
612 | + A_NO, // Always forbid | |
|
613 | + A_YES, // Always permit | |
|
614 | + A_FILENAME, // Permit if arg1 is a known filename | |
|
615 | + A_ACTION_MASK = 15, | |
|
616 | + A_NO_RETVAL = 32, // Does not return a value | |
|
617 | + A_SAMPLE_MEM = 64, // Sample memory usage before the syscall | |
|
618 | + A_LIBERAL = 128, // Valid only in liberal mode | |
|
619 | + // Must fit in a unsigned char | |
|
620 | + }; | |
|
621 | + | |
|
622 | + static unsigned char syscall_action[NUM_ACTIONS] = { | |
|
623 | + #define S(x) [__NR_##x] | |
|
624 | + | |
|
625 | + // Syscalls permitted for specific file names | |
|
626 | + S(open) = A_FILENAME, | |
|
627 | + S(creat) = A_FILENAME, | |
|
628 | + S(unlink) = A_FILENAME, | |
|
629 | + S(access) = A_FILENAME, | |
|
630 | + S(truncate) = A_FILENAME, | |
|
631 | + S(stat) = A_FILENAME, | |
|
632 | + S(lstat) = A_FILENAME, | |
|
633 | + S(readlink) = A_FILENAME, | |
|
634 | + #ifndef CONFIG_BOX_USER_AMD64 | |
|
635 | + S(oldstat) = A_FILENAME, | |
|
636 | + S(oldlstat) = A_FILENAME, | |
|
637 | + S(truncate64) = A_FILENAME, | |
|
638 | + S(stat64) = A_FILENAME, | |
|
639 | + S(lstat64) = A_FILENAME, | |
|
640 | + #endif | |
|
641 | + | |
|
642 | + // Syscalls permitted always | |
|
643 | + S(exit) = A_YES | A_SAMPLE_MEM, | |
|
644 | + S(read) = A_YES, | |
|
645 | + S(write) = A_YES, | |
|
646 | + S(close) = A_YES, | |
|
647 | + S(lseek) = A_YES, | |
|
648 | + S(getpid) = A_YES, | |
|
649 | + S(getuid) = A_YES, | |
|
650 | + S(dup) = A_YES, | |
|
651 | + S(brk) = A_YES, | |
|
652 | + S(getgid) = A_YES, | |
|
653 | + S(geteuid) = A_YES, | |
|
654 | + S(getegid) = A_YES, | |
|
655 | + S(dup2) = A_YES, | |
|
656 | + S(ftruncate) = A_YES, | |
|
657 | + S(fstat) = A_YES, | |
|
658 | + S(personality) = A_YES, | |
|
659 | + S(readv) = A_YES, | |
|
660 | + S(writev) = A_YES, | |
|
661 | + S(getresuid) = A_YES, | |
|
662 | + #ifdef __NR_pread64 | |
|
663 | + S(pread64) = A_YES, | |
|
664 | + S(pwrite64) = A_YES, | |
|
665 | + #else | |
|
666 | + S(pread) = A_YES, | |
|
667 | + S(pwrite) = A_YES, | |
|
668 | + #endif | |
|
669 | + S(fcntl) = A_YES, | |
|
670 | + S(mmap) = A_YES, | |
|
671 | + S(munmap) = A_YES, | |
|
672 | + S(ioctl) = A_YES, | |
|
673 | + S(uname) = A_YES, | |
|
674 | + S(gettid) = A_YES, | |
|
675 | + S(set_thread_area) = A_YES, | |
|
676 | + S(get_thread_area) = A_YES, | |
|
677 | + S(set_tid_address) = A_YES, | |
|
678 | + S(exit_group) = A_YES | A_SAMPLE_MEM, | |
|
679 | + #ifdef CONFIG_BOX_USER_AMD64 | |
|
680 | + S(arch_prctl) = A_YES, | |
|
681 | + #else | |
|
682 | + S(oldfstat) = A_YES, | |
|
683 | + S(ftruncate64) = A_YES, | |
|
684 | + S(_llseek) = A_YES, | |
|
685 | + S(fstat64) = A_YES, | |
|
686 | + S(fcntl64) = A_YES, | |
|
687 | + S(mmap2) = A_YES, | |
|
688 | + #endif | |
|
689 | + | |
|
690 | + // Syscalls permitted only in liberal mode | |
|
691 | + S(time) = A_YES | A_LIBERAL, | |
|
692 | + S(alarm) = A_YES | A_LIBERAL, | |
|
693 | + S(pause) = A_YES | A_LIBERAL, | |
|
694 | + S(fchmod) = A_YES | A_LIBERAL, | |
|
695 | + S(getrlimit) = A_YES | A_LIBERAL, | |
|
696 | + S(getrusage) = A_YES | A_LIBERAL, | |
|
697 | + S(gettimeofday) = A_YES | A_LIBERAL, | |
|
698 | + S(select) = A_YES | A_LIBERAL, | |
|
699 | + S(setitimer) = A_YES | A_LIBERAL, | |
|
700 | + S(getitimer) = A_YES | A_LIBERAL, | |
|
701 | + S(mprotect) = A_YES | A_LIBERAL, | |
|
702 | + S(getdents) = A_YES | A_LIBERAL, | |
|
703 | + S(getdents64) = A_YES | A_LIBERAL, | |
|
704 | + S(fdatasync) = A_YES | A_LIBERAL, | |
|
705 | + S(mremap) = A_YES | A_LIBERAL, | |
|
706 | + S(poll) = A_YES | A_LIBERAL, | |
|
707 | + S(getcwd) = A_YES | A_LIBERAL, | |
|
708 | + S(nanosleep) = A_YES | A_LIBERAL, | |
|
709 | + S(rt_sigreturn) = A_YES | A_LIBERAL | A_NO_RETVAL, | |
|
710 | + S(rt_sigaction) = A_YES | A_LIBERAL, | |
|
711 | + S(rt_sigprocmask) = A_YES | A_LIBERAL, | |
|
712 | + S(rt_sigpending) = A_YES | A_LIBERAL, | |
|
713 | + S(rt_sigtimedwait) = A_YES | A_LIBERAL, | |
|
714 | + S(rt_sigqueueinfo) = A_YES | A_LIBERAL, | |
|
715 | + S(rt_sigsuspend) = A_YES | A_LIBERAL, | |
|
716 | + S(_sysctl) = A_YES | A_LIBERAL, | |
|
717 | + #ifndef CONFIG_BOX_USER_AMD64 | |
|
718 | + S(sigaction) = A_YES | A_LIBERAL, | |
|
719 | + S(sgetmask) = A_YES | A_LIBERAL, | |
|
720 | + S(ssetmask) = A_YES | A_LIBERAL, | |
|
721 | + S(sigsuspend) = A_YES | A_LIBERAL, | |
|
722 | + S(sigpending) = A_YES | A_LIBERAL, | |
|
723 | + S(sigreturn) = A_YES | A_LIBERAL | A_NO_RETVAL, | |
|
724 | + S(sigprocmask) = A_YES | A_LIBERAL, | |
|
725 | + S(ugetrlimit) = A_YES | A_LIBERAL, | |
|
726 | + S(readdir) = A_YES | A_LIBERAL, | |
|
727 | + S(signal) = A_YES | A_LIBERAL, | |
|
728 | + S(_newselect) = A_YES | A_LIBERAL, | |
|
729 | + #endif | |
|
730 | + | |
|
731 | + #undef S | |
|
732 | + }; | |
|
733 | + | |
|
734 | + static const char * | |
|
735 | + syscall_name(unsigned int id, char *buf) | |
|
736 | + { | |
|
737 | + if (id < NUM_SYSCALLS && syscall_names[id]) | |
|
738 | + return syscall_names[id]; | |
|
739 | + else | |
|
740 | + { | |
|
741 | + sprintf(buf, "#%d", id); | |
|
742 | + return buf; | |
|
743 | + } | |
|
744 | + } | |
|
745 | + | |
|
746 | + static int | |
|
747 | + syscall_by_name(char *name) | |
|
748 | + { | |
|
749 | + for (unsigned int i=0; i<NUM_SYSCALLS; i++) | |
|
750 | + if (syscall_names[i] && !strcmp(syscall_names[i], name)) | |
|
751 | + return i; | |
|
752 | + if (name[0] == '#') | |
|
753 | + name++; | |
|
754 | + if (!*name) | |
|
755 | + return -1; | |
|
756 | + char *ep; | |
|
757 | + unsigned long l = strtoul(name, &ep, 0); | |
|
758 | + if (*ep) | |
|
759 | + return -1; | |
|
760 | + if (l >= NUM_ACTIONS) | |
|
761 | + return NUM_ACTIONS; | |
|
762 | + return l; | |
|
763 | + } | |
|
764 | + | |
|
765 | + static int | |
|
766 | + set_syscall_action(char *a) | |
|
767 | + { | |
|
768 | + char *sep = strchr(a, '='); | |
|
769 | + enum action act = A_YES; | |
|
770 | + if (sep) | |
|
771 | + { | |
|
772 | + *sep++ = 0; | |
|
773 | + if (!strcmp(sep, "yes")) | |
|
774 | + act = A_YES; | |
|
775 | + else if (!strcmp(sep, "no")) | |
|
776 | + act = A_NO; | |
|
777 | + else if (!strcmp(sep, "file")) | |
|
778 | + act = A_FILENAME; | |
|
779 | + else | |
|
780 | + return 0; | |
|
781 | + } | |
|
782 | + | |
|
783 | + int sys = syscall_by_name(a); | |
|
784 | + if (sys < 0) | |
|
785 | + die("Unknown syscall `%s'", a); | |
|
786 | + if (sys >= NUM_ACTIONS) | |
|
787 | + die("Syscall `%s' out of range", a); | |
|
788 | + syscall_action[sys] = act; | |
|
789 | + return 1; | |
|
790 | + } | |
|
791 | + | |
|
792 | + /*** Path rules ***/ | |
|
793 | + | |
|
794 | + struct path_rule { | |
|
795 | + char *path; | |
|
796 | + enum action action; | |
|
797 | + struct path_rule *next; | |
|
798 | + }; | |
|
799 | + | |
|
800 | + static struct path_rule default_path_rules[] = { | |
|
801 | + { "/etc/", A_YES }, | |
|
802 | + { "/lib/", A_YES }, | |
|
803 | + { "/usr/lib/", A_YES }, | |
|
804 | + { "/opt/lib/", A_YES }, | |
|
805 | + { "/usr/share/zoneinfo/", A_YES }, | |
|
806 | + { "/usr/share/locale/", A_YES }, | |
|
807 | + { "/dev/null", A_YES }, | |
|
808 | + { "/dev/zero", A_YES }, | |
|
809 | + { "/proc/meminfo", A_YES }, | |
|
810 | + { "/proc/self/stat", A_YES }, | |
|
811 | + { "/proc/self/exe", A_YES }, // Needed by FPC 2.0.x runtime | |
|
812 | + { "/proc/self/maps", A_YES }, // Needed by glibc when it reports arena corruption | |
|
813 | + }; | |
|
814 | + | |
|
815 | + static struct path_rule *user_path_rules; | |
|
816 | + static struct path_rule **last_path_rule = &user_path_rules; | |
|
817 | + | |
|
818 | + static int | |
|
819 | + set_path_action(char *a) | |
|
820 | + { | |
|
821 | + char *sep = strchr(a, '='); | |
|
822 | + enum action act = A_YES; | |
|
823 | + if (sep) | |
|
824 | + { | |
|
825 | + *sep++ = 0; | |
|
826 | + if (!strcmp(sep, "yes")) | |
|
827 | + act = A_YES; | |
|
828 | + else if (!strcmp(sep, "no")) | |
|
829 | + act = A_NO; | |
|
830 | + else | |
|
831 | + return 0; | |
|
832 | + } | |
|
833 | + | |
|
834 | + struct path_rule *r = xmalloc(sizeof(*r) + strlen(a) + 1); | |
|
835 | + r->path = (char *)(r+1); | |
|
836 | + strcpy(r->path, a); | |
|
837 | + r->action = act; | |
|
838 | + r->next = NULL; | |
|
839 | + *last_path_rule = r; | |
|
840 | + last_path_rule = &r->next; | |
|
841 | + return 1; | |
|
842 | + } | |
|
843 | + | |
|
844 | + static enum action | |
|
845 | + match_path_rule(struct path_rule *r, char *path) | |
|
846 | + { | |
|
847 | + char *rr = r->path; | |
|
848 | + while (*rr) | |
|
849 | + if (*rr++ != *path++) | |
|
850 | + { | |
|
851 | + if (rr[-1] == '/' && !path[-1]) | |
|
852 | + break; | |
|
853 | + return A_DEFAULT; | |
|
854 | + } | |
|
855 | + if (rr > r->path && rr[-1] != '/' && *path) | |
|
856 | + return A_DEFAULT; | |
|
857 | + return r->action; | |
|
858 | + } | |
|
859 | + | |
|
860 | + /*** Environment rules ***/ | |
|
861 | + | |
|
862 | + struct env_rule { | |
|
863 | + char *var; // Variable to match | |
|
864 | + char *val; // ""=clear, NULL=inherit | |
|
865 | + int var_len; | |
|
866 | + struct env_rule *next; | |
|
867 | + }; | |
|
868 | + | |
|
869 | + static struct env_rule *first_env_rule; | |
|
870 | + static struct env_rule **last_env_rule = &first_env_rule; | |
|
871 | + | |
|
872 | + static struct env_rule default_env_rules[] = { | |
|
873 | + { "LIBC_FATAL_STDERR_", "1" } | |
|
874 | + }; | |
|
875 | + | |
|
876 | + static int | |
|
877 | + set_env_action(char *a0) | |
|
878 | + { | |
|
879 | + struct env_rule *r = xmalloc(sizeof(*r) + strlen(a0) + 1); | |
|
880 | + char *a = (char *)(r+1); | |
|
881 | + strcpy(a, a0); | |
|
882 | + | |
|
883 | + char *sep = strchr(a, '='); | |
|
884 | + if (sep == a) | |
|
885 | + return 0; | |
|
886 | + r->var = a; | |
|
887 | + if (sep) | |
|
888 | + { | |
|
889 | + *sep++ = 0; | |
|
890 | + r->val = sep; | |
|
891 | + } | |
|
892 | + else | |
|
893 | + r->val = NULL; | |
|
894 | + *last_env_rule = r; | |
|
895 | + last_env_rule = &r->next; | |
|
896 | + r->next = NULL; | |
|
897 | + return 1; | |
|
898 | + } | |
|
899 | + | |
|
900 | + static int | |
|
901 | + match_env_var(char *env_entry, struct env_rule *r) | |
|
902 | + { | |
|
903 | + if (strncmp(env_entry, r->var, r->var_len)) | |
|
904 | + return 0; | |
|
905 | + return (env_entry[r->var_len] == '='); | |
|
906 | + } | |
|
907 | + | |
|
908 | + static void | |
|
909 | + apply_env_rule(char **env, int *env_sizep, struct env_rule *r) | |
|
910 | + { | |
|
911 | + // First remove the variable if already set | |
|
912 | + int pos = 0; | |
|
913 | + while (pos < *env_sizep && !match_env_var(env[pos], r)) | |
|
914 | + pos++; | |
|
915 | + if (pos < *env_sizep) | |
|
916 | + { | |
|
917 | + (*env_sizep)--; | |
|
918 | + env[pos] = env[*env_sizep]; | |
|
919 | + env[*env_sizep] = NULL; | |
|
920 | + } | |
|
921 | + | |
|
922 | + // What is the new value? | |
|
923 | + char *new; | |
|
924 | + if (r->val) | |
|
925 | + { | |
|
926 | + if (!r->val[0]) | |
|
927 | + return; | |
|
928 | + new = xmalloc(r->var_len + 1 + strlen(r->val) + 1); | |
|
929 | + sprintf(new, "%s=%s", r->var, r->val); | |
|
930 | + } | |
|
931 | + else | |
|
932 | + { | |
|
933 | + pos = 0; | |
|
934 | + while (environ[pos] && !match_env_var(environ[pos], r)) | |
|
935 | + pos++; | |
|
936 | + if (!(new = environ[pos])) | |
|
937 | + return; | |
|
938 | + } | |
|
939 | + | |
|
940 | + // Add it at the end of the array | |
|
941 | + env[(*env_sizep)++] = new; | |
|
942 | + env[*env_sizep] = NULL; | |
|
943 | + } | |
|
944 | + | |
|
945 | + static char ** | |
|
946 | + setup_environment(void) | |
|
947 | + { | |
|
948 | + // Link built-in rules with user rules | |
|
949 | + for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--) | |
|
950 | + { | |
|
951 | + default_env_rules[i].next = first_env_rule; | |
|
952 | + first_env_rule = &default_env_rules[i]; | |
|
953 | + } | |
|
954 | + | |
|
955 | + // Scan the original environment | |
|
956 | + char **orig_env = environ; | |
|
957 | + int orig_size = 0; | |
|
958 | + while (orig_env[orig_size]) | |
|
959 | + orig_size++; | |
|
960 | + | |
|
961 | + // For each rule, reserve one more slot and calculate length | |
|
962 | + int num_rules = 0; | |
|
963 | + for (struct env_rule *r = first_env_rule; r; r=r->next) | |
|
964 | + { | |
|
965 | + num_rules++; | |
|
966 | + r->var_len = strlen(r->var); | |
|
967 | + } | |
|
968 | + | |
|
969 | + // Create a new environment | |
|
970 | + char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *)); | |
|
971 | + int size; | |
|
972 | + if (pass_environ) | |
|
973 | + { | |
|
974 | + memcpy(env, environ, orig_size * sizeof(char *)); | |
|
975 | + size = orig_size; | |
|
976 | + } | |
|
977 | + else | |
|
978 | + size = 0; | |
|
979 | + env[size] = NULL; | |
|
980 | + | |
|
981 | + // Apply the rules one by one | |
|
982 | + for (struct env_rule *r = first_env_rule; r; r=r->next) | |
|
983 | + apply_env_rule(env, &size, r); | |
|
984 | + | |
|
985 | + // Return the new env and pass some gossip | |
|
986 | + if (verbose > 1) | |
|
987 | + { | |
|
988 | + fprintf(stderr, "Passing environment:\n"); | |
|
989 | + for (int i=0; env[i]; i++) | |
|
990 | + fprintf(stderr, "\t%s\n", env[i]); | |
|
991 | + } | |
|
992 | + return env; | |
|
993 | + } | |
|
994 | + | |
|
995 | + /*** Low-level parsing of syscalls ***/ | |
|
996 | + | |
|
997 | + #ifdef CONFIG_BOX_KERNEL_AMD64 | |
|
998 | + typedef uint64_t arg_t; | |
|
999 | + #else | |
|
1000 | + typedef uint32_t arg_t; | |
|
1001 | + #endif | |
|
1002 | + | |
|
1003 | + struct syscall_args { | |
|
1004 | + arg_t sys; | |
|
1005 | + arg_t arg1, arg2, arg3; | |
|
1006 | + arg_t result; | |
|
1007 | + struct user user; | |
|
1008 | + }; | |
|
1009 | + | |
|
1010 | + static int user_mem_fd; | |
|
1011 | + | |
|
1012 | + static int read_user_mem(arg_t addr, char *buf, int len) | |
|
1013 | + { | |
|
1014 | + if (!user_mem_fd) | |
|
1015 | + { | |
|
1016 | + char memname[64]; | |
|
1017 | + sprintf(memname, "/proc/%d/mem", (int) box_pid); | |
|
1018 | + user_mem_fd = open(memname, O_RDONLY); | |
|
1019 | + if (user_mem_fd < 0) | |
|
1020 | + die("open(%s): %m", memname); | |
|
1021 | + } | |
|
1022 | + if (lseek64(user_mem_fd, addr, SEEK_SET) < 0) | |
|
1023 | + die("lseek64(mem): %m"); | |
|
1024 | + return read(user_mem_fd, buf, len); | |
|
1025 | + } | |
|
1026 | + | |
|
1027 | + static void close_user_mem(void) | |
|
1028 | + { | |
|
1029 | + if (user_mem_fd) | |
|
1030 | + { | |
|
1031 | + close(user_mem_fd); | |
|
1032 | + user_mem_fd = 0; | |
|
1033 | + } | |
|
1034 | + } | |
|
1035 | + | |
|
1036 | + #ifdef CONFIG_BOX_KERNEL_AMD64 | |
|
1037 | + | |
|
1038 | + static void | |
|
1039 | + get_syscall_args(struct syscall_args *a, int is_exit) | |
|
1040 | + { | |
|
1041 | + if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0) | |
|
1042 | + die("ptrace(PTRACE_GETREGS): %m"); | |
|
1043 | + a->sys = a->user.regs.orig_rax; | |
|
1044 | + a->result = a->user.regs.rax; | |
|
1045 | + | |
|
1046 | + /* | |
|
1047 | + * CAVEAT: We have to check carefully that this is a real 64-bit syscall. | |
|
1048 | + * We test whether the process runs in 64-bit mode, but surprisingly this | |
|
1049 | + * is not enough: a 64-bit process can still issue the INT 0x80 instruction | |
|
1050 | + * which performs a 32-bit syscall. Currently, the only known way how to | |
|
1051 | + * detect this situation is to inspect the instruction code (the kernel | |
|
1052 | + * keeps a syscall type flag internally, but it is not accessible from | |
|
1053 | + * user space). Hopefully, there is no instruction whose suffix is the | |
|
1054 | + * code of the SYSCALL instruction. Sometimes, one would wish the | |
|
1055 | + * instruction codes to be unique even when read backwards :) | |
|
1056 | + */ | |
|
1057 | + | |
|
1058 | + if (is_exit) | |
|
1059 | + return; | |
|
1060 | + | |
|
1061 | + int sys_type; | |
|
1062 | + uint16_t instr; | |
|
1063 | + | |
|
1064 | + switch (a->user.regs.cs) | |
|
1065 | + { | |
|
1066 | + case 0x23: | |
|
1067 | + // 32-bit CPU mode => only 32-bit syscalls can be issued | |
|
1068 | + sys_type = 32; | |
|
1069 | + break; | |
|
1070 | + case 0x33: | |
|
1071 | + // 64-bit CPU mode | |
|
1072 | + if (read_user_mem(a->user.regs.rip-2, (char *) &instr, 2) != 2) | |
|
1073 | + err("FO: Cannot read syscall instruction"); | |
|
1074 | + switch (instr) | |
|
1075 | + { | |
|
1076 | + case 0x050f: | |
|
1077 | + break; | |
|
1078 | + case 0x80cd: | |
|
1079 | + err("FO: Forbidden 32-bit syscall in 64-bit mode"); | |
|
1080 | + default: | |
|
1081 | + err("XX: Unknown syscall instruction %04x", instr); | |
|
1082 | + } | |
|
1083 | + sys_type = 64; | |
|
1084 | + break; | |
|
1085 | + default: | |
|
1086 | + err("XX: Unknown code segment %04jx", (intmax_t) a->user.regs.cs); | |
|
1087 | + } | |
|
1088 | + | |
|
1089 | + #ifdef CONFIG_BOX_USER_AMD64 | |
|
1090 | + if (sys_type != 64) | |
|
1091 | + err("FO: Forbidden %d-bit mode syscall", sys_type); | |
|
1092 | + #else | |
|
1093 | + if (sys_type != (exec_seen ? 32 : 64)) | |
|
1094 | + err("FO: Forbidden %d-bit mode syscall", sys_type); | |
|
1095 | + #endif | |
|
1096 | + | |
|
1097 | + if (sys_type == 32) | |
|
1098 | + { | |
|
1099 | + a->arg1 = a->user.regs.rbx; | |
|
1100 | + a->arg2 = a->user.regs.rcx; | |
|
1101 | + a->arg3 = a->user.regs.rdx; | |
|
1102 | + } | |
|
1103 | + else | |
|
1104 | + { | |
|
1105 | + a->arg1 = a->user.regs.rdi; | |
|
1106 | + a->arg2 = a->user.regs.rsi; | |
|
1107 | + a->arg3 = a->user.regs.rdx; | |
|
1108 | + } | |
|
1109 | + } | |
|
1110 | + | |
|
1111 | + static void | |
|
1112 | + set_syscall_nr(struct syscall_args *a, arg_t sys) | |
|
1113 | + { | |
|
1114 | + a->sys = sys; | |
|
1115 | + a->user.regs.orig_rax = sys; | |
|
1116 | + if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0) | |
|
1117 | + die("ptrace(PTRACE_SETREGS): %m"); | |
|
1118 | + } | |
|
1119 | + | |
|
1120 | + static void | |
|
1121 | + sanity_check(void) | |
|
1122 | + { | |
|
1123 | + } | |
|
1124 | + | |
|
1125 | + #else | |
|
1126 | + | |
|
1127 | + static void | |
|
1128 | + get_syscall_args(struct syscall_args *a, int is_exit UNUSED) | |
|
1129 | + { | |
|
1130 | + if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0) | |
|
1131 | + die("ptrace(PTRACE_GETREGS): %m"); | |
|
1132 | + a->sys = a->user.regs.orig_eax; | |
|
1133 | + a->arg1 = a->user.regs.ebx; | |
|
1134 | + a->arg2 = a->user.regs.ecx; | |
|
1135 | + a->arg3 = a->user.regs.edx; | |
|
1136 | + a->result = a->user.regs.eax; | |
|
1137 | + } | |
|
1138 | + | |
|
1139 | + static void | |
|
1140 | + set_syscall_nr(struct syscall_args *a, arg_t sys) | |
|
1141 | + { | |
|
1142 | + a->sys = sys; | |
|
1143 | + a->user.regs.orig_eax = sys; | |
|
1144 | + if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0) | |
|
1145 | + die("ptrace(PTRACE_SETREGS): %m"); | |
|
1146 | + } | |
|
1147 | + | |
|
1148 | + static void | |
|
1149 | + sanity_check(void) | |
|
1150 | + { | |
|
1151 | + #if !defined(CONFIG_BOX_ALLOW_INSECURE) | |
|
1152 | + struct utsname uts; | |
|
1153 | + if (uname(&uts) < 0) | |
|
1154 | + die("uname() failed: %m"); | |
|
1155 | + | |
|
1156 | + if (!strcmp(uts.machine, "x86_64")) | |
|
1157 | + die("Running 32-bit sandbox on 64-bit kernels is inherently unsafe. Please get a 64-bit version."); | |
|
1158 | + #endif | |
|
1159 | + } | |
|
1160 | + | |
|
1161 | + #endif | |
|
1162 | + | |
|
1163 | + /*** Syscall checks ***/ | |
|
1164 | + | |
|
1165 | + static void | |
|
1166 | + valid_filename(arg_t addr) | |
|
1167 | + { | |
|
1168 | + char namebuf[4096], *p, *end; | |
|
1169 | + | |
|
1170 | + if (!file_access) | |
|
1171 | + err("FA: File access forbidden"); | |
|
1172 | + if (file_access >= 9) | |
|
1173 | + return; | |
|
1174 | + | |
|
1175 | + p = end = namebuf; | |
|
1176 | + do | |
|
1177 | + { | |
|
1178 | + if (p >= end) | |
|
1179 | + { | |
|
1180 | + int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1)); | |
|
1181 | + int l = namebuf + sizeof(namebuf) - end; | |
|
1182 | + if (l > remains) | |
|
1183 | + l = remains; | |
|
1184 | + if (!l) | |
|
1185 | + err("FA: Access to file with name too long"); | |
|
1186 | + remains = read_user_mem(addr, end, l); | |
|
1187 | + if (remains < 0) | |
|
1188 | + die("read(mem): %m"); | |
|
1189 | + if (!remains) | |
|
1190 | + err("FA: Access to file with name out of memory"); | |
|
1191 | + end += remains; | |
|
1192 | + addr += remains; | |
|
1193 | + } | |
|
1194 | + } | |
|
1195 | + while (*p++); | |
|
1196 | + | |
|
1197 | + msg("[%s] ", namebuf); | |
|
1198 | + if (file_access >= 3) | |
|
1199 | + return; | |
|
1200 | + | |
|
1201 | + // Everything in current directory is permitted | |
|
1202 | + if (!strchr(namebuf, '/') && strcmp(namebuf, "..")) | |
|
1203 | + return; | |
|
1204 | + | |
|
1205 | + // ".." anywhere in the path is forbidden | |
|
1206 | + enum action act = A_DEFAULT; | |
|
1207 | + if (strstr(namebuf, "..")) | |
|
1208 | + act = A_NO; | |
|
1209 | + | |
|
1210 | + // Scan user rules | |
|
1211 | + for (struct path_rule *r = user_path_rules; r && !act; r=r->next) | |
|
1212 | + act = match_path_rule(r, namebuf); | |
|
1213 | + | |
|
1214 | + // Scan built-in rules | |
|
1215 | + if (file_access >= 2) | |
|
1216 | + for (int i=0; i<ARRAY_SIZE(default_path_rules) && !act; i++) | |
|
1217 | + act = match_path_rule(&default_path_rules[i], namebuf); | |
|
1218 | + | |
|
1219 | + if (act != A_YES) | |
|
1220 | + err("FA: Forbidden access to file `%s'", namebuf); | |
|
1221 | + } | |
|
1222 | + | |
|
1223 | + // Check syscall. If invalid, return -1, otherwise return the action mask. | |
|
1224 | + static int | |
|
1225 | + valid_syscall(struct syscall_args *a) | |
|
1226 | + { | |
|
1227 | + unsigned int sys = a->sys; | |
|
1228 | + unsigned int act = (sys < NUM_ACTIONS) ? syscall_action[sys] : A_DEFAULT; | |
|
1229 | + | |
|
1230 | + if (act & A_LIBERAL) | |
|
1231 | + { | |
|
1232 | + if (filter_syscalls != 1) | |
|
1233 | + act = A_DEFAULT; | |
|
1234 | + } | |
|
1235 | + | |
|
1236 | + switch (act & A_ACTION_MASK) | |
|
1237 | + { | |
|
1238 | + case A_YES: | |
|
1239 | + return act; | |
|
1240 | + case A_NO: | |
|
1241 | + return -1; | |
|
1242 | + case A_FILENAME: | |
|
1243 | + valid_filename(a->arg1); | |
|
1244 | + return act; | |
|
1245 | + default: ; | |
|
1246 | + } | |
|
1247 | + | |
|
1248 | + switch (sys) | |
|
1249 | + { | |
|
1250 | + case __NR_kill: | |
|
1251 | + if (a->arg1 == (arg_t) box_pid) | |
|
1252 | + { | |
|
1253 | + meta_printf("exitsig:%d\n", (int) a->arg2); | |
|
1254 | + err("SG: Committed suicide by signal %d", (int) a->arg2); | |
|
1255 | + } | |
|
1256 | + return -1; | |
|
1257 | + case __NR_tgkill: | |
|
1258 | + if (a->arg1 == (arg_t) box_pid && a->arg2 == (arg_t) box_pid) | |
|
1259 | + { | |
|
1260 | + meta_printf("exitsig:%d\n", (int) a->arg3); | |
|
1261 | + err("SG: Committed suicide by signal %d", (int) a->arg3); | |
|
1262 | + } | |
|
1263 | + return -1; | |
|
1264 | + default: | |
|
1265 | + return -1; | |
|
1266 | + } | |
|
1267 | + } | |
|
1268 | + | |
|
1269 | + static void | |
|
1270 | + signal_alarm(int unused UNUSED) | |
|
1271 | + { | |
|
1272 | + /* Time limit checks are synchronous, so we only schedule them there. */ | |
|
1273 | + timer_tick = 1; | |
|
1274 | + alarm(1); | |
|
1275 | + } | |
|
1276 | + | |
|
1277 | + static void | |
|
1278 | + signal_int(int unused UNUSED) | |
|
1279 | + { | |
|
1280 | + /* Interrupts are fatal, so no synchronization requirements. */ | |
|
1281 | + meta_printf("exitsig:%d\n", SIGINT); | |
|
1282 | + err("SG: Interrupted"); | |
|
1283 | + } | |
|
1284 | + | |
|
1285 | + #define PROC_BUF_SIZE 4096 | |
|
1286 | + static void | |
|
1287 | + read_proc_file(char *buf, char *name, int *fdp) | |
|
1288 | + { | |
|
1289 | + int c; | |
|
1290 | + | |
|
1291 | + if (!*fdp) | |
|
1292 | + { | |
|
1293 | + sprintf(buf, "/proc/%d/%s", (int) box_pid, name); | |
|
1294 | + *fdp = open(buf, O_RDONLY); | |
|
1295 | + if (*fdp < 0) | |
|
1296 | + die("open(%s): %m", buf); | |
|
1297 | + } | |
|
1298 | + lseek(*fdp, 0, SEEK_SET); | |
|
1299 | + if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0) | |
|
1300 | + die("read on /proc/$pid/%s: %m", name); | |
|
1301 | + if (c >= PROC_BUF_SIZE-1) | |
|
1302 | + die("/proc/$pid/%s too long", name); | |
|
1303 | + buf[c] = 0; | |
|
1304 | + } | |
|
1305 | + | |
|
1306 | + static void | |
|
1307 | + check_timeout(void) | |
|
1308 | + { | |
|
1309 | + if (wall_timeout) | |
|
1310 | + { | |
|
1311 | + struct timeval now, wall; | |
|
1312 | + int wall_ms; | |
|
1313 | + gettimeofday(&now, NULL); | |
|
1314 | + timersub(&now, &start_time, &wall); | |
|
1315 | + wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000; | |
|
1316 | + if (wall_ms > wall_timeout) | |
|
1317 | + err("TO: Time limit exceeded (wall clock)"); | |
|
1318 | + if (verbose > 1) | |
|
1319 | + fprintf(stderr, "[wall time check: %d msec]\n", wall_ms); | |
|
1320 | + } | |
|
1321 | + if (timeout) | |
|
1322 | + { | |
|
1323 | + char buf[PROC_BUF_SIZE], *x; | |
|
1324 | + int utime, stime, ms; | |
|
1325 | + static int proc_stat_fd; | |
|
1326 | + read_proc_file(buf, "stat", &proc_stat_fd); | |
|
1327 | + x = buf; | |
|
1328 | + while (*x && *x != ' ') | |
|
1329 | + x++; | |
|
1330 | + while (*x == ' ') | |
|
1331 | + x++; | |
|
1332 | + if (*x++ != '(') | |
|
1333 | + die("proc stat syntax error 1"); | |
|
1334 | + while (*x && (*x != ')' || x[1] != ' ')) | |
|
1335 | + x++; | |
|
1336 | + while (*x == ')' || *x == ' ') | |
|
1337 | + x++; | |
|
1338 | + if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2) | |
|
1339 | + die("proc stat syntax error 2"); | |
|
1340 | + ms = (utime + stime) * 1000 / ticks_per_sec; | |
|
1341 | + if (verbose > 1) | |
|
1342 | + fprintf(stderr, "[time check: %d msec]\n", ms); | |
|
1343 | + if (ms > timeout && ms > extra_timeout) | |
|
1344 | + err("TO: Time limit exceeded"); | |
|
1345 | + } | |
|
1346 | + } | |
|
1347 | + | |
|
1348 | + static void | |
|
1349 | + sample_mem_peak(void) | |
|
1350 | + { | |
|
1351 | + /* | |
|
1352 | + * We want to find out the peak memory usage of the process, which is | |
|
1353 | + * maintained by the kernel, but unforunately it gets lost when the | |
|
1354 | + * process exits (it is not reported in struct rusage). Therefore we | |
|
1355 | + * have to sample it whenever we suspect that the process is about | |
|
1356 | + * to exit. | |
|
1357 | + */ | |
|
1358 | + char buf[PROC_BUF_SIZE], *x; | |
|
1359 | + static int proc_status_fd; | |
|
1360 | + read_proc_file(buf, "status", &proc_status_fd); | |
|
1361 | + | |
|
1362 | + x = buf; | |
|
1363 | + while (*x) | |
|
1364 | + { | |
|
1365 | + char *key = x; | |
|
1366 | + while (*x && *x != ':' && *x != '\n') | |
|
1367 | + x++; | |
|
1368 | + if (!*x || *x == '\n') | |
|
1369 | + break; | |
|
1370 | + *x++ = 0; | |
|
1371 | + while (*x == ' ' || *x == '\t') | |
|
1372 | + x++; | |
|
1373 | + | |
|
1374 | + char *val = x; | |
|
1375 | + while (*x && *x != '\n') | |
|
1376 | + x++; | |
|
1377 | + if (!*x) | |
|
1378 | + break; | |
|
1379 | + *x++ = 0; | |
|
1380 | + | |
|
1381 | + if (!strcmp(key, "VmPeak")) | |
|
1382 | + { | |
|
1383 | + int peak = atoi(val); | |
|
1384 | + if (peak > mem_peak_kb) | |
|
1385 | + mem_peak_kb = peak; | |
|
1386 | + } | |
|
1387 | + } | |
|
1388 | + | |
|
1389 | + if (verbose > 1) | |
|
1390 | + msg("[mem-peak: %u KB]\n", mem_peak_kb); | |
|
1391 | + } | |
|
1392 | + | |
|
1393 | + static void | |
|
1394 | + boxkeeper(void) | |
|
1395 | + { | |
|
1396 | + int syscall_count = (filter_syscalls ? 0 : 1); | |
|
1397 | + struct sigaction sa; | |
|
1398 | + | |
|
1399 | + is_ptraced = 1; | |
|
1400 | + | |
|
1401 | + bzero(&sa, sizeof(sa)); | |
|
1402 | + sa.sa_handler = signal_int; | |
|
1403 | + sigaction(SIGINT, &sa, NULL); | |
|
1404 | + | |
|
1405 | + gettimeofday(&start_time, NULL); | |
|
1406 | + ticks_per_sec = sysconf(_SC_CLK_TCK); | |
|
1407 | + if (ticks_per_sec <= 0) | |
|
1408 | + die("Invalid ticks_per_sec!"); | |
|
1409 | + | |
|
1410 | + if (timeout || wall_timeout) | |
|
1411 | + { | |
|
1412 | + sa.sa_handler = signal_alarm; | |
|
1413 | + sigaction(SIGALRM, &sa, NULL); | |
|
1414 | + alarm(1); | |
|
1415 | + } | |
|
1416 | + | |
|
1417 | + for(;;) | |
|
1418 | + { | |
|
1419 | + struct rusage rus; | |
|
1420 | + int stat; | |
|
1421 | + pid_t p; | |
|
1422 | + if (timer_tick) | |
|
1423 | + { | |
|
1424 | + check_timeout(); | |
|
1425 | + timer_tick = 0; | |
|
1426 | + } | |
|
1427 | + p = wait4(box_pid, &stat, WUNTRACED, &rus); | |
|
1428 | + if (p < 0) | |
|
1429 | + { | |
|
1430 | + if (errno == EINTR) | |
|
1431 | + continue; | |
|
1432 | + die("wait4: %m"); | |
|
1433 | + } | |
|
1434 | + if (p != box_pid) | |
|
1435 | + die("wait4: unknown pid %d exited!", p); | |
|
1436 | + if (WIFEXITED(stat)) | |
|
1437 | + { | |
|
1438 | + box_pid = 0; | |
|
1439 | + final_stats(&rus); | |
|
1440 | + if (WEXITSTATUS(stat)) | |
|
1441 | + { | |
|
1442 | + if (syscall_count) | |
|
1443 | + { | |
|
1444 | + meta_printf("exitcode:%d\n", WEXITSTATUS(stat)); | |
|
1445 | + err("RE: Exited with error status %d", WEXITSTATUS(stat)); | |
|
1446 | + } | |
|
1447 | + else | |
|
1448 | + { | |
|
1449 | + // Internal error happened inside the child process and it has been already reported. | |
|
1450 | + box_exit(2); | |
|
1451 | + } | |
|
1452 | + } | |
|
1453 | + if (timeout && total_ms > timeout) | |
|
1454 | + err("TO: Time limit exceeded"); | |
|
1455 | + if (wall_timeout && wall_ms > wall_timeout) | |
|
1456 | + err("TO: Time limit exceeded (wall clock)"); | |
|
1457 | + flush_line(); | |
|
1458 | + fprintf(stderr,"OK\n"); | |
|
1459 | + box_exit(0); | |
|
1460 | + } | |
|
1461 | + if (WIFSIGNALED(stat)) | |
|
1462 | + { | |
|
1463 | + box_pid = 0; | |
|
1464 | + meta_printf("exitsig:%d\n", WTERMSIG(stat)); | |
|
1465 | + final_stats(&rus); | |
|
1466 | + err("SG: Caught fatal signal %d%s", WTERMSIG(stat), (syscall_count ? "" : " during startup")); | |
|
1467 | + } | |
|
1468 | + if (WIFSTOPPED(stat)) | |
|
1469 | + { | |
|
1470 | + int sig = WSTOPSIG(stat); | |
|
1471 | + if (sig == SIGTRAP) | |
|
1472 | + { | |
|
1473 | + if (verbose > 2) | |
|
1474 | + msg("[ptrace status %08x] ", stat); | |
|
1475 | + static int stop_count; | |
|
1476 | + if (!stop_count++) /* Traceme request */ | |
|
1477 | + msg(">> Traceme request caught\n"); | |
|
1478 | + else | |
|
1479 | + err("SG: Breakpoint"); | |
|
1480 | + ptrace(PTRACE_SYSCALL, box_pid, 0, 0); | |
|
1481 | + } | |
|
1482 | + else if (sig == (SIGTRAP | 0x80)) | |
|
1483 | + { | |
|
1484 | + if (verbose > 2) | |
|
1485 | + msg("[ptrace status %08x] ", stat); | |
|
1486 | + struct syscall_args a; | |
|
1487 | + static unsigned int sys_tick, last_act; | |
|
1488 | + static arg_t last_sys; | |
|
1489 | + if (++sys_tick & 1) /* Syscall entry */ | |
|
1490 | + { | |
|
1491 | + char namebuf[32]; | |
|
1492 | + int act; | |
|
1493 | + | |
|
1494 | + get_syscall_args(&a, 0); | |
|
1495 | + arg_t sys = a.sys; | |
|
1496 | + msg(">> Syscall %-12s (%08jx,%08jx,%08jx) ", syscall_name(sys, namebuf), (intmax_t) a.arg1, (intmax_t) a.arg2, (intmax_t) a.arg3); | |
|
1497 | + if (!exec_seen) | |
|
1498 | + { | |
|
1499 | + msg("[master] "); | |
|
1500 | + if (sys == NATIVE_NR_execve) | |
|
1501 | + { | |
|
1502 | + exec_seen = 1; | |
|
1503 | + close_user_mem(); | |
|
1504 | + } | |
|
1505 | + } | |
|
1506 | + else if ((act = valid_syscall(&a)) >= 0) | |
|
1507 | + { | |
|
1508 | + last_act = act; | |
|
1509 | + syscall_count++; | |
|
1510 | + if (act & A_SAMPLE_MEM) | |
|
1511 | + sample_mem_peak(); | |
|
1512 | + } | |
|
1513 | + else | |
|
1514 | + { | |
|
1515 | + /* | |
|
1516 | + * Unfortunately, PTRACE_KILL kills _after_ the syscall completes, | |
|
1517 | + * so we have to change it to something harmless (e.g., an undefined | |
|
1518 | + * syscall) and make the program continue. | |
|
1519 | + */ | |
|
1520 | + set_syscall_nr(&a, ~(arg_t)0); | |
|
1521 | + err("FO: Forbidden syscall %s", syscall_name(sys, namebuf)); | |
|
1522 | + } | |
|
1523 | + last_sys = sys; | |
|
1524 | + } | |
|
1525 | + else /* Syscall return */ | |
|
1526 | + { | |
|
1527 | + get_syscall_args(&a, 1); | |
|
1528 | + if (a.sys == ~(arg_t)0) | |
|
1529 | + { | |
|
1530 | + /* Some syscalls (sigreturn et al.) do not return a value */ | |
|
1531 | + if (!(last_act & A_NO_RETVAL)) | |
|
1532 | + err("XX: Syscall does not return, but it should"); | |
|
1533 | + } | |
|
1534 | + else | |
|
1535 | + { | |
|
1536 | + if (a.sys != last_sys) | |
|
1537 | + err("XX: Mismatched syscall entry/exit"); | |
|
1538 | + } | |
|
1539 | + if (last_act & A_NO_RETVAL) | |
|
1540 | + msg("= ?\n"); | |
|
1541 | + else | |
|
1542 | + msg("= %jd\n", (intmax_t) a.result); | |
|
1543 | + } | |
|
1544 | + ptrace(PTRACE_SYSCALL, box_pid, 0, 0); | |
|
1545 | + } | |
|
1546 | + else if (sig == SIGSTOP) | |
|
1547 | + { | |
|
1548 | + msg(">> SIGSTOP\n"); | |
|
1549 | + if (ptrace(PTRACE_SETOPTIONS, box_pid, NULL, (void *) PTRACE_O_TRACESYSGOOD) < 0) | |
|
1550 | + die("ptrace(PTRACE_SETOPTIONS): %m"); | |
|
1551 | + ptrace(PTRACE_SYSCALL, box_pid, 0, 0); | |
|
1552 | + } | |
|
1553 | + else if (sig != SIGXCPU && sig != SIGXFSZ) | |
|
1554 | + { | |
|
1555 | + msg(">> Signal %d\n", sig); | |
|
1556 | + sample_mem_peak(); /* Signal might be fatal, so update mem-peak */ | |
|
1557 | + ptrace(PTRACE_SYSCALL, box_pid, 0, sig); | |
|
1558 | + } | |
|
1559 | + else | |
|
1560 | + { | |
|
1561 | + meta_printf("exitsig:%d", sig); | |
|
1562 | + err("SG: Received signal %d", sig); | |
|
1563 | + } | |
|
1564 | + } | |
|
1565 | + else | |
|
1566 | + die("wait4: unknown status %x, giving up!", stat); | |
|
1567 | + } | |
|
1568 | + } | |
|
1569 | + | |
|
1570 | + static void | |
|
1571 | + box_inside(int argc, char **argv) | |
|
1572 | + { | |
|
1573 | + struct rlimit rl; | |
|
1574 | + char *args[argc+1]; | |
|
1575 | + | |
|
1576 | + memcpy(args, argv, argc * sizeof(char *)); | |
|
1577 | + args[argc] = NULL; | |
|
1578 | + if (set_cwd && chdir(set_cwd)) | |
|
1579 | + die("chdir: %m"); | |
|
1580 | + if (redir_stdin) | |
|
1581 | + { | |
|
1582 | + close(0); | |
|
1583 | + if (open(redir_stdin, O_RDONLY) != 0) | |
|
1584 | + die("open(\"%s\"): %m", redir_stdin); | |
|
1585 | + } | |
|
1586 | + if (redir_stdout) | |
|
1587 | + { | |
|
1588 | + close(1); | |
|
1589 | + if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1) | |
|
1590 | + die("open(\"%s\"): %m", redir_stdout); | |
|
1591 | + } | |
|
1592 | + if (redir_stderr) | |
|
1593 | + { | |
|
1594 | + close(2); | |
|
1595 | + if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2) | |
|
1596 | + die("open(\"%s\"): %m", redir_stderr); | |
|
1597 | + } | |
|
1598 | + else | |
|
1599 | + dup2(1, 2); | |
|
1600 | + setpgrp(); | |
|
1601 | + | |
|
1602 | + if (memory_limit) | |
|
1603 | + { | |
|
1604 | + rl.rlim_cur = rl.rlim_max = memory_limit * 1024; | |
|
1605 | + if (setrlimit(RLIMIT_AS, &rl) < 0) | |
|
1606 | + die("setrlimit(RLIMIT_AS): %m"); | |
|
1607 | + } | |
|
1608 | + | |
|
1609 | + rl.rlim_cur = rl.rlim_max = (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY); | |
|
1610 | + if (setrlimit(RLIMIT_STACK, &rl) < 0) | |
|
1611 | + die("setrlimit(RLIMIT_STACK): %m"); | |
|
1612 | + | |
|
1613 | + rl.rlim_cur = rl.rlim_max = 64; | |
|
1614 | + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) | |
|
1615 | + die("setrlimit(RLIMIT_NOFILE): %m"); | |
|
1616 | + | |
|
1617 | + char **env = setup_environment(); | |
|
1618 | + if (filter_syscalls) | |
|
1619 | + { | |
|
1620 | + if (ptrace(PTRACE_TRACEME) < 0) | |
|
1621 | + die("ptrace(PTRACE_TRACEME): %m"); | |
|
1622 | + /* Trick: Make sure that we are stopped until the boxkeeper wakes up. */ | |
|
1623 | + raise(SIGSTOP); | |
|
1624 | + } | |
|
1625 | + execve(args[0], args, env); | |
|
1626 | + die("execve(\"%s\"): %m", args[0]); | |
|
1627 | + } | |
|
1628 | + | |
|
1629 | + static void | |
|
1630 | + usage(void) | |
|
1631 | + { | |
|
1632 | + fprintf(stderr, "Invalid arguments!\n"); | |
|
1633 | + printf("\ | |
|
1634 | + Usage: box [<options>] -- <command> <arguments>\n\ | |
|
1635 | + \n\ | |
|
1636 | + Options:\n\ | |
|
1637 | + -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\ | |
|
1638 | + -c <dir>\tChange directory to <dir> first\n\ | |
|
1639 | + -e\t\tInherit full environment of the parent process\n\ | |
|
1640 | + -E <var>\tInherit the environment variable <var> from the parent process\n\ | |
|
1641 | + -E <var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\ | |
|
1642 | + -f\t\tFilter system calls (-ff=very restricted)\n\ | |
|
1643 | + -i <file>\tRedirect stdin from <file>\n\ | |
|
1644 | + -k <size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\ | |
|
1645 | + -m <size>\tLimit address space to <size> KB\n\ | |
|
1646 | + -M <file>\tOutput process information to <file> (name:value)\n\ | |
|
1647 | + -o <file>\tRedirect stdout to <file>\n\ | |
|
1648 | + -p <path>\tPermit access to the specified path (or subtree if it ends with a `/')\n\ | |
|
1649 | + -p <path>=<act>\tDefine action for the specified path (<act>=yes/no)\n\ | |
|
1650 | + -r <file>\tRedirect stderr to <file>\n\ | |
|
1651 | + -s <sys>\tPermit the specified syscall (be careful)\n\ | |
|
1652 | + -s <sys>=<act>\tDefine action for the specified syscall (<act>=yes/no/file)\n\ | |
|
1653 | + -t <time>\tSet run time limit (seconds, fractions allowed)\n\ | |
|
1654 | + -T\t\tAllow syscalls for measuring run time\n\ | |
|
1655 | + -v\t\tBe verbose (use multiple times for even more verbosity)\n\ | |
|
1656 | + -w <time>\tSet wall clock time limit (seconds, fractions allowed)\n\ | |
|
1657 | + -x <time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\ | |
|
1658 | + \t\tso that its real execution time is reported (seconds, fractions allowed)\n\ | |
|
1659 | + -A <opt>\tPass <opt> as additional argument to the <command>\n\ | |
|
1660 | + \t\tBe noted that this option will be appended after <arguments> respectively\n\ | |
|
1661 | + "); | |
|
1662 | + exit(2); | |
|
1663 | + } | |
|
1664 | + | |
|
1665 | + int | |
|
1666 | + main(int argc, char **argv) | |
|
1667 | + { | |
|
1668 | + int c; | |
|
1669 | + uid_t uid; | |
|
1670 | + char **prog_argv = xmalloc(sizeof(char*) * argc); | |
|
1671 | + int prog_argc = 0; | |
|
1672 | + | |
|
1673 | + while ((c = getopt(argc, argv, "a:c:eE:fi:k:m:M:o:p:r:s:t:Tvw:x:A:")) >= 0) | |
|
1674 | + switch (c) | |
|
1675 | + { | |
|
1676 | + case 'a': | |
|
1677 | + file_access = atol(optarg); | |
|
1678 | + break; | |
|
1679 | + case 'c': | |
|
1680 | + set_cwd = optarg; | |
|
1681 | + break; | |
|
1682 | + case 'e': | |
|
1683 | + pass_environ = 1; | |
|
1684 | + break; | |
|
1685 | + case 'E': | |
|
1686 | + if (!set_env_action(optarg)) | |
|
1687 | + usage(); | |
|
1688 | + break; | |
|
1689 | + case 'f': | |
|
1690 | + filter_syscalls++; | |
|
1691 | + break; | |
|
1692 | + case 'k': | |
|
1693 | + stack_limit = atol(optarg); | |
|
1694 | + break; | |
|
1695 | + case 'i': | |
|
1696 | + redir_stdin = optarg; | |
|
1697 | + break; | |
|
1698 | + case 'm': | |
|
1699 | + memory_limit = atol(optarg); | |
|
1700 | + break; | |
|
1701 | + case 'M': | |
|
1702 | + meta_open(optarg); | |
|
1703 | + break; | |
|
1704 | + case 'o': | |
|
1705 | + redir_stdout = optarg; | |
|
1706 | + break; | |
|
1707 | + case 'p': | |
|
1708 | + if (!set_path_action(optarg)) | |
|
1709 | + usage(); | |
|
1710 | + break; | |
|
1711 | + case 'r': | |
|
1712 | + redir_stderr = optarg; | |
|
1713 | + break; | |
|
1714 | + case 's': | |
|
1715 | + if (!set_syscall_action(optarg)) | |
|
1716 | + usage(); | |
|
1717 | + break; | |
|
1718 | + case 't': | |
|
1719 | + timeout = 1000*atof(optarg); | |
|
1720 | + break; | |
|
1721 | + case 'T': | |
|
1722 | + syscall_action[__NR_times] = A_YES; | |
|
1723 | + break; | |
|
1724 | + case 'v': | |
|
1725 | + verbose++; | |
|
1726 | + break; | |
|
1727 | + case 'w': | |
|
1728 | + wall_timeout = 1000*atof(optarg); | |
|
1729 | + break; | |
|
1730 | + case 'x': | |
|
1731 | + extra_timeout = 1000*atof(optarg); | |
|
1732 | + case 'A': | |
|
1733 | + prog_argv[prog_argc++] = strdup(optarg); | |
|
1734 | + break; | |
|
1735 | + break; | |
|
1736 | + default: | |
|
1737 | + usage(); | |
|
1738 | + } | |
|
1739 | + if (optind >= argc) | |
|
1740 | + usage(); | |
|
1741 | + | |
|
1742 | + sanity_check(); | |
|
1743 | + uid = geteuid(); | |
|
1744 | + if (setreuid(uid, uid) < 0) | |
|
1745 | + die("setreuid: %m"); | |
|
1746 | + box_pid = fork(); | |
|
1747 | + if (box_pid < 0) | |
|
1748 | + die("fork: %m"); | |
|
1749 | + if (!box_pid) { | |
|
1750 | + int real_argc = prog_argc + argc - optind; | |
|
1751 | + char **real_argv = xmalloc(sizeof(char*) * (real_argc)); | |
|
1752 | + for (int i = 0;i < argc-optind;i++) | |
|
1753 | + real_argv[i] = strdup(argv[i+optind]); | |
|
1754 | + for (int i = 0;i < prog_argc;i++) | |
|
1755 | + real_argv[argc - optind + i] = strdup(prog_argv[i]); | |
|
1756 | + box_inside(real_argc, real_argv); | |
|
1757 | + } else | |
|
1758 | + boxkeeper(); | |
|
1759 | + die("Internal error: fell over edge of the world"); | |
|
1760 | + } |
You need to be logged in to leave comments.
Login now