diff --git a/README b/README index dbeaf27..d60e181 100644 --- a/README +++ b/README @@ -13,6 +13,7 @@ check (for tests) intltool libmagic from file(1) (optional, for mime-type detection) libsynctex from TeXLive (optional, for SyncTeX support) +libseccomp (optional, for sandbox support) Sphinx (optional, for manpages and HTML documentation) doxygen (optional, for HTML documentation) breathe (optional, for HTML documentation) @@ -33,6 +34,9 @@ WITH_SQLITE=0 and sqlite support won't be available. The use of magic to detect mime types is optional and can be disabled by setting WITH_MAGIC=0. +The use of seccomp to create a sandboxed environment is optional and can be disabled by setting +WITH_SECCOMP=0. + If you pass these flags as a command line argument to make, you have to ensure to pass the same flags when executing the install target. diff --git a/config.mk b/config.mk index 43acb9d..74a83fb 100644 --- a/config.mk +++ b/config.mk @@ -47,9 +47,9 @@ WITH_SYNCTEX ?= $(shell (${PKG_CONFIG} synctex && echo 1) || echo 0) # To disable support for mimetype detction with libmagic set WITH_MAGIC to 0. WITH_MAGIC ?= 1 -# seccomp -# To enable support for seccomp filter set WITH_SECCOMP to 1. -WITH_SECCOMP ?= 0 +# seccomp sandbox +# To disable support for seccomp filter set WITH_SECCOMP to 0. +WITH_SECCOMP ?= 1 # paths PREFIX ?= /usr diff --git a/zathura/config.c b/zathura/config.c index d3fe43a..e0e06d2 100644 --- a/zathura/config.c +++ b/zathura/config.c @@ -185,6 +185,8 @@ config_load_default(zathura_t* zathura) girara_setting_add(gsession, "index-active-fg", "#232323", STRING, true, _("Index mode foreground color (active element)"), NULL, NULL); girara_setting_add(gsession, "index-active-bg", "#9FBC00", STRING, true, _("Index mode background color (active element)"), NULL, NULL); + girara_setting_add(gsession, "sandbox", "normal", STRING, true, _("Sandbox level"), NULL, NULL); + bool_value = false; girara_setting_add(gsession, "recolor", &bool_value, BOOLEAN, false, _("Recolor pages"), cb_setting_recolor_change, NULL); bool_value = false; diff --git a/zathura/libsec.c b/zathura/libsec.c index 47b1a13..8bb212d 100644 --- a/zathura/libsec.c +++ b/zathura/libsec.c @@ -13,7 +13,7 @@ #define DENY_RULE(call) { if (seccomp_rule_add (ctx, SCMP_ACT_KILL, SCMP_SYS(call), 0) < 0) goto out; } #define ALLOW_RULE(call) { if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(call), 0) < 0) goto out; } -int seccomp_enable_protected_mode(void){ +int seccomp_enable_basic_filter(void){ scmp_filter_ctx ctx; @@ -87,274 +87,10 @@ int seccomp_enable_protected_mode(void){ DENY_RULE (umount2); DENY_RULE (uselib); DENY_RULE (vmsplice); - - /* applying filter... */ - if (seccomp_load (ctx) >= 0){ - /* free ctx after the filter has been loaded into the kernel */ - seccomp_release(ctx); - return 0; - } - out: - /* something went wrong */ - seccomp_release(ctx); - return 1; -} - - -int seccomp_enable_protected_view(void){ - - scmp_filter_ctx ctx; - - /* prevent child processes from getting more priv e.g. via setuid, capabilities, ... */ - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { - perror("prctl SET_NO_NEW_PRIVS"); - exit(EXIT_FAILURE); - } - - /* prevent escape via ptrace */ - if(prctl (PR_SET_DUMPABLE, 0, 0, 0, 0)){ - perror("prctl PR_SET_DUMPABLE"); - exit(EXIT_FAILURE); - } - - /* initialize the filter */ - ctx = seccomp_init(SCMP_ACT_KILL); - if (ctx == NULL){ - perror("seccomp_init failed"); - exit(EXIT_FAILURE); - } - - ALLOW_RULE (access); - ALLOW_RULE (bind); - ALLOW_RULE (brk); - ALLOW_RULE (clock_getres); - ALLOW_RULE (clone); - ALLOW_RULE (close); - ALLOW_RULE (connect); - ALLOW_RULE (eventfd2); - ALLOW_RULE (exit); - ALLOW_RULE (exit_group); - ALLOW_RULE (fadvise64); - ALLOW_RULE (fallocate); - ALLOW_RULE (fcntl); /* TODO: build detailed filter */ - ALLOW_RULE (fstat); - ALLOW_RULE (fstatfs); - ALLOW_RULE (ftruncate); - ALLOW_RULE (futex); - ALLOW_RULE (getdents); - ALLOW_RULE (getegid); - ALLOW_RULE (geteuid); - ALLOW_RULE (getgid); - ALLOW_RULE (getuid); - ALLOW_RULE (getpid); - ALLOW_RULE (getppid); - ALLOW_RULE (getpgrp); - ALLOW_RULE (getpeername); - ALLOW_RULE (getrandom); - ALLOW_RULE (getresgid); - ALLOW_RULE (getresuid); - ALLOW_RULE (getrlimit); - ALLOW_RULE (getsockname); - ALLOW_RULE (getsockopt); /* needed for access to x11 socket in network namespace (without abstract sockets) */ - ALLOW_RULE (inotify_add_watch); - ALLOW_RULE (inotify_init1); - ALLOW_RULE (inotify_rm_watch); - /* ALLOW_RULE (ioctl); specified below */ - ALLOW_RULE (lseek); - ALLOW_RULE (lstat); - ALLOW_RULE (madvise); - ALLOW_RULE (memfd_create); - ALLOW_RULE (mkdir); /* needed for first run only */ - ALLOW_RULE (mmap); - ALLOW_RULE (mprotect); - ALLOW_RULE (mremap); - ALLOW_RULE (munmap); - ALLOW_RULE (open); /* (zathura needs to open for writing) TODO: avoid needing this somehow */ - ALLOW_RULE (openat); - ALLOW_RULE (pipe); - ALLOW_RULE (poll); - ALLOW_RULE (pwrite64); /* TODO: build detailed filter */ - ALLOW_RULE (pread64); - ALLOW_RULE (prlimit64); - ALLOW_RULE (prctl); /* NOT specified below */ - ALLOW_RULE (read); - ALLOW_RULE (readlink); - ALLOW_RULE (recvfrom); - ALLOW_RULE (recvmsg); - ALLOW_RULE (restart_syscall); - ALLOW_RULE (rt_sigaction); - ALLOW_RULE (rt_sigprocmask); - ALLOW_RULE (seccomp); - ALLOW_RULE (sendmsg); - ALLOW_RULE (sendto); - ALLOW_RULE (select); - ALLOW_RULE (set_robust_list); - ALLOW_RULE (setsockopt); - ALLOW_RULE (shmat); - ALLOW_RULE (shmctl); - ALLOW_RULE (shmdt); - ALLOW_RULE (shmget); - ALLOW_RULE (shutdown); - ALLOW_RULE (stat); - ALLOW_RULE (statfs); - /* ALLOW_RULE (socket); specified below */ - ALLOW_RULE (sysinfo); - ALLOW_RULE (uname); - ALLOW_RULE (unlink); - ALLOW_RULE (write); /* specified below (zathura needs to write files)*/ - ALLOW_RULE (writev); - ALLOW_RULE (wait4); /* trying to open links should not crash the app */ - - /* allowed for use with container */ - - ALLOW_RULE (chmod); - ALLOW_RULE (link); - ALLOW_RULE (rename); - - /* allowed for debugging: */ - - /* ALLOW_RULE (prctl); */ - /* ALLOW_RULE (ioctl); */ - - - /* incomplete */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, F_GETFL)) < 0) */ - /* goto out; */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, F_SETFL)) < 0) */ - /* goto out; */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, F_SETFD)) < 0) */ - /* goto out; */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, F_GETFD)) < 0) */ - /* goto out; */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, F_SETLK)) < 0) */ - /* goto out; */ - - - /* Special requirements for ioctl, allowed on stdout/stderr */ - if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1, - SCMP_CMP(0, SCMP_CMP_EQ, 1)) < 0) - goto out; - if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1, - SCMP_CMP(0, SCMP_CMP_EQ, 2)) < 0) - goto out; - - - - /* TODO: build detailed filter for prctl */ - /* needed by gtk??? (does not load content without) */ - - /* /\* special restrictions for prctl, only allow PR_SET_NAME/PR_SET_PDEATHSIG *\/ */ - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_NAME)) < 0) */ - /* goto out; */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_PDEATHSIG)) < 0) */ - /* goto out; */ - - - /* when zathura is run on wayland, with X11 server available but blocked, unset the DISPLAY variable */ - /* otherwise it will try to connect to X11 using inet socket protocol */ - - /* required changes in links.c (at girara_xdg_open) */ - /* special restrictions for socket, only allow AF_UNIX/AF_LOCAL */ - if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1, - SCMP_CMP(0, SCMP_CMP_EQ, AF_UNIX)) < 0) - goto out; - - if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1, - SCMP_CMP(0, SCMP_CMP_EQ, AF_LOCAL)) < 0) - goto out; - - - /* TODO: avoid the need for the open syscall to be allowed with write permissions */ - - /* zathura needs to open files for writing to save current position */ - - /* /\* special restrictions for open, prevent opening files for writing *\/ */ - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1, */ - /* SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, 0)) < 0) */ - /* goto out; */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ERRNO (EACCES), SCMP_SYS(open), 1, */ - /* SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)) < 0) */ - /* goto out; */ - - /* if (seccomp_rule_add (ctx, SCMP_ACT_ERRNO (EACCES), SCMP_SYS(open), 1, */ - /* SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)) < 0) */ - /* goto out; */ - - - /* ------------ experimental filters --------------- */ - - /* /\* this filter is susceptible to TOCTOU race conditions, providing limited use *\/ */ - /* /\* allow opening only specified files identified by their file descriptors*\/ */ - - /* this requires either a list of all files to open (A LOT!!!) */ - /* or needs to be applied only after initialisation, right before parsing */ - /* if(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 1, */ - /* SCMP_CMP(SCMP_CMP_EQ, fd)) < 0) /\* or < 1 ??? *\/ */ - /* goto out; */ - - - /* /\* restricting write access *\/ */ - - /* /\* allow stdin *\/ */ - /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, 0)) < 0 ) */ - /* goto out; */ - - /* /\* allow stdout *\/ */ - /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, 1)) < 0 ) */ - /* goto out; */ - - - /* /\* allow stderr *\/ */ - /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, 2)) < 0 ) */ - /* goto out; */ - - - /* /\* restrict writev (write a vector) access *\/ */ - /* this does not seem reliable but it surprisingly is. investigate more */ - /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, 3)) < 0 ) */ - /* goto out; */ - - /* test if repeating this after some time or denying it works */ - - - /* first attempt to filter poll requests */ - /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(poll), 1, */ - /* SCMP_CMP(0, SCMP_CMP_MASKED_EQ, POLLIN | POLL, 0)) < 0) */ - /* goto out; */ - - - /* /\* restrict fcntl calls *\/ */ - /* this syscall sets the file descriptor to read write */ - /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ - /* SCMP_CMP(0, SCMP_CMP_EQ, 3)) < 0 ) */ - /* goto out; */ - /* fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) */ - /* fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 */ - /* fcntl(3, F_SETFD, FD_CLOEXEC) = 0 */ - - - /* ------------------ end of experimental filters ------------------ */ - - + /* TODO: check for additional syscalls to blacklist */ + /* DENY_RULE (execve); */ + /* applying filter... */ if (seccomp_load (ctx) >= 0){ /* free ctx after the filter has been loaded into the kernel */ @@ -526,6 +262,104 @@ int seccomp_enable_strict_filter(void){ /* ALLOW_RULE (ioctl); */ + /* TODO: test fcntl rules */ + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, F_GETFL)) < 0) */ + /* goto out; */ + + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, F_SETFL)) < 0) */ + /* goto out; */ + + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, F_SETFD)) < 0) */ + /* goto out; */ + + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, F_GETFD)) < 0) */ + /* goto out; */ + + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, F_SETLK)) < 0) */ + /* goto out; */ + + + /* TODO: build detailed filter for prctl */ + /* needed by gtk??? (does not load content without) */ + + /* /\* special restrictions for prctl, only allow PR_SET_NAME/PR_SET_PDEATHSIG *\/ */ + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_NAME)) < 0) */ + /* goto out; */ + + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_PDEATHSIG)) < 0) */ + /* goto out; */ + + + /* when zathura is run on wayland, with X11 server available but blocked, unset the DISPLAY variable */ + /* otherwise it will try to connect to X11 using inet socket protocol */ + + + /* ------------ experimental filters --------------- */ + + /* /\* this filter is susceptible to TOCTOU race conditions, providing limited use *\/ */ + /* /\* allow opening only specified files identified by their file descriptors*\/ */ + + /* this requires either a list of all files to open (A LOT!!!) */ + /* or needs to be applied only after initialisation, right before parsing */ + /* if(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 1, */ + /* SCMP_CMP(SCMP_CMP_EQ, fd)) < 0) /\* or < 1 ??? *\/ */ + /* goto out; */ + + + /* /\* restricting write access *\/ */ + + /* /\* allow stdin *\/ */ + /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, 0)) < 0 ) */ + /* goto out; */ + + /* /\* allow stdout *\/ */ + /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, 1)) < 0 ) */ + /* goto out; */ + + + /* /\* allow stderr *\/ */ + /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, 2)) < 0 ) */ + /* goto out; */ + + + /* /\* restrict writev (write a vector) access *\/ */ + /* this does not seem reliable but it surprisingly is. investigate more */ + /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, 3)) < 0 ) */ + /* goto out; */ + + /* test if repeating this after some time or denying it works */ + + + /* first attempt to filter poll requests */ + /* if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(poll), 1, */ + /* SCMP_CMP(0, SCMP_CMP_MASKED_EQ, POLLIN | POLL, 0)) < 0) */ + /* goto out; */ + + + /* /\* restrict fcntl calls *\/ */ + /* this syscall sets the file descriptor to read write */ + /* if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 1, */ + /* SCMP_CMP(0, SCMP_CMP_EQ, 3)) < 0 ) */ + /* goto out; */ + /* fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) */ + /* fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 */ + /* fcntl(3, F_SETFD, FD_CLOEXEC) = 0 */ + + + /* ------------------ end of experimental filters ------------------ */ + + /* applying filter... */ if (seccomp_load (ctx) >= 0){ /* free ctx after the filter has been loaded into the kernel */ diff --git a/zathura/libsec.h b/zathura/libsec.h index 6d04fb1..3b1af19 100644 --- a/zathura/libsec.h +++ b/zathura/libsec.h @@ -4,13 +4,7 @@ /* basic filter */ /* this mode allows normal use */ /* only dangerous syscalls are blacklisted */ -int seccomp_enable_protected_mode(void); - -/* secure whitelist filter */ -/* whitelist minimal syscalls only */ -/* this mode does not allow to open external links or to start applications */ -/* network connections are prohibited as well */ -int seccomp_enable_protected_view(void); +int seccomp_enable_basic_filter(void); /* strict filter before document parsing */ /* this filter is to be enabled after most of the initialisation of zathura has finished */ diff --git a/zathura/links.c b/zathura/links.c index b3303f7..4b1e1c3 100644 --- a/zathura/links.c +++ b/zathura/links.c @@ -135,6 +135,10 @@ zathura_link_evaluate(zathura_t* zathura, zathura_link_t* link) bool link_zoom = true; girara_setting_get(zathura->ui.session, "link-zoom", &link_zoom); + /* required below to prevent opening hyperlinks in strict sandbox mode */ + char* sandbox = NULL; + girara_setting_get(zathura->ui.session, "sandbox", &sandbox); + switch (link->type) { case ZATHURA_LINK_GOTO_DEST: if (link->target.destination_type != ZATHURA_LINK_DESTINATION_UNKNOWN) { @@ -203,13 +207,13 @@ zathura_link_evaluate(zathura_t* zathura, zathura_link_t* link) link_remote(zathura, link->target.value); break; case ZATHURA_LINK_URI: -#ifndef WITH_SECCOMP - if (girara_xdg_open(link->target.value) == false) { - girara_notify(zathura->ui.session, GIRARA_ERROR, _("Failed to run xdg-open.")); + if (g_strcmp0(sandbox, "strict") == 0) { + girara_notify(zathura->ui.session, GIRARA_ERROR, _("Opening external applications in strict sandbox mode is not permitted")); + } else { + if (girara_xdg_open(link->target.value) == false) { + girara_notify(zathura->ui.session, GIRARA_ERROR, _("Failed to run xdg-open.")); + } } -#else - girara_notify(zathura->ui.session, GIRARA_ERROR, _("Opening external apps in protectedView Sandbox mode is not permitted")); -#endif break; case ZATHURA_LINK_LAUNCH: link_launch(zathura, link); diff --git a/zathura/main.c b/zathura/main.c index 8a3b859..4668365 100644 --- a/zathura/main.c +++ b/zathura/main.c @@ -127,10 +127,6 @@ int main(int argc, char* argv[]) { -#ifdef WITH_SECCOMP - seccomp_enable_protected_view(); -#endif - init_locale(); /* parse command line arguments */ @@ -298,8 +294,20 @@ main(int argc, char* argv[]) } #ifdef WITH_SECCOMP - /* enforce strict syscall filter before parsing the document */ - seccomp_enable_strict_filter(); + + char* sandbox = NULL; + girara_setting_get(zathura->ui.session, "sandbox", &sandbox); + + if (g_strcmp0(sandbox, "none") == 0) { + girara_debug("Sandbox deactivated."); + } else if (g_strcmp0(sandbox, "normal") == 0) { + girara_debug("Basic sandbox allowing normal operation."); + seccomp_enable_basic_filter(); + } else if (g_strcmp0(sandbox, "strict") == 0) { + girara_debug("Strict sandbox preventing write and network access."); + seccomp_enable_strict_filter(); + } + #endif /* open document if passed */