From d01216de45884300c87e7d3ccb70e53ebb461449 Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Sat, 19 Aug 2017 23:22:38 +0300 Subject: Feature: switch/config option to block secondary architectures Add a feature for a new (opt-in) command line switch and config file option to block secondary architectures entirely. Also block changing Linux execution domain with personality() system call for the primary architecture. Closes #1479 --- .gitignore | 1 + Makefile.in | 6 ++- src/firejail/firejail.h | 5 ++- src/firejail/main.c | 12 +++++ src/firejail/preproc.c | 10 +++-- src/firejail/profile.c | 10 +++++ src/firejail/seccomp.c | 35 ++++++++++++--- src/fseccomp/fseccomp.h | 1 + src/fseccomp/main.c | 3 ++ src/fseccomp/seccomp_print.c | 19 ++++++++ src/fseccomp/seccomp_secondary.c | 97 ++++++++++++++++++++++++++-------------- src/include/seccomp.h | 9 ++++ src/man/firejail-profile.txt | 4 ++ src/man/firejail.txt | 15 +++++-- 14 files changed, 178 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index 3ed76f776..30793847c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ seccomp seccomp.debug seccomp.i386 seccomp.amd64 +seccomp.block_secondary seccomp.mdwx diff --git a/Makefile.in b/Makefile.in index af30d860e..442766e27 100644 --- a/Makefile.in +++ b/Makefile.in @@ -2,7 +2,7 @@ all: apps man filters MYLIBS = src/lib APPS = src/firejail src/firemon src/firecfg src/libtrace src/libtracelog src/ftee src/faudit src/fnet src/fseccomp src/fcopy src/fldd src/libpostexecseccomp MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 -SECCOMP_FILTERS = seccomp seccomp.i386 seccomp.amd64 +SECCOMP_FILTERS = seccomp seccomp.debug seccomp.i386 seccomp.amd64 seccomp.block_secondary seccomp.mwdx prefix=@prefix@ exec_prefix=@exec_prefix@ @@ -45,6 +45,7 @@ ifeq ($(HAVE_SECCOMP),-DHAVE_SECCOMP) src/fseccomp/fseccomp default seccomp.debug allow-debuggers src/fseccomp/fseccomp secondary 32 seccomp.i386 src/fseccomp/fseccomp secondary 64 seccomp.amd64 + src/fseccomp/fseccomp secondary block seccomp.block_secondary src/fseccomp/fseccomp memory-deny-write-execute seccomp.mdwx endif @@ -53,7 +54,7 @@ clean: $(MAKE) -C $$dir clean; \ done rm -f $(MANPAGES) $(MANPAGES:%=%.gz) firejail*.rpm - rm -f seccomp seccomp.debug seccomp.i386 seccomp.amd64 seccomp.mdwx + rm -f $(SECCOMP_FILTERS) rm -f test/utils/index.html* rm -f test/utils/wget-log rm -f test/utils/lstesting @@ -104,6 +105,7 @@ ifeq ($(HAVE_SECCOMP),-DHAVE_SECCOMP) install -c -m 0644 seccomp.debug $(DESTDIR)/$(libdir)/firejail/. install -c -m 0644 seccomp.i386 $(DESTDIR)/$(libdir)/firejail/. install -c -m 0644 seccomp.amd64 $(DESTDIR)/$(libdir)/firejail/. + install -c -m 0644 seccomp.block_secondary $(DESTDIR)/$(libdir)/firejail/. install -c -m 0644 seccomp.mdwx $(DESTDIR)/$(libdir)/firejail/. endif ifeq ($(HAVE_CONTRIB_INSTALL),yes) diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 90f88ef37..71c5ae87c 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -57,12 +57,14 @@ #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_BLOCK_SECONDARY "/run/firejail/mnt/seccomp.block_secondary" // secondary arch blocking filter #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 #define PATH_SECCOMP_I386 (LIBDIR "/firejail/seccomp.i386") // i386 filter built during make #define PATH_SECCOMP_MDWX (LIBDIR "/firejail/seccomp.mdwx") // filter for memory-deny-write-execute built during make +#define PATH_SECCOMP_BLOCK_SECONDARY (LIBDIR "/firejail/seccomp.block_secondary") // secondary arch blocking filter built during make #define RUN_DEV_DIR "/run/firejail/mnt/dev" @@ -307,6 +309,7 @@ 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_seccomp_block_secondary; // block any secondary architectures extern int arg_caps_default_filter; // enable default capabilities filter extern int arg_caps_drop; // drop list @@ -538,8 +541,6 @@ void fs_private_home_list(void); char *seccomp_check_list(const char *str); int seccomp_install_filters(void); int seccomp_load(const char *fname); -void seccomp_filter_32(void); -void seccomp_filter_64(void); int seccomp_filter_drop(int enforce_seccomp); int seccomp_filter_keep(void); void seccomp_print_filter(pid_t pid); diff --git a/src/firejail/main.c b/src/firejail/main.c index 71a37beb7..3f805a7e0 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -57,6 +57,7 @@ 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_seccomp_block_secondary = 0; // block any secondary architectures int arg_caps_default_filter = 0; // enable default capabilities filter int arg_caps_drop = 0; // drop list @@ -1147,6 +1148,13 @@ int main(int argc, char **argv) { else exit_err_feature("seccomp"); } + else if (strcmp(argv[i], "--seccomp.block-secondary") == 0) { + if (checkcfg(CFG_SECCOMP)) { + arg_seccomp_block_secondary = 1; + } + else + exit_err_feature("seccomp"); + } else if (strcmp(argv[i], "--memory-deny-write-execute") == 0) { if (checkcfg(CFG_SECCOMP)) arg_memory_deny_write_execute = 1; @@ -2239,6 +2247,10 @@ int main(int argc, char **argv) { } } + // enable seccomp if only seccomp.block-secondary was specified + if (arg_seccomp_block_secondary) + arg_seccomp = 1; + // log command logargs(argc, argv); if (fullargc) { diff --git a/src/firejail/preproc.c b/src/firejail/preproc.c index 583cc4610..bf1ef0469 100644 --- a/src/firejail/preproc.c +++ b/src/firejail/preproc.c @@ -75,9 +75,13 @@ void preproc_mount_mnt_dir(void) { tmpfs_mounted = 1; fs_logger2("tmpfs", RUN_MNT_DIR); - //copy defaultl seccomp files - copy_file(PATH_SECCOMP_I386, RUN_SECCOMP_I386, getuid(), getgid(), 0644); // root needed - copy_file(PATH_SECCOMP_AMD64, RUN_SECCOMP_AMD64, getuid(), getgid(), 0644); // root needed + if (arg_seccomp_block_secondary) + copy_file(PATH_SECCOMP_BLOCK_SECONDARY, RUN_SECCOMP_BLOCK_SECONDARY, getuid(), getgid(), 0644); // root needed + else { + //copy default seccomp files + copy_file(PATH_SECCOMP_I386, RUN_SECCOMP_I386, getuid(), getgid(), 0644); // root needed + copy_file(PATH_SECCOMP_AMD64, RUN_SECCOMP_AMD64, getuid(), getgid(), 0644); // root needed + } if (arg_allow_debuggers) copy_file(PATH_SECCOMP_DEFAULT_DEBUG, RUN_SECCOMP_CFG, getuid(), getgid(), 0644); // root needed else diff --git a/src/firejail/profile.c b/src/firejail/profile.c index 7753ee3b2..fc390c83a 100644 --- a/src/firejail/profile.c +++ b/src/firejail/profile.c @@ -577,6 +577,16 @@ int profile_check_line(char *ptr, int lineno, const char *fname) { return 0; } + if (strcmp(ptr, "seccomp.block-secondary") == 0) { +#ifdef HAVE_SECCOMP + if (checkcfg(CFG_SECCOMP)) { + arg_seccomp_block_secondary = 1; + } + else + warning_feature_disabled("seccomp"); +#endif + return 0; + } // seccomp drop list without default list if (strncmp(ptr, "seccomp.drop ", 13) == 0) { #ifdef HAVE_SECCOMP diff --git a/src/firejail/seccomp.c b/src/firejail/seccomp.c index e855ce7ed..aaf53b2a1 100644 --- a/src/firejail/seccomp.c +++ b/src/firejail/seccomp.c @@ -118,7 +118,7 @@ errexit: } // i386 filter installed on amd64 architectures -void seccomp_filter_32(void) { +static void seccomp_filter_32(void) { if (seccomp_load(RUN_SECCOMP_I386) == 0) { if (arg_debug) printf("Dual i386/amd64 seccomp filter configured\n"); @@ -126,13 +126,20 @@ void seccomp_filter_32(void) { } // amd64 filter installed on i386 architectures -void seccomp_filter_64(void) { +static void seccomp_filter_64(void) { if (seccomp_load(RUN_SECCOMP_AMD64) == 0) { if (arg_debug) printf("Dual i386/amd64 seccomp filter configured\n"); } } +static void seccomp_filter_block_secondary(void) { + if (seccomp_load(RUN_SECCOMP_BLOCK_SECONDARY) == 0) { + if (arg_debug) + printf("Secondary arch blocking seccomp filter configured\n"); + } +} + // drop filter for seccomp option int seccomp_filter_drop(int enforce_seccomp) { // if we have multiple seccomp commands, only one of them is executed @@ -143,21 +150,29 @@ int seccomp_filter_drop(int enforce_seccomp) { if (cfg.seccomp_list_drop == NULL) { // default seccomp if (cfg.seccomp_list == NULL) { + if (arg_seccomp_block_secondary) + seccomp_filter_block_secondary(); + else { #if defined(__x86_64__) - seccomp_filter_32(); + seccomp_filter_32(); #endif #if defined(__i386__) - seccomp_filter_64(); + seccomp_filter_64(); #endif + } } // default seccomp filter with additional drop list else { // cfg.seccomp_list != NULL + if (arg_seccomp_block_secondary) + seccomp_filter_block_secondary(); + else { #if defined(__x86_64__) - seccomp_filter_32(); + seccomp_filter_32(); #endif #if defined(__i386__) - seccomp_filter_64(); + seccomp_filter_64(); #endif + } if (arg_debug) printf("Build default+drop seccomp filter\n"); @@ -175,7 +190,10 @@ int seccomp_filter_drop(int enforce_seccomp) { } // drop list without defaults - secondary filters are not installed + // except when secondary architectures are explicitly blocked else { // cfg.seccomp_list_drop != NULL + if (arg_seccomp_block_secondary) + seccomp_filter_block_secondary(); if (arg_debug) printf("Build drop seccomp filter\n"); @@ -216,6 +234,11 @@ int seccomp_filter_drop(int enforce_seccomp) { // keep filter for seccomp option int seccomp_filter_keep(void) { + // secondary filters are not installed except when secondary + // architectures are explicitly blocked + if (arg_seccomp_block_secondary) + seccomp_filter_block_secondary(); + if (arg_debug) printf("Build drop seccomp filter\n"); diff --git a/src/fseccomp/fseccomp.h b/src/fseccomp/fseccomp.h index 144b612ae..2deb282f5 100644 --- a/src/fseccomp/fseccomp.h +++ b/src/fseccomp/fseccomp.h @@ -46,6 +46,7 @@ void protocol_build_filter(const char *prlist, const char *fname); // seccomp_secondary.c void seccomp_secondary_64(const char *fname); void seccomp_secondary_32(const char *fname); +void seccomp_secondary_block(const char *fname); // seccomp_file.c void write_to_file(int fd, const void *data, int size); diff --git a/src/fseccomp/main.c b/src/fseccomp/main.c index 3bf7de0fa..ae0ae64ef 100644 --- a/src/fseccomp/main.c +++ b/src/fseccomp/main.c @@ -28,6 +28,7 @@ static void usage(void) { printf("\tfseccomp protocol build list file\n"); printf("\tfseccomp secondary 64 file\n"); printf("\tfseccomp secondary 32 file\n"); + printf("\tfseccomp secondary block file\n"); printf("\tfseccomp default file\n"); printf("\tfseccomp default file allow-debuggers\n"); printf("\tfseccomp drop file1 file2 list\n"); @@ -74,6 +75,8 @@ printf("\n"); seccomp_secondary_64(argv[3]); else if (argc == 4 && strcmp(argv[1], "secondary") == 0 && strcmp(argv[2], "32") == 0) seccomp_secondary_32(argv[3]); + else if (argc == 4 && strcmp(argv[1], "secondary") == 0 && strcmp(argv[2], "block") == 0) + seccomp_secondary_block(argv[3]); else if (argc == 3 && strcmp(argv[1], "default") == 0) seccomp_default(argv[2], 0); else if (argc == 4 && strcmp(argv[1], "default") == 0 && strcmp(argv[3], "allow-debuggers") == 0) diff --git a/src/fseccomp/seccomp_print.c b/src/fseccomp/seccomp_print.c index 19fe7a545..7af95d51c 100644 --- a/src/fseccomp/seccomp_print.c +++ b/src/fseccomp/seccomp_print.c @@ -113,6 +113,25 @@ static int detect_filter_type(void) { printf(" EXAMINE_SYSCALL\n"); return sizeof(start_secondary_32) / sizeof(struct sock_filter); } + + const struct sock_filter start_secondary_block[] = { + VALIDATE_ARCHITECTURE_KILL, +#if defined(__x86_64__) + EXAMINE_SYSCALL, + HANDLE_X32_KILL, +#else + EXAMINE_SYSCALL +#endif + }; + + if (memcmp(&start_secondary_block[0], filter, sizeof(start_secondary_block)) == 0) { + printf(" VALIDATE_ARCHITECTURE_KILL\n"); + printf(" EXAMINE_SYSCALL\n"); +#if defined(__x86_64__) + printf(" HANDLE_X32_KILL\n"); +#endif + return sizeof(start_secondary_block) / sizeof(struct sock_filter); + } return 0; // filter unrecognized } diff --git a/src/fseccomp/seccomp_secondary.c b/src/fseccomp/seccomp_secondary.c index fceb2c3ec..dd69b58cc 100644 --- a/src/fseccomp/seccomp_secondary.c +++ b/src/fseccomp/seccomp_secondary.c @@ -19,8 +19,29 @@ */ #include "fseccomp.h" #include "../include/seccomp.h" +#include #include +static void write_filter(const char *fname, size_t size, const void *filter) { + // save filter to file + int dst = open(fname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (dst < 0) { + fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname); + exit(1); + } + + size_t written = 0; + while (written < size) { + ssize_t rv = write(dst, (unsigned char *) filter + written, size - written); + if (rv == -1) { + fprintf(stderr, "Error fseccomp: cannot write %s file\n", fname); + exit(1); + } + written += rv; + } + close(dst); +} + void seccomp_secondary_64(const char *fname) { // hardcoded syscall values struct sock_filter filter[] = { @@ -84,23 +105,7 @@ void seccomp_secondary_64(const char *fname) { }; // save filter to file - int dst = open(fname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (dst < 0) { - fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname); - exit(1); - } - - int size = (int) sizeof(filter); - int written = 0; - while (written < size) { - int rv = write(dst, (unsigned char *) filter + written, size - written); - if (rv == -1) { - fprintf(stderr, "Error fseccomp: cannot write %s file\n", fname); - exit(1); - } - written += rv; - } - close(dst); + write_filter(fname, sizeof(filter), filter); } // i386 filter installed on amd64 architectures @@ -166,21 +171,47 @@ void seccomp_secondary_32(const char *fname) { }; // save filter to file - int dst = open(fname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (dst < 0) { - fprintf(stderr, "Error fseccomp: cannot open %s file\n", fname); - exit(1); - } + write_filter(fname, sizeof(filter), filter); +} - int size = (int) sizeof(filter); - int written = 0; - while (written < size) { - int rv = write(dst, (unsigned char *) filter + written, size - written); - if (rv == -1) { - fprintf(stderr, "Error fseccomp: cannot write %s file\n", fname); - exit(1); - } - written += rv; - } - close(dst); +#define jmp_from_to(from_addr, to_addr) ((to_addr) - (from_addr) - 1) + +#if __BYTE_ORDER == __BIG_ENDIAN +#define MSW 0 +#define LSW (sizeof(int)) +#else +#define MSW (sizeof(int)) +#define LSW 0 +#endif + +void seccomp_secondary_block(const char *fname) { + struct sock_filter filter[] = { + // block other architectures + VALIDATE_ARCHITECTURE_KILL, + EXAMINE_SYSCALL, +#if defined(__x86_64__) + // block x32 + HANDLE_X32_KILL, +#endif + // block personality(2) where domain != PER_LINUX or 0xffffffff (query current personality) + // 0: if personality(2), continue to 1, else goto 7 (allow) + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SYS_personality, 0, jmp_from_to(0, 7)), + // 1: get LSW of system call argument 0 + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, args[0])) + LSW), + // 2: if LSW(arg0) == PER_LINUX, goto step 4, else continue to 3 + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, PER_LINUX, jmp_from_to(2, 4), 0), + // 3: if LSW(arg0) == 0xffffffff, continue to 4, else goto 6 (kill) + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 0, jmp_from_to(3, 6)), + // 4: get MSW of system call argument 0 + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, args[0])) + MSW), + // 5: if MSW(arg0) == 0, goto 7 (allow) else continue to 6 (kill) + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, jmp_from_to(5, 7), 0), + // 6: + KILL_PROCESS, + // 7: + RETURN_ALLOW + }; + + // save filter to file + write_filter(fname, sizeof(filter), filter); } diff --git a/src/include/seccomp.h b/src/include/seccomp.h index b1a19a9b6..2f2b2384d 100644 --- a/src/include/seccomp.h +++ b/src/include/seccomp.h @@ -105,6 +105,11 @@ struct seccomp_data { BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) +#define VALIDATE_ARCHITECTURE_KILL \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, arch))), \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) + #define VALIDATE_ARCHITECTURE_64 \ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, arch))), \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, AUDIT_ARCH_X86_64, 1, 0), \ @@ -122,6 +127,10 @@ struct seccomp_data { BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, X32_SYSCALL_BIT, 1, 0), \ BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, 0, 1, 0), \ RETURN_ERRNO(EPERM) +#define HANDLE_X32_KILL \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, X32_SYSCALL_BIT, 1, 0), \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, 0, 1, 0), \ + KILL_PROCESS #endif #define EXAMINE_SYSCALL BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \ diff --git a/src/man/firejail-profile.txt b/src/man/firejail-profile.txt index 2a7d926b9..050c3d7e5 100644 --- a/src/man/firejail-profile.txt +++ b/src/man/firejail-profile.txt @@ -310,6 +310,10 @@ Enable seccomp filter and blacklist the syscalls in the default list. See man 1 \fBseccomp syscall,syscall,syscall Enable seccomp filter and blacklist the system calls in the list on top of default seccomp filter. .TP +\fBseccomp.block-secondary +Enable seccomp filter and filter system call architectures +so that only the native architecture is allowed. +.TP \fBseccomp.drop syscall,syscall,syscall Enable seccomp filter and blacklist the system calls in the list. .TP diff --git a/src/man/firejail.txt b/src/man/firejail.txt index 89b815e02..d1970c985 100644 --- a/src/man/firejail.txt +++ b/src/man/firejail.txt @@ -1572,9 +1572,10 @@ system call can be specified by its number instead of name with prefix $, so for example $165 would be equal to mount on i386. .br -System architecture is not strictly imposed. The filter is applied -at run time only if the correct architecture was detected. For the case of I386 and AMD64 -both 32-bit and 64-bit filters are installed. +System architecture is strictly imposed only if flag +\-\-seccomp.block_secondary is used. The filter is applied at run time +only if the correct architecture was detected. For the case of I386 +and AMD64 both 32-bit and 64-bit filters are installed. .br .br @@ -1645,6 +1646,14 @@ $ ls Bad system call .br +.TP +\fB\-\-seccomp.block_secondary +Enable seccomp filter and filter system call architectures so that +only the native architecture is allowed. For example, on amd64, i386 +and x32 system calls are blocked as well as changing the execution +domain with personality(2) 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