Merge remote-tracking branch 'valoq/feature/seccomp' into HEAD

This commit is contained in:
Sebastian Ramacher 2018-03-11 16:17:56 +01:00
commit 6f14dfb914
10 changed files with 473 additions and 3 deletions

4
README
View file

@ -16,6 +16,7 @@ check (optional, 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)
@ -31,6 +32,9 @@ enable-sqlite=off and sqlite support won't be available.
The use of magic to detect mime types is optional and can be disabled by setting
enable-magic=off.
The use of seccomp to create a sandboxed environment is optional and can be
enabled by setting enable-seccomp=on.
Installation
------------

View file

@ -1044,6 +1044,16 @@ Define the background color of the selected element in index mode.
* Value type: String
* Default value: #9FBC00
sandbox
^^^^^^^
Defines the sandbox mode to use for the seccomp syscall filter. Possible
values are "none", "normal" and "strict". If "none" is used, the sandbox
will be disabled. The use of "normal" will provide minimal protection and
allow normal use of seccomp with support for all features. The "strict" mode
is a read only sandbox that is intended for viewing documents only.
* Value type: String
* Default value: normal
SEE ALSO
========

View file

@ -70,11 +70,12 @@ additional_sources = []
sqlite = dependency('sqlite3', version: '>=3.5.9', required: false)
synctex = dependency('synctex', required: false)
magic = cc.find_library('magic', required: false)
seccomp = dependency('libseccomp', required: false)
if get_option('enable-sqlite') and sqlite.found()
build_dependencies += sqlite
defines += '-DWITH_SQLITE'
additional_sources = files('zathura/database-sqlite.c')
additional_sources += files('zathura/database-sqlite.c')
endif
if get_option('enable-synctex') and synctex.found()
@ -87,6 +88,12 @@ if get_option('enable-magic') and magic.found()
defines += '-DWITH_MAGIC'
endif
if get_option('enable-seccomp') and seccomp.found()
build_dependencies += seccomp
defines += '-DWITH_SECCOMP'
additional_sources += files('zathura/libsec.c')
endif
# generate version header file
version_header = configure_file(
input: 'zathura/version.h.in',

View file

@ -13,3 +13,8 @@ option('enable-magic',
value: true,
description: 'Enable magic support if available.'
)
option('enable-seccomp',
type: 'boolean',
value: true,
description: 'Enable seccomp support if available.'
)

View file

@ -280,6 +280,15 @@ cmd_print(girara_session_t* session, girara_list_t* UNUSED(argument_list))
return false;
}
char* sandbox = NULL;
girara_setting_get(zathura->ui.session, "sandbox", &sandbox);
if (g_strcmp0(sandbox, "strict") == 0) {
girara_notify(zathura->ui.session, GIRARA_ERROR, _("Printing is not permitted in strict sandbox mode"));
g_free(sandbox);
return false;
}
print(zathura);
return true;

View file

@ -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;

377
zathura/libsec.c Normal file
View file

@ -0,0 +1,377 @@
#include "libsec.h"
#include <stdio.h>
#ifdef WITH_SECCOMP
#include <seccomp.h> /* libseccomp */
#include <sys/prctl.h> /* prctl */
#include <sys/socket.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <girara/utils.h>
#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_basic_filter(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)) {
girara_error("prctl SET_NO_NEW_PRIVS");
return -1;
}
/* prevent escape via ptrace */
if(prctl (PR_SET_DUMPABLE, 0, 0, 0, 0)){
girara_error("prctl PR_SET_DUMPABLE");
return -1;
}
/* initialize the filter */
ctx = seccomp_init(SCMP_ACT_ALLOW);
if (ctx == NULL){
girara_error("seccomp_init failed");
return -1;
}
DENY_RULE (_sysctl);
DENY_RULE (acct);
DENY_RULE (add_key);
DENY_RULE (adjtimex);
DENY_RULE (chroot);
DENY_RULE (clock_adjtime);
DENY_RULE (create_module);
DENY_RULE (delete_module);
DENY_RULE (fanotify_init);
DENY_RULE (finit_module);
DENY_RULE (get_kernel_syms);
DENY_RULE (get_mempolicy);
DENY_RULE (init_module);
DENY_RULE (io_cancel);
DENY_RULE (io_destroy);
DENY_RULE (io_getevents);
DENY_RULE (io_setup);
DENY_RULE (io_submit);
DENY_RULE (ioperm);
DENY_RULE (iopl);
DENY_RULE (ioprio_set);
DENY_RULE (kcmp);
DENY_RULE (kexec_file_load);
DENY_RULE (kexec_load);
DENY_RULE (keyctl);
DENY_RULE (lookup_dcookie);
DENY_RULE (mbind);
DENY_RULE (nfsservctl);
DENY_RULE (migrate_pages);
DENY_RULE (modify_ldt);
DENY_RULE (mount);
DENY_RULE (move_pages);
DENY_RULE (name_to_handle_at);
DENY_RULE (open_by_handle_at);
DENY_RULE (perf_event_open);
DENY_RULE (pivot_root);
DENY_RULE (process_vm_readv);
DENY_RULE (process_vm_writev);
DENY_RULE (ptrace);
DENY_RULE (reboot);
DENY_RULE (remap_file_pages);
DENY_RULE (request_key);
DENY_RULE (set_mempolicy);
DENY_RULE (swapoff);
DENY_RULE (swapon);
DENY_RULE (sysfs);
DENY_RULE (syslog);
DENY_RULE (tuxcall);
DENY_RULE (umount2);
DENY_RULE (uselib);
DENY_RULE (vmsplice);
/* 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 */
seccomp_release(ctx);
return 0;
}
out:
/* something went wrong */
seccomp_release(ctx);
return -1;
}
int seccomp_enable_strict_filter(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 (arch_prctl); */
ALLOW_RULE (bind);
ALLOW_RULE (brk);
ALLOW_RULE (clock_getres);
ALLOW_RULE (clone); /* TODO: investigate */
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 (getpeername); */
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); 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 (sendmsg);
ALLOW_RULE (sendto);
ALLOW_RULE (select);
ALLOW_RULE (set_robust_list);
/* ALLOW_RULE (set_tid_address); */
/* 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); */
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 */
/* 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;
/* 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;
/* 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;
/* special restrictions for openat, prevent opening files for writing */
if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 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(openat), 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(openat), 1,
SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)) < 0)
goto out;
/* allowed for debugging: */
/* ALLOW_RULE (prctl); */
/* 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 */
seccomp_release(ctx);
return 0;
}
out:
/* something went wrong */
seccomp_release(ctx);
return -1;
}
#endif /* WITH_SECCOMP */

13
zathura/libsec.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef SECCOMP_H
#define SECCOMP_H
/* basic filter */
/* this mode allows normal use */
/* only dangerous syscalls are blacklisted */
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 */
int seccomp_enable_strict_filter(void);
#endif

View file

@ -14,6 +14,10 @@
#include "page.h"
#include "render.h"
#ifdef WITH_SECCOMP
#include "libsec.h"
#endif
struct zathura_link_s {
zathura_rectangle_t position; /**< Position of the link */
zathura_link_type_t type; /**< Link type */
@ -131,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) {
@ -199,8 +207,12 @@ zathura_link_evaluate(zathura_t* zathura, zathura_link_t* link)
link_remote(zathura, link->target.value);
break;
case ZATHURA_LINK_URI:
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."));
}
}
break;
case ZATHURA_LINK_LAUNCH:
@ -209,6 +221,7 @@ zathura_link_evaluate(zathura_t* zathura, zathura_link_t* link)
default:
break;
}
g_free(sandbox);
}
void

View file

@ -19,6 +19,10 @@
#include "synctex.h"
#endif
#ifdef WITH_SECCOMP
#include "libsec.h"
#endif
/* Init locale */
static void
init_locale(void)
@ -122,6 +126,7 @@ init_zathura(const char* config_dir, const char* data_dir,
GIRARA_VISIBLE int
main(int argc, char* argv[])
{
init_locale();
/* parse command line arguments */
@ -288,6 +293,31 @@ main(int argc, char* argv[])
goto free_and_ret;
}
#ifdef WITH_SECCOMP
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.");
ret = seccomp_enable_basic_filter();
} else if (g_strcmp0(sandbox, "strict") == 0) {
girara_debug("Strict sandbox preventing write and network access.");
ret = seccomp_enable_strict_filter();
} else {
girara_error("Invalid sandbox option");
ret = -1;
}
g_free(sandbox);
if (ret){
goto free_and_ret;
}
#endif
/* open document if passed */
if (file_idx != 0) {
if (page_number > 0) {