1 //===-- klee-replay.c -----------------------------------------------------===//
2 //
3 // The KLEE Symbolic Virtual Machine
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
10 #include "klee-replay.h"
11
12 #include "klee/ADT/KTest.h"
13
14 #include <assert.h>
15 #include <errno.h>
16 #include <getopt.h>
17 #include <stdint.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/wait.h>
22 #include <time.h>
23 #include <unistd.h>
24
25 #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__DragonFly__)
26 #include <signal.h>
27
28 #ifndef fgetc_unlocked
29 #define fgetc_unlocked(x) fgetc (x)
30 #endif
31
32 #ifndef fputc_unlocked
33 #define fputc_unlocked(x,y) fputc (x,y)
34 #endif
35
36 #else
37 #include <sys/signal.h>
38 #endif
39
40 #ifdef HAVE_SYS_CAPABILITY_H
41 #include <sys/capability.h>
42 #endif
43
44 static void __emit_error(const char *msg);
45
46 static KTest* input;
47 static unsigned obj_index;
48
49 static const char *progname = 0;
50 static unsigned monitored_pid = 0;
51 static unsigned monitored_timeout;
52
53 static char *rootdir = NULL;
54 static struct option long_options[] = {
55 {"create-files-only", required_argument, 0, 'f'},
56 {"chroot-to-dir", required_argument, 0, 'r'},
57 {"help", no_argument, 0, 'h'},
58 {"keep-replay-dir", no_argument, 0, 'k'},
59 {0, 0, 0, 0},
60 };
61
stop_monitored(int process)62 static void stop_monitored(int process) {
63 fputs("KLEE-REPLAY: NOTE: TIMEOUT: ATTEMPTING GDB EXIT\n", stderr);
64 int pid = fork();
65 if (pid < 0) {
66 fputs("KLEE-REPLAY: ERROR: gdb_exit: fork failed\n", stderr);
67 } else if (pid == 0) {
68 /* Run gdb in a child process. */
69 const char *gdbargs[] = {
70 "/usr/bin/gdb",
71 "--pid", "",
72 "-q",
73 "--batch",
74 "--eval-command=call exit(1)",
75 0,
76 0
77 };
78 char pids[64];
79 snprintf(pids, sizeof(pids), "%d", process);
80
81 gdbargs[2] = pids;
82 /* Make sure gdb doesn't talk to the user */
83 close(0);
84
85 fputs("KLEE-REPLAY: NOTE: RUNNING GDB: ", stderr);
86 unsigned i;
87 for (i = 0; i != 5; ++i)
88 fprintf(stderr, "%s ", gdbargs[i]);
89 fputc('\n', stderr);
90
91 execvp(gdbargs[0], (char * const *) gdbargs);
92 perror("execvp");
93 _exit(66);
94 } else {
95 /* Parent process, wait for gdb to finish. */
96 int res, status;
97 do {
98 res = waitpid(pid, &status, 0);
99 } while (res < 0 && errno == EINTR);
100
101 if (res < 0) {
102 perror("waitpid");
103 _exit(66);
104 }
105 }
106 }
107
int_handler(int signal)108 static void int_handler(int signal) {
109 fprintf(stderr, "KLEE-REPLAY: NOTE: %s: Received signal %d. Killing monitored process(es)\n",
110 progname, signal);
111 if (monitored_pid) {
112 stop_monitored(monitored_pid);
113 /* Kill the process group of monitored_pid. Since we called
114 setpgrp() for pid, this will not kill us, or any of our
115 ancestors */
116 kill(-monitored_pid, SIGKILL);
117 } else {
118 _exit(99);
119 }
120 }
121
timeout_handler(int signal)122 static void timeout_handler(int signal) {
123 fprintf(stderr, "KLEE-REPLAY: NOTE: EXIT STATUS: TIMED OUT (%d seconds)\n",
124 monitored_timeout);
125 if (monitored_pid) {
126 stop_monitored(monitored_pid);
127 /* Kill the process group of monitored_pid. Since we called
128 setpgrp() for pid, this will not kill us, or any of our
129 ancestors */
130 kill(-monitored_pid, SIGKILL);
131 } else {
132 _exit(88);
133 }
134 }
135
process_status(int status,time_t elapsed,const char * pfx)136 void process_status(int status, time_t elapsed, const char *pfx) {
137 if (pfx)
138 fprintf(stderr, "KLEE-REPLAY: NOTE: %s: ", pfx);
139 if (WIFSIGNALED(status)) {
140 fprintf(stderr, "KLEE-REPLAY: NOTE: EXIT STATUS: CRASHED signal %d (%d seconds)\n",
141 WTERMSIG(status), (int) elapsed);
142 _exit(77);
143 } else if (WIFEXITED(status)) {
144 int rc = WEXITSTATUS(status);
145
146 char msg[64];
147 if (rc == 0) {
148 strcpy(msg, "NORMAL");
149 } else {
150 snprintf(msg, sizeof(msg), "ABNORMAL %d", rc);
151 }
152 fprintf(stderr, "KLEE-REPLAY: NOTE: EXIT STATUS: %s (%d seconds)\n", msg, (int) elapsed);
153 _exit(rc);
154 } else {
155 fprintf(stderr, "KLEE-REPLAY: NOTE: EXIT STATUS: NONE (%d seconds)\n", (int) elapsed);
156 _exit(0);
157 }
158 }
159
160 /* This function assumes that executable is a path pointing to some existing
161 * binary and rootdir is a path pointing to some directory.
162 */
strip_root_dir(char * executable,char * rootdir)163 static inline char *strip_root_dir(char *executable, char *rootdir) {
164 return executable + strlen(rootdir);
165 }
166
run_monitored(char * executable,int argc,char ** argv)167 static void run_monitored(char *executable, int argc, char **argv) {
168 int pid;
169 const char *t = getenv("KLEE_REPLAY_TIMEOUT");
170 if (!t)
171 t = "10000000";
172 monitored_timeout = atoi(t);
173
174 if (monitored_timeout==0) {
175 fprintf(stderr, "KLEE-REPLAY: ERROR: invalid timeout (%s)\n", t);
176 _exit(1);
177 }
178
179 /* Kill monitored process(es) on SIGINT and SIGTERM */
180 signal(SIGINT, int_handler);
181 signal(SIGTERM, int_handler);
182
183 signal(SIGALRM, timeout_handler);
184 pid = fork();
185 if (pid < 0) {
186 perror("fork");
187 _exit(66);
188 } else if (pid == 0) {
189 /* This process actually executes the target program.
190 *
191 * Create a new process group for pid, and the process tree it may spawn. We
192 * do this, because later on we might want to kill pid _and_ all processes
193 * spawned by it and its descendants.
194 */
195 #if !defined(__FreeBSD__) && !defined(__DragonFly__)
196 setpgrp();
197 #else
198 setpgrp(0, 0);
199 #endif
200
201 if (!rootdir) {
202 if (chdir(replay_dir) != 0) {
203 perror("chdir");
204 _exit(66);
205 }
206
207 execv(executable, argv);
208 perror("execv");
209 _exit(66);
210 }
211
212 fprintf(stderr, "KLEE-REPLAY: NOTE: rootdir: %s\n", rootdir);
213 const char *msg;
214 if ((msg = "chdir", chdir(rootdir) == 0) &&
215 (msg = "chroot", chroot(rootdir) == 0)) {
216 msg = "execv";
217 executable = strip_root_dir(executable, rootdir);
218 argv[0] = strip_root_dir(argv[0], rootdir);
219 execv(executable, argv);
220 }
221 perror(msg);
222 _exit(66);
223 } else {
224 /* Parent process which monitors the child. */
225 int res, status;
226 time_t start = time(0);
227 sigset_t masked;
228
229 sigemptyset(&masked);
230 sigaddset(&masked, SIGALRM);
231
232 monitored_pid = pid;
233 alarm(monitored_timeout);
234 do {
235 res = waitpid(pid, &status, 0);
236 } while (res < 0 && errno == EINTR);
237
238 if (res < 0) {
239 perror("waitpid");
240 _exit(66);
241 }
242
243 /* Just in case, kill the process group of pid. Since we called setpgrp()
244 for pid, this will not kill us, or any of our ancestors */
245 kill(-pid, SIGKILL);
246 process_status(status, time(0) - start, 0);
247 }
248 }
249
250 #ifdef HAVE_SYS_CAPABILITY_H
251 /* ensure this process has CAP_SYS_CHROOT capability. */
ensure_capsyschroot(const char * executable)252 void ensure_capsyschroot(const char *executable) {
253 cap_t caps = cap_get_proc(); // all current capabilities.
254 cap_flag_value_t chroot_permitted, chroot_effective;
255
256 if (!caps)
257 perror("cap_get_proc");
258 /* effective and permitted flags should be set for CAP_SYS_CHROOT. */
259 cap_get_flag(caps, CAP_SYS_CHROOT, CAP_PERMITTED, &chroot_permitted);
260 cap_get_flag(caps, CAP_SYS_CHROOT, CAP_EFFECTIVE, &chroot_effective);
261 if (chroot_permitted != CAP_SET || chroot_effective != CAP_SET) {
262 fputs("KLEE-REPLAY: ERROR: chroot: No CAP_SYS_CHROOT capability.\n", stderr);
263 exit(1);
264 }
265 cap_free(caps);
266 }
267 #endif
268
usage(void)269 static void usage(void) {
270 fprintf(stderr,
271 "Usage: %s [option]... <executable> <ktest-file>...\n"
272 " or: %s --create-files-only <ktest-file>\n"
273 "\n"
274 "-r, --chroot-to-dir=DIR use chroot jail, requires CAP_SYS_CHROOT\n"
275 "-k, --keep-replay-dir do not delete replay directory\n"
276 "-h, --help display this help and exit\n"
277 "\n"
278 "Use KLEE_REPLAY_TIMEOUT environment variable to set a timeout (in seconds).\n",
279 progname, progname);
280 exit(1);
281 }
282
283
284 int keep_temps = 0;
285
main(int argc,char ** argv)286 int main(int argc, char** argv) {
287 int prg_argc;
288 char ** prg_argv;
289
290 progname = argv[0];
291
292 if (argc < 3)
293 usage();
294
295 int c, opt_index;
296 while ((c = getopt_long(argc, argv, "f:r:k", long_options, &opt_index)) != -1) {
297 switch (c) {
298 case 'f': {
299 /* Special case hack for only creating files and not actually executing
300 * the program. */
301 if (argc != 3)
302 usage();
303
304 char *input_fname = optarg;
305
306 input = kTest_fromFile(input_fname);
307 if (!input) {
308 fprintf(stderr, "KLEE-REPLAY: ERROR: input file %s not valid.\n", input_fname);
309 exit(1);
310 }
311
312 prg_argc = input->numArgs;
313 prg_argv = input->args;
314 prg_argv[0] = argv[1];
315 klee_init_env(&prg_argc, &prg_argv);
316
317 replay_create_files(&__exe_fs);
318 return 0;
319 }
320
321 case 'r':
322 rootdir = optarg;
323 break;
324
325 case 'k':
326 keep_temps = 1;
327 break;
328 }
329 }
330
331 // Executable needs to be converted to an absolute path, as klee-replay calls
332 // chdir just before executing it
333 char executable[PATH_MAX];
334 if (!realpath(argv[optind], executable)) {
335 snprintf(executable, PATH_MAX, "KLEE-REPLAY: ERROR: executable %s:",
336 argv[optind]);
337 perror(executable);
338 exit(1);
339 }
340 /* Normal execution path ... */
341
342 /* make sure this process has the CAP_SYS_CHROOT capability, if possible. */
343 #ifdef HAVE_SYS_CAPABILITY_H
344 if (rootdir)
345 ensure_capsyschroot(progname);
346 #endif
347
348 /* rootdir should be a prefix of executable's path. */
349 if (rootdir && strstr(executable, rootdir) != executable) {
350 fputs("KLEE-REPLAY: ERROR: chroot: root dir should be a parent dir of executable.\n", stderr);
351 exit(1);
352 }
353
354 int idx = 0;
355 for (idx = optind + 1; idx != argc; ++idx) {
356 char* input_fname = argv[idx];
357 unsigned i;
358
359 input = kTest_fromFile(input_fname);
360 if (!input) {
361 fprintf(stderr, "KLEE-REPLAY: ERROR: input file %s not valid.\n",
362 input_fname);
363 exit(1);
364 }
365
366 obj_index = 0;
367 prg_argc = input->numArgs;
368 prg_argv = input->args;
369 prg_argv[0] = argv[optind];
370 klee_init_env(&prg_argc, &prg_argv);
371 if (idx > 2)
372 fputc('\n', stderr);
373 fprintf(stderr, "KLEE-REPLAY: NOTE: Test file: %s\n"
374 "KLEE-REPLAY: NOTE: Arguments: ", input_fname);
375 for (i=0; i != (unsigned) prg_argc; ++i) {
376 char *s = prg_argv[i];
377 if (s[0]=='A' && s[1] && !s[2]) s[1] = '\0';
378 fprintf(stderr, "\"%s\" ", prg_argv[i]);
379 }
380 fputc('\n', stderr);
381
382 /* Create the input files, pipes, etc. */
383 replay_create_files(&__exe_fs);
384
385 /* Run the test case machinery in a subprocess, eventually this parent
386 process should be a script or something which shells out to the actual
387 execution tool. */
388 int pid = fork();
389 if (pid < 0) {
390 perror("fork");
391 _exit(66);
392 } else if (pid == 0) {
393 /* Run the executable */
394 run_monitored(executable, prg_argc, prg_argv);
395 _exit(0);
396 } else {
397 /* Wait for the executable to finish. */
398 int res, status;
399
400 do {
401 res = waitpid(pid, &status, 0);
402 } while (res < 0 && errno == EINTR);
403
404 // Delete all files in the replay directory
405 replay_delete_files();
406
407 if (res < 0) {
408 perror("waitpid");
409 _exit(66);
410 }
411 }
412 }
413
414 return 0;
415 }
416
417
418 /* KLEE functions */
419
__fputc_unlocked(int c,FILE * f)420 int __fputc_unlocked(int c, FILE *f) {
421 return fputc_unlocked(c, f);
422 }
423
__fgetc_unlocked(FILE * f)424 int __fgetc_unlocked(FILE *f) {
425 return fgetc_unlocked(f);
426 }
427
klee_get_errno()428 int klee_get_errno() {
429 return errno;
430 }
431
klee_warning(char * name)432 void klee_warning(char *name) {
433 fprintf(stderr, "KLEE-REPLAY: klee_warning: %s\n", name);
434 }
435
klee_warning_once(char * name)436 void klee_warning_once(char *name) {
437 fprintf(stderr, "KLEE-REPLAY: klee_warning_once: %s\n", name);
438 }
439
klee_assume(uintptr_t x)440 unsigned klee_assume(uintptr_t x) {
441 if (!x) {
442 fputs("KLEE-REPLAY: klee_assume(0)!\n", stderr);
443 }
444 return 0;
445 }
446
klee_is_symbolic(uintptr_t x)447 unsigned klee_is_symbolic(uintptr_t x) {
448 return 0;
449 }
450
klee_prefer_cex(void * buffer,uintptr_t condition)451 void klee_prefer_cex(void *buffer, uintptr_t condition) {
452 ;
453 }
454
klee_posix_prefer_cex(void * buffer,uintptr_t condition)455 void klee_posix_prefer_cex(void *buffer, uintptr_t condition) {
456 ;
457 }
458
klee_make_symbolic(void * addr,size_t nbytes,const char * name)459 void klee_make_symbolic(void *addr, size_t nbytes, const char *name) {
460 /* XXX remove model version code once new tests gen'd */
461 if (obj_index >= input->numObjects) {
462 if (strcmp("model_version", name) == 0) {
463 assert(nbytes == 4);
464 *((int*) addr) = 0;
465 } else {
466 __emit_error("ran out of appropriate inputs");
467 }
468 } else {
469 KTestObject *boo = &input->objects[obj_index];
470
471 if (strcmp("model_version", name) == 0 &&
472 strcmp("model_version", boo->name) != 0) {
473 assert(nbytes == 4);
474 *((int*) addr) = 0;
475 } else {
476 if (boo->numBytes != nbytes) {
477 fprintf(stderr, "KLEE-REPLAY: ERROR: make_symbolic mismatch, different sizes: "
478 "%d in input file, %lu in code\n", boo->numBytes, (unsigned long)nbytes);
479 exit(1);
480 } else {
481 memcpy(addr, boo->bytes, nbytes);
482 obj_index++;
483 }
484 }
485 }
486 }
487
488 /* Redefined here so that we can check the value read. */
klee_range(int start,int end,const char * name)489 int klee_range(int start, int end, const char* name) {
490 int r;
491
492 if (start >= end) {
493 fputs("KLEE-REPLAY: ERROR: klee_range: invalid range\n", stderr);
494 exit(1);
495 }
496
497 if (start+1 == end)
498 return start;
499 else {
500 klee_make_symbolic(&r, sizeof r, name);
501
502 if (r < start || r >= end) {
503 fprintf(stderr, "KLEE-REPLAY: ERROR: klee_range(%d, %d, %s) returned invalid result: %d\n",
504 start, end, name, r);
505 exit(1);
506 }
507
508 return r;
509 }
510 }
511
klee_report_error(const char * file,int line,const char * message,const char * suffix)512 void klee_report_error(const char *file, int line,
513 const char *message, const char *suffix) {
514 __emit_error(message);
515 }
516
klee_mark_global(void * object)517 void klee_mark_global(void *object) {
518 ;
519 }
520
521 /*** HELPER FUNCTIONS ***/
522
__emit_error(const char * msg)523 static void __emit_error(const char *msg) {
524 fprintf(stderr, "KLEE-REPLAY: ERROR: %s\n", msg);
525 exit(1);
526 }
527