Add seccomp errno filter support

This commit is contained in:
Matias Wadman 2015-09-23 22:44:48 +02:00
parent 5db7520b29
commit 081d1fbf2a
8 changed files with 393 additions and 38 deletions

214
src/firejail/errno.c Normal file
View file

@ -0,0 +1,214 @@
/*
* Copyright (C) 2015 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.
*/
#ifdef HAVE_SECCOMP
#include "firejail.h"
#include <errno.h>
#include <attr/xattr.h>
typedef struct {
char *name;
int nr;
} ErrnoEntry;
static ErrnoEntry errnolist[] = {
//
// code generated using tools/extract-errnos
//
"EPERM", EPERM,
"ENOENT", ENOENT,
"ESRCH", ESRCH,
"EINTR", EINTR,
"EIO", EIO,
"ENXIO", ENXIO,
"E2BIG", E2BIG,
"ENOEXEC", ENOEXEC,
"EBADF", EBADF,
"ECHILD", ECHILD,
"EAGAIN", EAGAIN,
"ENOMEM", ENOMEM,
"EACCES", EACCES,
"EFAULT", EFAULT,
"ENOTBLK", ENOTBLK,
"EBUSY", EBUSY,
"EEXIST", EEXIST,
"EXDEV", EXDEV,
"ENODEV", ENODEV,
"ENOTDIR", ENOTDIR,
"EISDIR", EISDIR,
"EINVAL", EINVAL,
"ENFILE", ENFILE,
"EMFILE", EMFILE,
"ENOTTY", ENOTTY,
"ETXTBSY", ETXTBSY,
"EFBIG", EFBIG,
"ENOSPC", ENOSPC,
"ESPIPE", ESPIPE,
"EROFS", EROFS,
"EMLINK", EMLINK,
"EPIPE", EPIPE,
"EDOM", EDOM,
"ERANGE", ERANGE,
"EDEADLK", EDEADLK,
"ENAMETOOLONG", ENAMETOOLONG,
"ENOLCK", ENOLCK,
"ENOSYS", ENOSYS,
"ENOTEMPTY", ENOTEMPTY,
"ELOOP", ELOOP,
"EWOULDBLOCK", EWOULDBLOCK,
"ENOMSG", ENOMSG,
"EIDRM", EIDRM,
"ECHRNG", ECHRNG,
"EL2NSYNC", EL2NSYNC,
"EL3HLT", EL3HLT,
"EL3RST", EL3RST,
"ELNRNG", ELNRNG,
"EUNATCH", EUNATCH,
"ENOCSI", ENOCSI,
"EL2HLT", EL2HLT,
"EBADE", EBADE,
"EBADR", EBADR,
"EXFULL", EXFULL,
"ENOANO", ENOANO,
"EBADRQC", EBADRQC,
"EBADSLT", EBADSLT,
"EDEADLOCK", EDEADLOCK,
"EBFONT", EBFONT,
"ENOSTR", ENOSTR,
"ENODATA", ENODATA,
"ETIME", ETIME,
"ENOSR", ENOSR,
"ENONET", ENONET,
"ENOPKG", ENOPKG,
"EREMOTE", EREMOTE,
"ENOLINK", ENOLINK,
"EADV", EADV,
"ESRMNT", ESRMNT,
"ECOMM", ECOMM,
"EPROTO", EPROTO,
"EMULTIHOP", EMULTIHOP,
"EDOTDOT", EDOTDOT,
"EBADMSG", EBADMSG,
"EOVERFLOW", EOVERFLOW,
"ENOTUNIQ", ENOTUNIQ,
"EBADFD", EBADFD,
"EREMCHG", EREMCHG,
"ELIBACC", ELIBACC,
"ELIBBAD", ELIBBAD,
"ELIBSCN", ELIBSCN,
"ELIBMAX", ELIBMAX,
"ELIBEXEC", ELIBEXEC,
"EILSEQ", EILSEQ,
"ERESTART", ERESTART,
"ESTRPIPE", ESTRPIPE,
"EUSERS", EUSERS,
"ENOTSOCK", ENOTSOCK,
"EDESTADDRREQ", EDESTADDRREQ,
"EMSGSIZE", EMSGSIZE,
"EPROTOTYPE", EPROTOTYPE,
"ENOPROTOOPT", ENOPROTOOPT,
"EPROTONOSUPPORT", EPROTONOSUPPORT,
"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT,
"EOPNOTSUPP", EOPNOTSUPP,
"EPFNOSUPPORT", EPFNOSUPPORT,
"EAFNOSUPPORT", EAFNOSUPPORT,
"EADDRINUSE", EADDRINUSE,
"EADDRNOTAVAIL", EADDRNOTAVAIL,
"ENETDOWN", ENETDOWN,
"ENETUNREACH", ENETUNREACH,
"ENETRESET", ENETRESET,
"ECONNABORTED", ECONNABORTED,
"ECONNRESET", ECONNRESET,
"ENOBUFS", ENOBUFS,
"EISCONN", EISCONN,
"ENOTCONN", ENOTCONN,
"ESHUTDOWN", ESHUTDOWN,
"ETOOMANYREFS", ETOOMANYREFS,
"ETIMEDOUT", ETIMEDOUT,
"ECONNREFUSED", ECONNREFUSED,
"EHOSTDOWN", EHOSTDOWN,
"EHOSTUNREACH", EHOSTUNREACH,
"EALREADY", EALREADY,
"EINPROGRESS", EINPROGRESS,
"ESTALE", ESTALE,
"EUCLEAN", EUCLEAN,
"ENOTNAM", ENOTNAM,
"ENAVAIL", ENAVAIL,
"EISNAM", EISNAM,
"EREMOTEIO", EREMOTEIO,
"EDQUOT", EDQUOT,
"ENOMEDIUM", ENOMEDIUM,
"EMEDIUMTYPE", EMEDIUMTYPE,
"ECANCELED", ECANCELED,
"ENOKEY", ENOKEY,
"EKEYEXPIRED", EKEYEXPIRED,
"EKEYREVOKED", EKEYREVOKED,
"EKEYREJECTED", EKEYREJECTED,
"EOWNERDEAD", EOWNERDEAD,
"ENOTRECOVERABLE", ENOTRECOVERABLE,
"ERFKILL", ERFKILL,
"EHWPOISON", EHWPOISON,
"ENOTSUP", ENOTSUP,
"ENOATTR", ENOATTR,
};
int errno_highest_nr(void) {
int i, max = 0;
int elems = sizeof(errnolist) / sizeof(errnolist[0]);
for (i = 0; i < elems; i++) {
if (errnolist[i].nr > max)
max = errnolist[i].nr;
}
return max;
}
int errno_find_name(const char *name) {
int i;
int elems = sizeof(errnolist) / sizeof(errnolist[0]);
for (i = 0; i < elems; i++) {
if (strcasecmp(name, errnolist[i].name) == 0)
return errnolist[i].nr;
}
return -1;
}
char *errno_find_nr(int nr) {
int i;
int elems = sizeof(errnolist) / sizeof(errnolist[0]);
for (i = 0; i < elems; i++) {
if (nr == errnolist[i].nr)
return errnolist[i].name;
}
return "unknown";
}
void errno_print(void) {
int i;
int elems = sizeof(errnolist) / sizeof(errnolist[0]);
for (i = 0; i < elems; i++) {
printf("%d\t- %s\n", errnolist[i].nr, errnolist[i].name);
}
printf("\n");
}
#endif // HAVE_SECCOMP

View file

@ -147,6 +147,7 @@ extern int arg_seccomp; // enable default seccomp filter
extern char *arg_seccomp_list;// optional seccomp list on top of default filter
extern char *arg_seccomp_list_drop; // seccomp drop list
extern char *arg_seccomp_list_keep; // seccomp keep list
extern char **arg_seccomp_list_errno; // seccomp errno[nr] lists
extern int arg_caps_default_filter; // enable default capabilities filter
extern int arg_caps_drop; // drop list
@ -335,7 +336,7 @@ void caps_print_filter_name(const char *name);
// 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));
int syscall_check_list(const char *slist, void (*callback)(int syscall, int arg), int arg);
// print all available syscalls
void syscall_print(void);
@ -392,5 +393,11 @@ void env_apply(void);
// fs_whitelist.c
void fs_whitelist(void);
// errno.c
int errno_highest_errno(void);
int errno_find_name(const char *name);
char *errno_find_nr(int nr);
void errno_print(void);
#endif

View file

@ -61,6 +61,7 @@ int arg_seccomp = 0; // enable default seccomp filter
char *arg_seccomp_list = NULL; // optional seccomp list on top of default filter
char *arg_seccomp_list_drop = NULL; // seccomp drop list
char *arg_seccomp_list_keep = NULL; // seccomp keep list
char **arg_seccomp_list_errno = NULL; // seccomp errno[nr] lists
int arg_caps_default_filter = 0; // enable default capabilities filter
int arg_caps_drop = 0; // drop list
@ -302,6 +303,10 @@ static void run_cmd_and_exit(int i, int argc, char **argv) {
syscall_print();
exit(0);
}
else if (strcmp(argv[i], "--debug-errnos") == 0) {
errno_print();
exit(0);
}
else if (strncmp(argv[i], "--seccomp.print=", 16) == 0) {
// join sandbox by pid or by name
pid_t pid;
@ -387,6 +392,7 @@ int main(int argc, char **argv) {
int arg_cgroup = 0;
int custom_profile = 0; // custom profile loaded
int arg_noprofile = 0; // use generic.profile if none other found/specified
int highest_errno = errno_highest_nr();
// check if we already have a sandbox running
int rv = check_kernel_procs();
@ -478,6 +484,34 @@ int main(int argc, char **argv) {
if (!arg_seccomp_list_keep)
errExit("strdup");
}
else if (strncmp(argv[i], "--seccomp.e", 11) == 0 && strchr(argv[i], '=')) {
if (arg_seccomp && !arg_seccomp_list_errno) {
fprintf(stderr, "Error: seccomp already enabled\n");
exit(1);
}
char *eq = strchr(argv[i], '=');
char *errnoname = strndup(argv[i] + 10, eq - (argv[i] + 10));
int nr = errno_find_name(errnoname);
if (nr == -1) {
fprintf(stderr, "Error: unknown errno %s\n", errnoname);
free(errnoname);
exit(1);
}
if (!arg_seccomp_list_errno)
arg_seccomp_list_errno = calloc(highest_errno+1, sizeof(arg_seccomp_list_errno[0]));
if (arg_seccomp_list_errno[nr]) {
fprintf(stderr, "Error: errno %s already configured\n", errnoname);
free(errnoname);
exit(1);
}
arg_seccomp = 1;
arg_seccomp_list_errno[nr] = strdup(eq+1);
if (!arg_seccomp_list_errno[nr])
errExit("strdup");
free(errnoname);
}
#endif
else if (strcmp(argv[i], "--caps") == 0)
arg_caps_default_filter = 1;
@ -1288,6 +1322,15 @@ int main(int argc, char **argv) {
// wait for the child to finish
waitpid(child, NULL, 0);
// free globals
if (arg_seccomp_list_errno) {
for (i = 0; i < highest_errno; i++)
free(arg_seccomp_list_errno[i]);
free(arg_seccomp_list_errno);
}
myexit(0);
return 0;
}

View file

@ -410,6 +410,8 @@ int sandbox(void* sandbox_arg) {
if (arg_seccomp == 1) {
if (arg_seccomp_list_keep)
seccomp_filter_keep(); // this will also save the fmyilter to MNT_DIR/seccomp file
else if (arg_seccomp_list_errno)
seccomp_filter_errno(); // this will also save the filter to MNT_DIR/seccomp file
else
seccomp_filter_drop(); // this will also save the filter to MNT_DIR/seccomp file
}

View file

@ -109,6 +109,10 @@ struct seccomp_data {
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, syscall_nr, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
#define ERRNO(syscall_nr, nr) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, syscall_nr, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO | nr)
#define RETURN_ALLOW \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
@ -157,6 +161,11 @@ void filter_debug(void) {
printf(" BLACKLIST %d %s\n", *nr, syscall_find_nr(*nr));
i += 2;
}
else if (*ptr == 0x15 && *(ptr +14) == 0x5 && *(ptr + 15) == 0) {
int err = *(ptr + 13) << 8 | *(ptr + 12);
printf(" ERRNO %d %s %d %s\n", *nr, syscall_find_nr(*nr), err, errno_find_nr(err));
i += 2;
}
else if (*ptr == 0x06 && *(ptr +6) == 0 && *(ptr + 7) == 0 ) {
printf(" KILL_PROCESS\n");
i++;
@ -216,7 +225,7 @@ static void filter_realloc(void) {
sfilter_alloc_size += SECSIZE;
}
static void filter_add_whitelist(int syscall) {
static void filter_add_whitelist(int syscall, int arg) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
@ -242,7 +251,7 @@ static void filter_add_whitelist(int syscall) {
sfilter_index += sizeof(filter) / sizeof(struct sock_filter);
}
static void filter_add_blacklist(int syscall) {
static void filter_add_blacklist(int syscall, int arg) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
@ -268,6 +277,32 @@ static void filter_add_blacklist(int syscall) {
sfilter_index += sizeof(filter) / sizeof(struct sock_filter);
}
static void filter_add_errno(int syscall, int arg) {
assert(sfilter);
assert(sfilter_alloc_size);
assert(sfilter_index);
// if (arg_debug)
// printf("Errno syscall %d %d %s\n", syscall, arg, syscall_find_nr(syscall));
if ((sfilter_index + 2) > sfilter_alloc_size)
filter_realloc();
struct sock_filter filter[] = {
ERRNO(syscall, arg)
};
#if 0
{
int i;
unsigned char *ptr = (unsigned char *) &filter[0];
for (i = 0; i < sizeof(filter); i++, ptr++)
printf("%x, ", (*ptr) & 0xff);
printf("\n");
}
#endif
memcpy(&sfilter[sfilter_index], filter, sizeof(filter));
sfilter_index += sizeof(filter) / sizeof(struct sock_filter);
}
static void filter_end_blacklist(void) {
assert(sfilter);
assert(sfilter_alloc_size);
@ -405,96 +440,96 @@ int seccomp_filter_drop(void) {
// default seccomp
if (arg_seccomp_list_drop == NULL) {
#ifdef SYS_mount
filter_add_blacklist(SYS_mount);
filter_add_blacklist(SYS_mount, 0);
#endif
#ifdef SYS_umount2
filter_add_blacklist(SYS_umount2);
filter_add_blacklist(SYS_umount2, 0);
#endif
#ifdef SYS_ptrace
filter_add_blacklist(SYS_ptrace);
filter_add_blacklist(SYS_ptrace, 0);
#endif
#ifdef SYS_kexec_load
filter_add_blacklist(SYS_kexec_load);
filter_add_blacklist(SYS_kexec_load, 0);
#endif
#ifdef SYS_open_by_handle_at
filter_add_blacklist(SYS_open_by_handle_at);
filter_add_blacklist(SYS_open_by_handle_at, 0);
#endif
#ifdef SYS_init_module
filter_add_blacklist(SYS_init_module);
filter_add_blacklist(SYS_init_module, 0);
#endif
#ifdef SYS_finit_module // introduced in 2013
filter_add_blacklist(SYS_finit_module);
filter_add_blacklist(SYS_finit_module, 0);
#endif
#ifdef SYS_delete_module
filter_add_blacklist(SYS_delete_module);
filter_add_blacklist(SYS_delete_module, 0);
#endif
#ifdef SYS_iopl
filter_add_blacklist(SYS_iopl);
filter_add_blacklist(SYS_iopl, 0);
#endif
#ifdef SYS_ioperm
filter_add_blacklist(SYS_ioperm);
filter_add_blacklist(SYS_ioperm, 0);
#endif
#ifdef SYS_ni_syscall // new io permisions call on arm devices
filter_add_blacklist(SYS_ni_syscall);
filter_add_blacklist(SYS_ni_syscall, 0);
#endif
#ifdef SYS_swapon
filter_add_blacklist(SYS_swapon);
filter_add_blacklist(SYS_swapon, 0);
#endif
#ifdef SYS_swapoff
filter_add_blacklist(SYS_swapoff);
filter_add_blacklist(SYS_swapoff, 0);
#endif
#ifdef SYS_syslog
filter_add_blacklist(SYS_syslog);
filter_add_blacklist(SYS_syslog, 0);
#endif
#ifdef SYS_process_vm_readv
filter_add_blacklist(SYS_process_vm_readv);
filter_add_blacklist(SYS_process_vm_readv, 0);
#endif
#ifdef SYS_process_vm_writev
filter_add_blacklist(SYS_process_vm_writev);
filter_add_blacklist(SYS_process_vm_writev, 0);
#endif
// mknod removed in 0.9.29
//#ifdef SYS_mknod
// filter_add_blacklist(SYS_mknod);
// filter_add_blacklist(SYS_mknod, 0);
//#endif
// new syscalls in 0.9,23
#ifdef SYS_sysfs
filter_add_blacklist(SYS_sysfs);
filter_add_blacklist(SYS_sysfs, 0);
#endif
#ifdef SYS__sysctl
filter_add_blacklist(SYS__sysctl);
filter_add_blacklist(SYS__sysctl, 0);
#endif
#ifdef SYS_adjtimex
filter_add_blacklist(SYS_adjtimex);
filter_add_blacklist(SYS_adjtimex, 0);
#endif
#ifdef SYS_clock_adjtime
filter_add_blacklist(SYS_clock_adjtime);
filter_add_blacklist(SYS_clock_adjtime, 0);
#endif
#ifdef SYS_lookup_dcookie
filter_add_blacklist(SYS_lookup_dcookie);
filter_add_blacklist(SYS_lookup_dcookie, 0);
#endif
#ifdef SYS_perf_event_open
filter_add_blacklist(SYS_perf_event_open);
filter_add_blacklist(SYS_perf_event_open, 0);
#endif
#ifdef SYS_fanotify_init
filter_add_blacklist(SYS_fanotify_init);
filter_add_blacklist(SYS_fanotify_init, 0);
#endif
#ifdef SYS_kcmp
filter_add_blacklist(SYS_kcmp);
filter_add_blacklist(SYS_kcmp, 0);
#endif
}
// default seccomp filter with additional drop list
if (arg_seccomp_list && arg_seccomp_list_drop == NULL) {
if (syscall_check_list(arg_seccomp_list, filter_add_blacklist)) {
if (syscall_check_list(arg_seccomp_list, filter_add_blacklist, 0)) {
fprintf(stderr, "Error: cannot load seccomp filter\n");
exit(1);
}
}
// drop list
else if (arg_seccomp_list == NULL && arg_seccomp_list_drop) {
if (syscall_check_list(arg_seccomp_list_drop, filter_add_blacklist)) {
if (syscall_check_list(arg_seccomp_list_drop, filter_add_blacklist, 0)) {
fprintf(stderr, "Error: cannot load seccomp filter\n");
exit(1);
}
@ -531,14 +566,14 @@ int seccomp_filter_keep(void) {
filter_init();
// these 4 syscalls are used by firejail after the seccomp filter is initialized
filter_add_whitelist(SYS_setuid);
filter_add_whitelist(SYS_setgid);
filter_add_whitelist(SYS_setgroups);
filter_add_whitelist(SYS_dup);
filter_add_whitelist(SYS_setuid, 0);
filter_add_whitelist(SYS_setgid, 0);
filter_add_whitelist(SYS_setgroups, 0);
filter_add_whitelist(SYS_dup, 0);
// apply keep list
if (arg_seccomp_list_keep) {
if (syscall_check_list(arg_seccomp_list_keep, filter_add_whitelist)) {
if (syscall_check_list(arg_seccomp_list_keep, filter_add_whitelist, 0)) {
fprintf(stderr, "Error: cannot load seccomp filter\n");
exit(1);
}
@ -569,6 +604,47 @@ int seccomp_filter_keep(void) {
return 0;
}
// errno filter for seccomp option
int seccomp_filter_errno(void) {
int i;
int higest_errno = errno_highest_nr();
filter_init();
// apply errno list
for (i = 0; i < higest_errno; i++) {
if (arg_seccomp_list_errno[i]) {
if (syscall_check_list(arg_seccomp_list_errno[i], filter_add_errno, i)) {
fprintf(stderr, "Error: cannot load seccomp filter\n");
exit(1);
}
}
}
filter_end_blacklist();
if (arg_debug)
filter_debug();
// save seccomp filter in /tmp/firejail/mnt/seccomp
// in order to use it in --join operations
write_seccomp_file();
struct sock_fprog prog = {
.len = sfilter_index,
.filter = sfilter,
};
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) || prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
fprintf(stderr, "Warning: seccomp disabled, it requires a Linux kernel version 3.5 or newer.\n");
return 1;
}
else if (arg_debug) {
printf("seccomp enabled\n");
}
return 0;
}
void seccomp_set(void) {

View file

@ -4889,7 +4889,7 @@ static int syscall_find_name(const char *name) {
}
// return 1 if error, 0 if OK
int syscall_check_list(const char *slist, void (*callback)(int)) {
int syscall_check_list(const char *slist, void (*callback)(int syscall, int arg), int arg) {
// don't allow empty lists
if (slist == NULL || *slist == '\0') {
fprintf(stderr, "Error: empty syscall lists are not allowed\n");
@ -4912,7 +4912,7 @@ int syscall_check_list(const char *slist, void (*callback)(int)) {
if (nr == -1)
fprintf(stderr, "Warning: syscall %s not found\n", start);
else if (callback != NULL)
callback(nr);
callback(nr, arg);
start = ptr + 1;
}
@ -4923,7 +4923,7 @@ int syscall_check_list(const char *slist, void (*callback)(int)) {
if (nr == -1)
fprintf(stderr, "Warning: syscall %s not found\n", start);
else if (callback != NULL)
callback(nr);
callback(nr, arg);
}
free(str);

View file

@ -845,6 +845,15 @@ Example:
.br
$ firejail \-\-shell=none \-\-seccomp.keep=poll,select,[...] transmission-gtk
.TP
\fB\-\-seccomp.<errno>=syscall,syscall,syscall
Enable seccomp filter, and return errno for the syscalls specified by the command.
.br
.br
Example:
.br
$ firejail \-\-shell=none \-\-seccomp.einval=kill kill 1
.TP
\fB\-\-seccomp.print=name
Print the seccomp filter for the sandbox started using \-\-name option.
.br

View file

@ -0,0 +1,4 @@
echo -e "#include <errno.h>\n#include <attr/xattr.h>" | \
cpp -dD | \
grep "^#define E" | \
sed -e '{s/#define \(.*\) .*/\t"\1", \1,/g}'