From 63e9d849f662d1a494c6396d4a439cd4c91dfa7e Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Sun, 13 Aug 2017 14:07:31 +0300 Subject: Allow any syscall to be blacklisted (#1447) Allow any syscall to be blacklisted with aid of LD_PRELOAD library, libpostexecseccomp.so. Closes: #1447 --- src/firejail/firejail.h | 4 +- src/firejail/fs_trace.c | 5 ++ src/firejail/main.c | 1 + src/firejail/preproc.c | 5 +- src/firejail/sandbox.c | 13 ++- src/firejail/seccomp.c | 34 +++++--- src/fseccomp/fseccomp.h | 15 ++-- src/fseccomp/main.c | 30 +++---- src/fseccomp/seccomp.c | 118 ++++++++++++++++++++++------ src/fseccomp/seccomp_file.c | 9 ++- src/fseccomp/syscall.c | 64 ++++++++++++++- src/libpostexecseccomp/Makefile.in | 26 ++++++ src/libpostexecseccomp/libpostexecseccomp.c | 59 ++++++++++++++ src/libpostexecseccomp/libpostexecseccomp.h | 25 ++++++ src/man/firejail.txt | 26 ++++++ 15 files changed, 362 insertions(+), 72 deletions(-) create mode 100644 src/libpostexecseccomp/Makefile.in create mode 100644 src/libpostexecseccomp/libpostexecseccomp.c create mode 100644 src/libpostexecseccomp/libpostexecseccomp.h (limited to 'src') diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index b19aded44..5f16d1a5d 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -57,6 +57,7 @@ #define RUN_SECCOMP_AMD64 "/run/firejail/mnt/seccomp.amd64" // amd64 filter installed on i386 architectures #define RUN_SECCOMP_I386 "/run/firejail/mnt/seccomp.i386" // i386 filter installed on amd64 architectures #define RUN_SECCOMP_MDWX "/run/firejail/mnt/seccomp.mdwx" // filter for memory-deny-write-execute +#define RUN_SECCOMP_POSTEXEC "/run/firejail/mnt/seccomp.postexec" // filter for post-exec library #define PATH_SECCOMP_DEFAULT (LIBDIR "/firejail/seccomp") // default filter built during make #define PATH_SECCOMP_DEFAULT_DEBUG (LIBDIR "/firejail/seccomp.debug") // default filter built during make #define PATH_SECCOMP_AMD64 (LIBDIR "/firejail/seccomp.amd64") // amd64 filter built during make @@ -305,6 +306,7 @@ extern int arg_overlay_keep; // place overlay diff in a known directory extern int arg_overlay_reuse; // allow the reuse of overlays extern int arg_seccomp; // enable default seccomp filter +extern int arg_seccomp_postexec; // need postexec ld.preload library? extern int arg_caps_default_filter; // enable default capabilities filter extern int arg_caps_drop; // drop list @@ -553,8 +555,6 @@ void caps_drop_dac_override(void); // syscall.c const char *syscall_find_nr(int nr); -// return -1 if error, 0 if no error -int syscall_check_list(const char *slist, void (*callback)(int syscall, int arg), int arg); // fs_trace.c void fs_trace_preload(void); diff --git a/src/firejail/fs_trace.c b/src/firejail/fs_trace.c index df76f4fe1..c87d29b5c 100644 --- a/src/firejail/fs_trace.c +++ b/src/firejail/fs_trace.c @@ -63,6 +63,11 @@ void fs_trace(void) { if (!arg_quiet) printf("Blacklist violations are logged to syslog\n"); } + if (arg_seccomp_postexec) { + fprintf(fp, "%s/libpostexecseccomp.so\n", prefix); + if (!arg_quiet) + printf("Post-exec seccomp protector enabled\n"); + } SET_PERMS_STREAM(fp, 0, 0, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); fclose(fp); diff --git a/src/firejail/main.c b/src/firejail/main.c index 407902676..9726c0b8a 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -56,6 +56,7 @@ int arg_overlay_keep = 0; // place overlay diff in a known directory int arg_overlay_reuse = 0; // allow the reuse of overlays int arg_seccomp = 0; // enable default seccomp filter +int arg_seccomp_postexec = 0; // need postexec ld.preload library? int arg_caps_default_filter = 0; // enable default capabilities filter int arg_caps_drop = 0; // drop list diff --git a/src/firejail/preproc.c b/src/firejail/preproc.c index 9c474415d..583cc4610 100644 --- a/src/firejail/preproc.c +++ b/src/firejail/preproc.c @@ -85,9 +85,12 @@ void preproc_mount_mnt_dir(void) { if (arg_memory_deny_write_execute) copy_file(PATH_SECCOMP_MDWX, RUN_SECCOMP_MDWX, getuid(), getgid(), 0644); // root needed - // as root, create an empty RUN_SECCOMP_PROTOCOL file + // as root, create empty RUN_SECCOMP_PROTOCOL and RUN_SECCOMP_POSTEXEC files create_empty_file_as_root(RUN_SECCOMP_PROTOCOL, 0644); if (set_perms(RUN_SECCOMP_PROTOCOL, getuid(), getgid(), 0644)) errExit("set_perms"); + create_empty_file_as_root(RUN_SECCOMP_POSTEXEC, 0644); + if (set_perms(RUN_SECCOMP_POSTEXEC, getuid(), getgid(), 0644)) + errExit("set_perms"); } } diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 472f09355..568549cbf 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c @@ -664,10 +664,15 @@ int sandbox(void* sandbox_arg) { if (rv) exit(rv); } + if (arg_seccomp && (cfg.seccomp_list || cfg.seccomp_list_drop || cfg.seccomp_list_keep)) + arg_seccomp_postexec = 1; #endif + // need ld.so.preload if tracing or seccomp with any non-default lists + bool need_preload = arg_trace || arg_tracelog || arg_seccomp_postexec; + // trace pre-install - if (arg_trace || arg_tracelog) + if (need_preload) fs_trace_preload(); // store hosts file @@ -704,7 +709,7 @@ int sandbox(void* sandbox_arg) { //**************************** // trace pre-install, this time inside chroot //**************************** - if (arg_trace || arg_tracelog) + if (need_preload) fs_trace_preload(); } else @@ -767,7 +772,7 @@ int sandbox(void* sandbox_arg) { else { fs_private_dir_list("/etc", RUN_ETC_DIR, cfg.etc_private_keep); // create /etc/ld.so.preload file again - if (arg_trace || arg_tracelog) + if (need_preload) fs_trace_preload(); } } @@ -903,7 +908,7 @@ int sandbox(void* sandbox_arg) { //**************************** // install trace //**************************** - if (arg_trace || arg_tracelog) + if (need_preload) fs_trace(); //**************************** diff --git a/src/firejail/seccomp.c b/src/firejail/seccomp.c index 7cbd79d6b..516c97fa0 100644 --- a/src/firejail/seccomp.c +++ b/src/firejail/seccomp.c @@ -145,11 +145,11 @@ int seccomp_filter_drop(int enforce_seccomp) { // build the seccomp filter as a regular user int rv; if (arg_allow_debuggers) - rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 6, - PATH_FSECCOMP, "default", "drop", RUN_SECCOMP_CFG, cfg.seccomp_list, "allow-debuggers"); + rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 7, + PATH_FSECCOMP, "default", "drop", RUN_SECCOMP_CFG, RUN_SECCOMP_POSTEXEC, cfg.seccomp_list, "allow-debuggers"); else - rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 5, - PATH_FSECCOMP, "default", "drop", RUN_SECCOMP_CFG, cfg.seccomp_list); + rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 6, + PATH_FSECCOMP, "default", "drop", RUN_SECCOMP_CFG, RUN_SECCOMP_POSTEXEC, cfg.seccomp_list); if (rv) exit(rv); } @@ -163,11 +163,11 @@ int seccomp_filter_drop(int enforce_seccomp) { // build the seccomp filter as a regular user int rv; if (arg_allow_debuggers) - rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 5, - PATH_FSECCOMP, "drop", RUN_SECCOMP_CFG, cfg.seccomp_list_drop, "allow-debuggers"); + rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 6, + PATH_FSECCOMP, "drop", RUN_SECCOMP_CFG, RUN_SECCOMP_POSTEXEC, cfg.seccomp_list_drop, "allow-debuggers"); else - rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 4, - PATH_FSECCOMP, "drop", RUN_SECCOMP_CFG, cfg.seccomp_list_drop); + rv = sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 5, + PATH_FSECCOMP, "drop", RUN_SECCOMP_CFG, RUN_SECCOMP_POSTEXEC, cfg.seccomp_list_drop); if (rv) exit(rv); @@ -183,9 +183,14 @@ int seccomp_filter_drop(int enforce_seccomp) { exit(1); } - if (arg_debug && access(PATH_FSECCOMP, X_OK) == 0) + if (arg_debug && access(PATH_FSECCOMP, X_OK) == 0) { sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FSECCOMP, "print", RUN_SECCOMP_CFG); + struct stat st; + if (stat(RUN_SECCOMP_POSTEXEC, &st) != -1 && st.st_size != 0) + sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 3, + PATH_FSECCOMP, "print", RUN_SECCOMP_POSTEXEC); + } return 0; } @@ -196,14 +201,19 @@ int seccomp_filter_keep(void) { printf("Build drop seccomp filter\n"); // build the seccomp filter as a regular user - sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 4, - PATH_FSECCOMP, "keep", RUN_SECCOMP_CFG, cfg.seccomp_list_keep); + sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 5, + PATH_FSECCOMP, "keep", RUN_SECCOMP_CFG, RUN_SECCOMP_POSTEXEC, cfg.seccomp_list_keep); if (arg_debug) printf("seccomp filter configured\n"); - if (arg_debug && access(PATH_FSECCOMP, X_OK) == 0) + if (arg_debug && access(PATH_FSECCOMP, X_OK) == 0) { sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FSECCOMP, "print", RUN_SECCOMP_CFG); + struct stat st; + if (stat(RUN_SECCOMP_POSTEXEC, &st) != -1 && st.st_size != 0) + sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FSECCOMP, "print", RUN_SECCOMP_POSTEXEC); + } + return seccomp_load(RUN_SECCOMP_CFG); } diff --git a/src/fseccomp/fseccomp.h b/src/fseccomp/fseccomp.h index 0db670380..144b612ae 100644 --- a/src/fseccomp/fseccomp.h +++ b/src/fseccomp/fseccomp.h @@ -30,8 +30,9 @@ extern int arg_quiet; // syscall.c void syscall_print(void); -int syscall_check_list(const char *slist, void (*callback)(int fd, int syscall, int arg), int fd, int arg); +int syscall_check_list(const char *slist, void (*callback)(int fd, int syscall, int arg, void *ptrarg), int fd, int arg, void *ptrarg); const char *syscall_find_nr(int nr); +void syscalls_in_list(const char *list, const char *slist, int fd, char **prelist, char **postlist); // errno.c void errno_print(void); @@ -49,9 +50,9 @@ void seccomp_secondary_32(const char *fname); // seccomp_file.c void write_to_file(int fd, const void *data, int size); void filter_init(int fd); -void filter_add_whitelist(int fd, int syscall, int arg); -void filter_add_blacklist(int fd, int syscall, int arg); -void filter_add_errno(int fd, int syscall, int arg); +void filter_add_whitelist(int fd, int syscall, int arg, void *ptrarg); +void filter_add_blacklist(int fd, int syscall, int arg, void *ptrarg); +void filter_add_errno(int fd, int syscall, int arg, void *ptrarg); void filter_end_blacklist(int fd); void filter_end_whitelist(int fd); @@ -59,11 +60,11 @@ void filter_end_whitelist(int fd); // default list void seccomp_default(const char *fname, int allow_debuggers); // drop list -void seccomp_drop(const char *fname, char *list, int allow_debuggers); +void seccomp_drop(const char *fname1, const char *fname2, char *list, int allow_debuggers); // default+drop list -void seccomp_default_drop(const char *fname, char *list, int allow_debuggers); +void seccomp_default_drop(const char *fname1, const char *fname2, char *list, int allow_debuggers); // whitelisted filter -void seccomp_keep(const char *fname, char *list); +void seccomp_keep(const char *fname1, const char *fname2, char *list); // block writable and executable memory void memory_deny_write_execute(const char *fname); diff --git a/src/fseccomp/main.c b/src/fseccomp/main.c index 3d95d5bb2..3bf7de0fa 100644 --- a/src/fseccomp/main.c +++ b/src/fseccomp/main.c @@ -30,11 +30,11 @@ static void usage(void) { printf("\tfseccomp secondary 32 file\n"); printf("\tfseccomp default file\n"); printf("\tfseccomp default file allow-debuggers\n"); - printf("\tfseccomp drop file list\n"); - printf("\tfseccomp drop file list allow-debuggers\n"); - printf("\tfseccomp default drop file list\n"); - printf("\tfseccomp default drop file list allow-debuggers\n"); - printf("\tfseccomp keep file list\n"); + printf("\tfseccomp drop file1 file2 list\n"); + printf("\tfseccomp drop file1 file2 list allow-debuggers\n"); + printf("\tfseccomp default drop file1 file2 list\n"); + printf("\tfseccomp default drop file1 file2 list allow-debuggers\n"); + printf("\tfseccomp keep file1 file2 list\n"); printf("\tfseccomp memory-deny-write-execute file\n"); printf("\tfseccomp print file\n"); } @@ -78,16 +78,16 @@ printf("\n"); seccomp_default(argv[2], 0); else if (argc == 4 && strcmp(argv[1], "default") == 0 && strcmp(argv[3], "allow-debuggers") == 0) seccomp_default(argv[2], 1); - else if (argc == 4 && strcmp(argv[1], "drop") == 0) - seccomp_drop(argv[2], argv[3], 0); - else if (argc == 5 && strcmp(argv[1], "drop") == 0 && strcmp(argv[4], "allow-debuggers") == 0) - seccomp_drop(argv[2], argv[3], 1); - else if (argc == 5 && strcmp(argv[1], "default") == 0 && strcmp(argv[2], "drop") == 0) - seccomp_default_drop(argv[3], argv[4], 0); - else if (argc == 6 && strcmp(argv[1], "default") == 0 && strcmp(argv[2], "drop") == 0 && strcmp(argv[5], "allow-debuggers") == 0) - seccomp_default_drop(argv[3], argv[4], 1); - else if (argc == 4 && strcmp(argv[1], "keep") == 0) - seccomp_keep(argv[2], argv[3]); + else if (argc == 5 && strcmp(argv[1], "drop") == 0) + seccomp_drop(argv[2], argv[3], argv[4], 0); + else if (argc == 6 && strcmp(argv[1], "drop") == 0 && strcmp(argv[5], "allow-debuggers") == 0) + seccomp_drop(argv[2], argv[3], argv[4], 1); + else if (argc == 6 && strcmp(argv[1], "default") == 0 && strcmp(argv[2], "drop") == 0) + seccomp_default_drop(argv[3], argv[4], argv[5], 0); + else if (argc == 7 && strcmp(argv[1], "default") == 0 && strcmp(argv[2], "drop") == 0 && strcmp(argv[6], "allow-debuggers") == 0) + seccomp_default_drop(argv[3], argv[4], argv[5], 1); + else if (argc == 5 && strcmp(argv[1], "keep") == 0) + seccomp_keep(argv[2], argv[3], argv[4]); else if (argc == 3 && strcmp(argv[1], "memory-deny-write-execute") == 0) memory_deny_write_execute(argv[2]); else if (argc == 3 && strcmp(argv[1], "print") == 0) diff --git a/src/fseccomp/seccomp.c b/src/fseccomp/seccomp.c index a3db46aad..577f3fdc9 100644 --- a/src/fseccomp/seccomp.c +++ b/src/fseccomp/seccomp.c @@ -27,9 +27,9 @@ static void add_default_list(int fd, int allow_debuggers) { int r; if (!allow_debuggers) - r = syscall_check_list("@default-nodebuggers", filter_add_blacklist, fd, 0); + r = syscall_check_list("@default-nodebuggers", filter_add_blacklist, fd, 0, NULL); else - r = syscall_check_list("@default", filter_add_blacklist, fd, 0); + r = syscall_check_list("@default", filter_add_blacklist, fd, 0, NULL); assert(r == 0); //#ifdef SYS_mknod - emoved in 0.9.29 - it breaks Zotero extension @@ -56,7 +56,7 @@ void seccomp_default(const char *fname, int allow_debuggers) { exit(1); } - // build filter + // build filter (no post-exec filter needed because default list is fine for us) filter_init(fd); add_default_list(fd, allow_debuggers); filter_end_blacklist(fd); @@ -66,44 +66,94 @@ void seccomp_default(const char *fname, int allow_debuggers) { } // drop list -void seccomp_drop(const char *fname, char *list, int allow_debuggers) { - assert(fname); +void seccomp_drop(const char *fname1, const char *fname2, char *list, int allow_debuggers) { + assert(fname1); + assert(fname2); (void) allow_debuggers; // todo: to implemnet it - // open file - int fd = open(fname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + // open file for pre-exec filter + int fd = open(fname1, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { - fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname); + fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname1); exit(1); } - // build filter + // build pre-exec filter: don't blacklist any syscalls in @default-keep + filter_init(fd); + char *prelist, *postlist; + syscalls_in_list(list, "@default-keep", fd, &prelist, &postlist); + if (prelist) + if (syscall_check_list(prelist, filter_add_blacklist, fd, 0, NULL)) { + fprintf(stderr, "Error fseccomp: cannot build seccomp filter\n"); + exit(1); + } + filter_end_whitelist(fd); + // close file + close(fd); + + if (!postlist) + return; + + // open file for post-exec filter + fd = open(fname2, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname2); + exit(1); + } + + // build post-exec filter: blacklist remaining syscalls filter_init(fd); - if (syscall_check_list(list, filter_add_blacklist, fd, 0)) { + if (syscall_check_list(postlist, filter_add_blacklist, fd, 0, NULL)) { fprintf(stderr, "Error fseccomp: cannot build seccomp filter\n"); exit(1); } - filter_end_blacklist(fd); + filter_end_whitelist(fd); // close file close(fd); } // default+drop -void seccomp_default_drop(const char *fname, char *list, int allow_debuggers) { - assert(fname); +void seccomp_default_drop(const char *fname1, const char *fname2, char *list, int allow_debuggers) { + assert(fname1); + assert(fname2); // open file - int fd = open(fname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + int fd = open(fname1, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { - fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname); + fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname1); exit(1); } - // build filter + // build pre-exec filter: blacklist @default, don't blacklist + // any listed syscalls in @default-keep filter_init(fd); add_default_list(fd, allow_debuggers); - if (syscall_check_list(list, filter_add_blacklist, fd, 0)) { + char *prelist, *postlist; + syscalls_in_list(list, "@default-keep", fd, &prelist, &postlist); + if (prelist) + if (syscall_check_list(prelist, filter_add_blacklist, fd, 0, NULL)) { + fprintf(stderr, "Error fseccomp: cannot build seccomp filter\n"); + exit(1); + } + filter_end_blacklist(fd); + + // close file + close(fd); + + if (!postlist) + return; + + // open file for post-exec filter + fd = open(fname2, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname2); + exit(1); + } + + // build post-exec filter: blacklist remaining syscalls + filter_init(fd); + if (syscall_check_list(postlist, filter_add_blacklist, fd, 0, NULL)) { fprintf(stderr, "Error fseccomp: cannot build seccomp filter\n"); exit(1); } @@ -113,22 +163,42 @@ void seccomp_default_drop(const char *fname, char *list, int allow_debuggers) { close(fd); } -void seccomp_keep(const char *fname, char *list) { - // open file - int fd = open(fname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +void seccomp_keep(const char *fname1, const char *fname2, char *list) { + // open file for pre-exec filter + int fd = open(fname1, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { - fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname); + fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname1); exit(1); } - // build filter + // build pre-exec filter: whitelist also @default-keep filter_init(fd); // these syscalls are used by firejail after the seccomp filter is initialized int r; - r = syscall_check_list("@default-keep", filter_add_whitelist, fd, 0); + r = syscall_check_list("@default-keep", filter_add_whitelist, fd, 0, NULL); assert(r == 0); - if (syscall_check_list(list, filter_add_whitelist, fd, 0)) { + if (syscall_check_list(list, filter_add_whitelist, fd, 0, NULL)) { + fprintf(stderr, "Error fseccomp: cannot build seccomp filter\n"); + exit(1); + } + + filter_end_whitelist(fd); + + // close file + close(fd); + + // open file for post-exec filter + fd = open(fname2, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname2); + exit(1); + } + + // build post-exec filter: whitelist without @default-keep + filter_init(fd); + + if (syscall_check_list(list, filter_add_whitelist, fd, 0, NULL)) { fprintf(stderr, "Error fseccomp: cannot build seccomp filter\n"); exit(1); } diff --git a/src/fseccomp/seccomp_file.c b/src/fseccomp/seccomp_file.c index 16ffd5302..2d5ee115d 100644 --- a/src/fseccomp/seccomp_file.c +++ b/src/fseccomp/seccomp_file.c @@ -60,8 +60,9 @@ void filter_init(int fd) { write_to_file(fd, filter, sizeof(filter)); } -void filter_add_whitelist(int fd, int syscall, int arg) { +void filter_add_whitelist(int fd, int syscall, int arg, void *ptrarg) { (void) arg; + (void) ptrarg; struct sock_filter filter[] = { WHITELIST(syscall) @@ -69,8 +70,9 @@ void filter_add_whitelist(int fd, int syscall, int arg) { write_to_file(fd, filter, sizeof(filter)); } -void filter_add_blacklist(int fd, int syscall, int arg) { +void filter_add_blacklist(int fd, int syscall, int arg, void *ptrarg) { (void) arg; + (void) ptrarg; struct sock_filter filter[] = { BLACKLIST(syscall) @@ -78,7 +80,8 @@ void filter_add_blacklist(int fd, int syscall, int arg) { write_to_file(fd, filter, sizeof(filter)); } -void filter_add_errno(int fd, int syscall, int arg) { +void filter_add_errno(int fd, int syscall, int arg, void *ptrarg) { + (void) ptrarg; struct sock_filter filter[] = { BLACKLIST_ERRNO(syscall, arg) }; diff --git a/src/fseccomp/syscall.c b/src/fseccomp/syscall.c index 5893a2ea8..b9e6d995b 100644 --- a/src/fseccomp/syscall.c +++ b/src/fseccomp/syscall.c @@ -17,7 +17,9 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _GNU_SOURCE #include "fseccomp.h" +#include #include typedef struct { @@ -30,6 +32,13 @@ typedef struct { const char * const list; } SyscallGroupList; +typedef struct { + const char *slist; + char *prelist, *postlist; + bool found; + int syscall; +} SyscallCheckList; + static const SyscallEntry syslist[] = { // // code generated using tools/extract-syscall @@ -174,6 +183,7 @@ static const SyscallGroupList sysgroups[] = { }, { .name = "@default-keep", .list = "dup," + "execve," "prctl," "setgid," "setgroups," @@ -449,7 +459,7 @@ error: } // return 1 if error, 0 if OK -int syscall_check_list(const char *slist, void (*callback)(int fd, int syscall, int arg), int fd, int arg) { +int syscall_check_list(const char *slist, void (*callback)(int fd, int syscall, int arg, void *ptrarg), int fd, int arg, void *ptrarg) { // don't allow empty lists if (slist == NULL || *slist == '\0') { fprintf(stderr, "Error fseccomp: empty syscall lists are not allowed\n"); @@ -477,7 +487,7 @@ int syscall_check_list(const char *slist, void (*callback)(int fd, int syscall, fprintf(stderr, "Error fseccomp: unknown syscall group %s\n", ptr); exit(1); } - syscall_check_list(new_list, callback, fd, arg); + syscall_check_list(new_list, callback, fd, arg, ptrarg); } else { syscall_process_name(ptr, &syscall_nr, &error_nr); @@ -487,9 +497,9 @@ int syscall_check_list(const char *slist, void (*callback)(int fd, int syscall, } else if (callback != NULL) { if (error_nr != -1) - filter_add_errno(fd, syscall_nr, error_nr); + filter_add_errno(fd, syscall_nr, error_nr, ptrarg); else - callback(fd, syscall_nr, arg); + callback(fd, syscall_nr, arg, ptrarg); } } ptr = strtok_r(NULL, ",", &saveptr); @@ -498,3 +508,49 @@ int syscall_check_list(const char *slist, void (*callback)(int fd, int syscall, free(str); return 0; } + +static void find_syscall(int fd, int syscall, int arg, void *ptrarg) { + (void)fd; + SyscallCheckList *ptr = ptrarg; + if (syscall == ptr->syscall) + ptr->found = true; +} + +// go through list2 and find matches for problem syscall +static void syscall_in_list(int fd, int syscall, int arg, void *ptrarg) { + (void)arg; + SyscallCheckList *ptr = ptrarg; + SyscallCheckList sl; + sl.found = false; + sl.syscall = syscall; + syscall_check_list(ptr->slist, find_syscall, fd, 0, &sl); + // if found in the problem list, add to post-exec list + if (sl.found) + if (ptr->postlist) { + if (asprintf(&ptr->postlist, "%s,%s", ptr->postlist, syscall_find_nr(syscall)) == -1) + errExit("asprintf"); + } + else + ptr->postlist = strdup(syscall_find_nr(syscall)); + else // no problem, add to pre-exec list + if (ptr->prelist) { + if (asprintf(&ptr->prelist, "%s,%s", ptr->prelist, syscall_find_nr(syscall)) == -1) + errExit("asprintf"); + } + else + ptr->prelist = strdup(syscall_find_nr(syscall)); +} + +// go through list and find matches for syscalls in list @default-keep +void syscalls_in_list(const char *list, const char *slist, int fd, char **prelist, char **postlist) { + SyscallCheckList sl; + // these syscalls are used by firejail after the seccomp filter is initialized + sl.slist = slist; + sl.prelist = NULL; + sl.postlist = NULL; + syscall_check_list(list, syscall_in_list, 0, 0, &sl); + if (!arg_quiet) + printf("list in: %s, check list: %s prelist: %s, postlist: %s\n", list, sl.slist, sl.prelist, sl.postlist); + *prelist = sl.prelist; + *postlist = sl.postlist; +} diff --git a/src/libpostexecseccomp/Makefile.in b/src/libpostexecseccomp/Makefile.in new file mode 100644 index 000000000..92803342c --- /dev/null +++ b/src/libpostexecseccomp/Makefile.in @@ -0,0 +1,26 @@ +CC=@CC@ +PREFIX=@prefix@ +VERSION=@PACKAGE_VERSION@ +NAME=@PACKAGE_NAME@ +HAVE_FATAL_WARNINGS=@HAVE_FATAL_WARNINGS@ + +H_FILE_LIST = $(sort $(wildcard *.[h])) +C_FILE_LIST = $(sort $(wildcard *.c)) +OBJS = $(C_FILE_LIST:.c=.o) +BINOBJS = $(foreach file, $(OBJS), $file) +CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -O2 -DVERSION='"$(VERSION)"' -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIC -Wformat -Wformat-security +LDFLAGS += -pie -Wl,-z,relro -Wl,-z,now + +all: libpostexecseccomp.so + +%.o : %.c $(H_FILE_LIST) + $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ + +libpostexecseccomp.so: $(OBJS) + $(CC) $(LDFLAGS) -shared -fPIC -z relro -o $@ $(OBJS) -ldl + + +clean:; rm -f $(OBJS) libpostexecseccomp.so + +distclean: clean + rm -fr Makefile diff --git a/src/libpostexecseccomp/libpostexecseccomp.c b/src/libpostexecseccomp/libpostexecseccomp.c new file mode 100644 index 000000000..801f968a6 --- /dev/null +++ b/src/libpostexecseccomp/libpostexecseccomp.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 Firejail Authors + * + * This file is part of firejail project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "libpostexecseccomp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +__attribute__((constructor)) +static void load_seccomp(void) { + int fd = open(RUN_SECCOMP_POSTEXEC, O_RDONLY); + if (fd == -1) + return; + + int size = lseek(fd, 0, SEEK_END); + unsigned short entries = (unsigned short) size / (unsigned short) sizeof(struct sock_filter); + struct sock_filter *filter = MAP_FAILED; + if (size != 0) + filter = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + + close(fd); + + if (size == 0 || filter == MAP_FAILED) + return; + + // install filter + struct sock_fprog prog = { + .len = entries, + .filter = filter, + }; + + prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); + munmap(filter, size); +} diff --git a/src/libpostexecseccomp/libpostexecseccomp.h b/src/libpostexecseccomp/libpostexecseccomp.h new file mode 100644 index 000000000..c4aca540a --- /dev/null +++ b/src/libpostexecseccomp/libpostexecseccomp.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Firejail Authors + * + * This file is part of firejail project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef LIBPOSTEXECSECCOMP_H +#define LIBPOSTEXECSECCOMP_H + +#define RUN_SECCOMP_POSTEXEC "/run/firejail/mnt/seccomp.postexec" + +#endif diff --git a/src/man/firejail.txt b/src/man/firejail.txt index 2c8dca09a..be73429bc 100644 --- a/src/man/firejail.txt +++ b/src/man/firejail.txt @@ -1578,6 +1578,32 @@ $ rm testfile rm: cannot remove `testfile': Operation not permitted .br +.br +If the blocked system calls would also block Firejail from operating, +they are handled by adding a preloaded library which performs seccomp +system calls later. +.br + +.br +Example: +.br + +.br +$ firejail \-\-noprofile \-\-shell=none \-\-seccomp=execve bash +.br +Parent pid 32751, child pid 32752 +.br +Post-exec seccomp protector enabled +.br +list in: execve, check list: @default-keep prelist: (null), postlist: execve +.br +Child process initialized in 46.44 ms +.br +$ ls +.br +Bad system call +.br + .TP \fB\-\-seccomp.drop=syscall,syscall,syscall Enable seccomp filter, and blacklist the syscalls specified by the command. -- cgit v1.2.3-54-g00ecf