Merge pull request #459 from smcv/multiple-seccomp
Allow loading more than one seccomp program
This commit is contained in:
@@ -32,11 +32,13 @@ test_programs = \
|
|||||||
$(NULL)
|
$(NULL)
|
||||||
test_scripts = \
|
test_scripts = \
|
||||||
tests/test-run.sh \
|
tests/test-run.sh \
|
||||||
|
tests/test-seccomp.py \
|
||||||
tests/test-specifying-userns.sh \
|
tests/test-specifying-userns.sh \
|
||||||
tests/test-specifying-pidns.sh \
|
tests/test-specifying-pidns.sh \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
test_extra_programs = \
|
test_extra_programs = \
|
||||||
test-bwrap \
|
test-bwrap \
|
||||||
|
tests/try-syscall \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
test-bwrap: bwrap
|
test-bwrap: bwrap
|
||||||
|
155
bubblewrap.c
155
bubblewrap.c
@@ -161,11 +161,6 @@ struct _LockFile
|
|||||||
LockFile *next;
|
LockFile *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
static SetupOp *ops = NULL;
|
|
||||||
static SetupOp *last_op = NULL;
|
|
||||||
static LockFile *lock_files = NULL;
|
|
||||||
static LockFile *last_lock_file = NULL;
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PRIV_SEP_OP_DONE,
|
PRIV_SEP_OP_DONE,
|
||||||
PRIV_SEP_OP_BIND_MOUNT,
|
PRIV_SEP_OP_BIND_MOUNT,
|
||||||
@@ -186,38 +181,104 @@ typedef struct
|
|||||||
uint32_t arg2_offset;
|
uint32_t arg2_offset;
|
||||||
} PrivSepOp;
|
} PrivSepOp;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEFINE_LINKED_LIST:
|
||||||
|
* @Type: A struct with a `Type *next` member
|
||||||
|
* @name: Used to form the names of variables and functions
|
||||||
|
*
|
||||||
|
* Define a global linked list of @Type structures, with pointers
|
||||||
|
* `NAMEs` to the head of the list and `last_NAME` to the tail of the
|
||||||
|
* list.
|
||||||
|
*
|
||||||
|
* A new zero-filled item can be allocated and appended to the list
|
||||||
|
* by calling `_NAME_append_new()`, which returns the new item.
|
||||||
|
*/
|
||||||
|
#define DEFINE_LINKED_LIST(Type, name) \
|
||||||
|
static Type *name ## s = NULL; \
|
||||||
|
static Type *last_ ## name = NULL; \
|
||||||
|
\
|
||||||
|
static inline Type * \
|
||||||
|
_ ## name ## _append_new (void) \
|
||||||
|
{ \
|
||||||
|
Type *self = xcalloc (sizeof (Type)); \
|
||||||
|
\
|
||||||
|
if (last_ ## name != NULL) \
|
||||||
|
last_ ## name ->next = self; \
|
||||||
|
else \
|
||||||
|
name ## s = self; \
|
||||||
|
\
|
||||||
|
last_ ## name = self; \
|
||||||
|
return self; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_LINKED_LIST (SetupOp, op)
|
||||||
|
|
||||||
static SetupOp *
|
static SetupOp *
|
||||||
setup_op_new (SetupOpType type)
|
setup_op_new (SetupOpType type)
|
||||||
{
|
{
|
||||||
SetupOp *op = xcalloc (sizeof (SetupOp));
|
SetupOp *op = _op_append_new ();
|
||||||
|
|
||||||
op->type = type;
|
op->type = type;
|
||||||
op->fd = -1;
|
op->fd = -1;
|
||||||
op->flags = 0;
|
op->flags = 0;
|
||||||
if (last_op != NULL)
|
|
||||||
last_op->next = op;
|
|
||||||
else
|
|
||||||
ops = op;
|
|
||||||
|
|
||||||
last_op = op;
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_LINKED_LIST (LockFile, lock_file)
|
||||||
|
|
||||||
static LockFile *
|
static LockFile *
|
||||||
lock_file_new (const char *path)
|
lock_file_new (const char *path)
|
||||||
{
|
{
|
||||||
LockFile *lock = xcalloc (sizeof (LockFile));
|
LockFile *lock = _lock_file_append_new ();
|
||||||
|
|
||||||
lock->path = path;
|
lock->path = path;
|
||||||
if (last_lock_file != NULL)
|
|
||||||
last_lock_file->next = lock;
|
|
||||||
else
|
|
||||||
lock_files = lock;
|
|
||||||
|
|
||||||
last_lock_file = lock;
|
|
||||||
return lock;
|
return lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct _SeccompProgram SeccompProgram;
|
||||||
|
|
||||||
|
struct _SeccompProgram
|
||||||
|
{
|
||||||
|
struct sock_fprog program;
|
||||||
|
SeccompProgram *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_LINKED_LIST (SeccompProgram, seccomp_program)
|
||||||
|
|
||||||
|
static SeccompProgram *
|
||||||
|
seccomp_program_new (int *fd)
|
||||||
|
{
|
||||||
|
SeccompProgram *self = _seccomp_program_append_new ();
|
||||||
|
cleanup_free char *data = NULL;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
data = load_file_data (*fd, &len);
|
||||||
|
|
||||||
|
if (data == NULL)
|
||||||
|
die_with_error ("Can't read seccomp data");
|
||||||
|
|
||||||
|
close (*fd);
|
||||||
|
*fd = -1;
|
||||||
|
|
||||||
|
if (len % 8 != 0)
|
||||||
|
die ("Invalid seccomp data, must be multiple of 8");
|
||||||
|
|
||||||
|
self->program.len = len / 8;
|
||||||
|
self->program.filter = (struct sock_filter *) steal_pointer (&data);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
seccomp_programs_apply (void)
|
||||||
|
{
|
||||||
|
SeccompProgram *program;
|
||||||
|
|
||||||
|
for (program = seccomp_programs; program != NULL; program = program->next)
|
||||||
|
{
|
||||||
|
if (prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program->program) != 0)
|
||||||
|
die_with_error ("prctl(PR_SET_SECCOMP)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
usage (int ecode, FILE *out)
|
usage (int ecode, FILE *out)
|
||||||
@@ -268,7 +329,8 @@ usage (int ecode, FILE *out)
|
|||||||
" --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST\n"
|
" --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST\n"
|
||||||
" --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST\n"
|
" --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST\n"
|
||||||
" --symlink SRC DEST Create symlink at DEST with target SRC\n"
|
" --symlink SRC DEST Create symlink at DEST with target SRC\n"
|
||||||
" --seccomp FD Load and use seccomp rules from FD\n"
|
" --seccomp FD Load and use seccomp rules from FD (not repeatable)\n"
|
||||||
|
" --add-seccomp FD Load and use seccomp rules from FD (repeatable)\n"
|
||||||
" --block-fd FD Block on FD until some data to read is available\n"
|
" --block-fd FD Block on FD until some data to read is available\n"
|
||||||
" --userns-block-fd FD Block on FD until the user namespace is ready\n"
|
" --userns-block-fd FD Block on FD until the user namespace is ready\n"
|
||||||
" --info-fd FD Write information about the running container to FD\n"
|
" --info-fd FD Write information about the running container to FD\n"
|
||||||
@@ -502,7 +564,7 @@ monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd)
|
|||||||
* When there are no other processes in the sandbox the wait will return
|
* When there are no other processes in the sandbox the wait will return
|
||||||
* ECHILD, and we then exit pid 1 to clean up the sandbox. */
|
* ECHILD, and we then exit pid 1 to clean up the sandbox. */
|
||||||
static int
|
static int
|
||||||
do_init (int event_fd, pid_t initial_pid, struct sock_fprog *seccomp_prog)
|
do_init (int event_fd, pid_t initial_pid)
|
||||||
{
|
{
|
||||||
int initial_exit_status = 1;
|
int initial_exit_status = 1;
|
||||||
LockFile *lock;
|
LockFile *lock;
|
||||||
@@ -530,9 +592,7 @@ do_init (int event_fd, pid_t initial_pid, struct sock_fprog *seccomp_prog)
|
|||||||
/* Optionally bind our lifecycle to that of the caller */
|
/* Optionally bind our lifecycle to that of the caller */
|
||||||
handle_die_with_parent ();
|
handle_die_with_parent ();
|
||||||
|
|
||||||
if (seccomp_prog != NULL &&
|
seccomp_programs_apply ();
|
||||||
prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, seccomp_prog) != 0)
|
|
||||||
die_with_error ("prctl(PR_SET_SECCOMP)");
|
|
||||||
|
|
||||||
while (TRUE)
|
while (TRUE)
|
||||||
{
|
{
|
||||||
@@ -2074,6 +2134,9 @@ parse_args_recurse (int *argcp,
|
|||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
die ("--seccomp takes an argument");
|
die ("--seccomp takes an argument");
|
||||||
|
|
||||||
|
if (seccomp_programs != NULL)
|
||||||
|
die ("--seccomp cannot be combined with --add-seccomp-fd");
|
||||||
|
|
||||||
if (opt_seccomp_fd != -1)
|
if (opt_seccomp_fd != -1)
|
||||||
warn_only_last_option ("--seccomp");
|
warn_only_last_option ("--seccomp");
|
||||||
|
|
||||||
@@ -2083,6 +2146,27 @@ parse_args_recurse (int *argcp,
|
|||||||
|
|
||||||
opt_seccomp_fd = the_fd;
|
opt_seccomp_fd = the_fd;
|
||||||
|
|
||||||
|
argv += 1;
|
||||||
|
argc -= 1;
|
||||||
|
}
|
||||||
|
else if (strcmp (arg, "--add-seccomp-fd") == 0)
|
||||||
|
{
|
||||||
|
int the_fd;
|
||||||
|
char *endptr;
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
die ("--add-seccomp-fd takes an argument");
|
||||||
|
|
||||||
|
if (opt_seccomp_fd != -1)
|
||||||
|
die ("--add-seccomp-fd cannot be combined with --seccomp");
|
||||||
|
|
||||||
|
the_fd = strtol (argv[1], &endptr, 10);
|
||||||
|
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
|
||||||
|
die ("Invalid fd: %s", argv[1]);
|
||||||
|
|
||||||
|
/* takes ownership of fd */
|
||||||
|
seccomp_program_new (&the_fd);
|
||||||
|
|
||||||
argv += 1;
|
argv += 1;
|
||||||
argc -= 1;
|
argc -= 1;
|
||||||
}
|
}
|
||||||
@@ -2468,9 +2552,6 @@ main (int argc,
|
|||||||
struct stat sbuf;
|
struct stat sbuf;
|
||||||
uint64_t val;
|
uint64_t val;
|
||||||
int res UNUSED;
|
int res UNUSED;
|
||||||
cleanup_free char *seccomp_data = NULL;
|
|
||||||
size_t seccomp_len;
|
|
||||||
struct sock_fprog seccomp_prog;
|
|
||||||
cleanup_free char *args_data = NULL;
|
cleanup_free char *args_data = NULL;
|
||||||
int intermediate_pids_sockets[2] = {-1, -1};
|
int intermediate_pids_sockets[2] = {-1, -1};
|
||||||
|
|
||||||
@@ -3034,17 +3115,9 @@ main (int argc,
|
|||||||
|
|
||||||
if (opt_seccomp_fd != -1)
|
if (opt_seccomp_fd != -1)
|
||||||
{
|
{
|
||||||
seccomp_data = load_file_data (opt_seccomp_fd, &seccomp_len);
|
assert (seccomp_programs == NULL);
|
||||||
if (seccomp_data == NULL)
|
/* takes ownership of fd */
|
||||||
die_with_error ("Can't read seccomp data");
|
seccomp_program_new (&opt_seccomp_fd);
|
||||||
|
|
||||||
if (seccomp_len % 8 != 0)
|
|
||||||
die ("Invalid seccomp data, must be multiple of 8");
|
|
||||||
|
|
||||||
seccomp_prog.len = seccomp_len / 8;
|
|
||||||
seccomp_prog.filter = (struct sock_filter *) seccomp_data;
|
|
||||||
|
|
||||||
close (opt_seccomp_fd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
umask (old_umask);
|
umask (old_umask);
|
||||||
@@ -3113,7 +3186,7 @@ main (int argc,
|
|||||||
fdwalk (proc_fd, close_extra_fds, dont_close);
|
fdwalk (proc_fd, close_extra_fds, dont_close);
|
||||||
}
|
}
|
||||||
|
|
||||||
return do_init (event_fd, pid, seccomp_data != NULL ? &seccomp_prog : NULL);
|
return do_init (event_fd, pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3141,9 +3214,7 @@ main (int argc,
|
|||||||
|
|
||||||
/* Should be the last thing before execve() so that filters don't
|
/* Should be the last thing before execve() so that filters don't
|
||||||
* need to handle anything above */
|
* need to handle anything above */
|
||||||
if (seccomp_data != NULL &&
|
seccomp_programs_apply ();
|
||||||
prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &seccomp_prog) != 0)
|
|
||||||
die_with_error ("prctl(PR_SET_SECCOMP)");
|
|
||||||
|
|
||||||
if (setup_finished_pipe[1] != -1)
|
if (setup_finished_pipe[1] != -1)
|
||||||
{
|
{
|
||||||
|
17
bwrap.xml
17
bwrap.xml
@@ -328,6 +328,23 @@
|
|||||||
Load and use seccomp rules from <arg choice="plain">FD</arg>.
|
Load and use seccomp rules from <arg choice="plain">FD</arg>.
|
||||||
The rules need to be in the form of a compiled cBPF program,
|
The rules need to be in the form of a compiled cBPF program,
|
||||||
as generated by seccomp_export_bpf.
|
as generated by seccomp_export_bpf.
|
||||||
|
If this option is given more than once, only the last one is used.
|
||||||
|
Use <option>--add-seccomp-fd</option> if multiple seccomp programs
|
||||||
|
are needed.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--add-seccomp-fd <arg choice="plain">FD</arg></option></term>
|
||||||
|
<listitem><para>
|
||||||
|
Load and use seccomp rules from <arg choice="plain">FD</arg>.
|
||||||
|
The rules need to be in the form of a compiled cBPF program,
|
||||||
|
as generated by seccomp_export_bpf.
|
||||||
|
This option can be repeated, in which case all the seccomp
|
||||||
|
programs will be loaded in the order given (note that the kernel
|
||||||
|
will evaluate them in reverse order, so the last program on the
|
||||||
|
bwrap command-line is evaluated first). All of them, except
|
||||||
|
possibly the last, must allow use of the PR_SET_SECCOMP prctl.
|
||||||
|
This option cannot be combined with <option>--seccomp</option>.
|
||||||
</para></listitem>
|
</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
|
635
tests/test-seccomp.py
Executable file
635
tests/test-seccomp.py
Executable file
@@ -0,0 +1,635 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Simon McVittie
|
||||||
|
# SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import termios
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
try:
|
||||||
|
import seccomp
|
||||||
|
except ImportError:
|
||||||
|
print('1..0 # SKIP cannot import seccomp Python module')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
# This is the @default set from systemd as of 2021-10-11
|
||||||
|
DEFAULT_SET = set('''
|
||||||
|
brk
|
||||||
|
cacheflush
|
||||||
|
clock_getres
|
||||||
|
clock_getres_time64
|
||||||
|
clock_gettime
|
||||||
|
clock_gettime64
|
||||||
|
clock_nanosleep
|
||||||
|
clock_nanosleep_time64
|
||||||
|
execve
|
||||||
|
exit
|
||||||
|
exit_group
|
||||||
|
futex
|
||||||
|
futex_time64
|
||||||
|
get_robust_list
|
||||||
|
get_thread_area
|
||||||
|
getegid
|
||||||
|
getegid32
|
||||||
|
geteuid
|
||||||
|
geteuid32
|
||||||
|
getgid
|
||||||
|
getgid32
|
||||||
|
getgroups
|
||||||
|
getgroups32
|
||||||
|
getpgid
|
||||||
|
getpgrp
|
||||||
|
getpid
|
||||||
|
getppid
|
||||||
|
getrandom
|
||||||
|
getresgid
|
||||||
|
getresgid32
|
||||||
|
getresuid
|
||||||
|
getresuid32
|
||||||
|
getrlimit
|
||||||
|
getsid
|
||||||
|
gettid
|
||||||
|
gettimeofday
|
||||||
|
getuid
|
||||||
|
getuid32
|
||||||
|
membarrier
|
||||||
|
mmap
|
||||||
|
mmap2
|
||||||
|
munmap
|
||||||
|
nanosleep
|
||||||
|
pause
|
||||||
|
prlimit64
|
||||||
|
restart_syscall
|
||||||
|
rseq
|
||||||
|
rt_sigreturn
|
||||||
|
sched_getaffinity
|
||||||
|
sched_yield
|
||||||
|
set_robust_list
|
||||||
|
set_thread_area
|
||||||
|
set_tid_address
|
||||||
|
set_tls
|
||||||
|
sigreturn
|
||||||
|
time
|
||||||
|
ugetrlimit
|
||||||
|
'''.split())
|
||||||
|
|
||||||
|
# This is the @basic-io set from systemd
|
||||||
|
BASIC_IO_SET = set('''
|
||||||
|
_llseek
|
||||||
|
close
|
||||||
|
close_range
|
||||||
|
dup
|
||||||
|
dup2
|
||||||
|
dup3
|
||||||
|
lseek
|
||||||
|
pread64
|
||||||
|
preadv
|
||||||
|
preadv2
|
||||||
|
pwrite64
|
||||||
|
pwritev
|
||||||
|
pwritev2
|
||||||
|
read
|
||||||
|
readv
|
||||||
|
write
|
||||||
|
writev
|
||||||
|
'''.split())
|
||||||
|
|
||||||
|
# This is the @filesystem-io set from systemd
|
||||||
|
FILESYSTEM_SET = set('''
|
||||||
|
access
|
||||||
|
chdir
|
||||||
|
chmod
|
||||||
|
close
|
||||||
|
creat
|
||||||
|
faccessat
|
||||||
|
faccessat2
|
||||||
|
fallocate
|
||||||
|
fchdir
|
||||||
|
fchmod
|
||||||
|
fchmodat
|
||||||
|
fcntl
|
||||||
|
fcntl64
|
||||||
|
fgetxattr
|
||||||
|
flistxattr
|
||||||
|
fremovexattr
|
||||||
|
fsetxattr
|
||||||
|
fstat
|
||||||
|
fstat64
|
||||||
|
fstatat64
|
||||||
|
fstatfs
|
||||||
|
fstatfs64
|
||||||
|
ftruncate
|
||||||
|
ftruncate64
|
||||||
|
futimesat
|
||||||
|
getcwd
|
||||||
|
getdents
|
||||||
|
getdents64
|
||||||
|
getxattr
|
||||||
|
inotify_add_watch
|
||||||
|
inotify_init
|
||||||
|
inotify_init1
|
||||||
|
inotify_rm_watch
|
||||||
|
lgetxattr
|
||||||
|
link
|
||||||
|
linkat
|
||||||
|
listxattr
|
||||||
|
llistxattr
|
||||||
|
lremovexattr
|
||||||
|
lsetxattr
|
||||||
|
lstat
|
||||||
|
lstat64
|
||||||
|
mkdir
|
||||||
|
mkdirat
|
||||||
|
mknod
|
||||||
|
mknodat
|
||||||
|
newfstatat
|
||||||
|
oldfstat
|
||||||
|
oldlstat
|
||||||
|
oldstat
|
||||||
|
open
|
||||||
|
openat
|
||||||
|
openat2
|
||||||
|
readlink
|
||||||
|
readlinkat
|
||||||
|
removexattr
|
||||||
|
rename
|
||||||
|
renameat
|
||||||
|
renameat2
|
||||||
|
rmdir
|
||||||
|
setxattr
|
||||||
|
stat
|
||||||
|
stat64
|
||||||
|
statfs
|
||||||
|
statfs64
|
||||||
|
statx
|
||||||
|
symlink
|
||||||
|
symlinkat
|
||||||
|
truncate
|
||||||
|
truncate64
|
||||||
|
unlink
|
||||||
|
unlinkat
|
||||||
|
utime
|
||||||
|
utimensat
|
||||||
|
utimensat_time64
|
||||||
|
utimes
|
||||||
|
'''.split())
|
||||||
|
|
||||||
|
# Miscellaneous syscalls used during process startup, at least on x86_64
|
||||||
|
ALLOWED = DEFAULT_SET | BASIC_IO_SET | FILESYSTEM_SET | set('''
|
||||||
|
arch_prctl
|
||||||
|
ioctl
|
||||||
|
madvise
|
||||||
|
mprotect
|
||||||
|
mremap
|
||||||
|
prctl
|
||||||
|
readdir
|
||||||
|
umask
|
||||||
|
'''.split())
|
||||||
|
|
||||||
|
# Syscalls we will try to use, expecting them to be either allowed or
|
||||||
|
# blocked by our allow and/or deny lists
|
||||||
|
TRY_SYSCALLS = [
|
||||||
|
'chmod',
|
||||||
|
'chroot',
|
||||||
|
'clone3',
|
||||||
|
'ioctl TIOCNOTTY',
|
||||||
|
'ioctl TIOCSTI CVE-2019-10063',
|
||||||
|
'ioctl TIOCSTI',
|
||||||
|
'listen',
|
||||||
|
'prctl',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Test(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
if 'G_TEST_SRCDIR' in os.environ:
|
||||||
|
self.test_srcdir = os.getenv('G_TEST_SRCDIR') + '/tests'
|
||||||
|
else:
|
||||||
|
self.test_srcdir = here
|
||||||
|
|
||||||
|
if 'G_TEST_BUILDDIR' in os.environ:
|
||||||
|
self.test_builddir = os.getenv('G_TEST_BUILDDIR') + '/tests'
|
||||||
|
else:
|
||||||
|
self.test_builddir = here
|
||||||
|
|
||||||
|
self.bwrap = os.getenv('BWRAP', 'bwrap')
|
||||||
|
self.try_syscall = os.path.join(self.test_builddir, 'try-syscall')
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'true',
|
||||||
|
],
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if completed.returncode != 0:
|
||||||
|
raise unittest.SkipTest(
|
||||||
|
'cannot run bwrap (does it need to be setuid?)'
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_no_seccomp(self) -> None:
|
||||||
|
for syscall in TRY_SYSCALLS:
|
||||||
|
print('# {} without seccomp'.format(syscall))
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
self.try_syscall, syscall,
|
||||||
|
],
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||||
|
and completed.returncode == errno.ENOENT
|
||||||
|
):
|
||||||
|
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if syscall == 'clone3':
|
||||||
|
# If the kernel supports it, we didn't block it so
|
||||||
|
# it fails with EFAULT. If the kernel doesn't support it,
|
||||||
|
# it'll fail with ENOSYS instead.
|
||||||
|
self.assertIn(
|
||||||
|
completed.returncode,
|
||||||
|
(errno.ENOSYS, errno.EFAULT),
|
||||||
|
)
|
||||||
|
elif syscall.startswith('ioctl') or syscall == 'listen':
|
||||||
|
self.assertEqual(completed.returncode, errno.EBADF)
|
||||||
|
else:
|
||||||
|
self.assertEqual(completed.returncode, errno.EFAULT)
|
||||||
|
|
||||||
|
def test_seccomp_allowlist(self) -> None:
|
||||||
|
with tempfile.TemporaryFile() as allowlist_temp:
|
||||||
|
allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS))
|
||||||
|
|
||||||
|
if os.uname().machine == 'x86_64':
|
||||||
|
# Allow Python and try-syscall to be different word sizes
|
||||||
|
allowlist.add_arch(seccomp.Arch.X86)
|
||||||
|
|
||||||
|
for syscall in ALLOWED:
|
||||||
|
try:
|
||||||
|
allowlist.add_rule(seccomp.ALLOW, syscall)
|
||||||
|
except Exception as e:
|
||||||
|
print('# Cannot add {} to allowlist: {!r}'.format(syscall, e))
|
||||||
|
|
||||||
|
allowlist.export_bpf(allowlist_temp)
|
||||||
|
|
||||||
|
for syscall in TRY_SYSCALLS:
|
||||||
|
print('# allowlist vs. {}'.format(syscall))
|
||||||
|
allowlist_temp.seek(0, os.SEEK_SET)
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--seccomp', str(allowlist_temp.fileno()),
|
||||||
|
self.try_syscall, syscall,
|
||||||
|
],
|
||||||
|
pass_fds=(allowlist_temp.fileno(),),
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||||
|
and completed.returncode == errno.ENOENT
|
||||||
|
):
|
||||||
|
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if syscall.startswith('ioctl'):
|
||||||
|
# We allow this, so it is executed (and in this simple
|
||||||
|
# example, immediately fails)
|
||||||
|
self.assertEqual(completed.returncode, errno.EBADF)
|
||||||
|
elif syscall in ('chroot', 'listen', 'clone3'):
|
||||||
|
# We don't allow these, so they fail with ENOSYS.
|
||||||
|
# clone3 might also be failing with ENOSYS because
|
||||||
|
# the kernel genuinely doesn't support it.
|
||||||
|
self.assertEqual(completed.returncode, errno.ENOSYS)
|
||||||
|
else:
|
||||||
|
# We allow this, so it is executed (and in this simple
|
||||||
|
# example, immediately fails)
|
||||||
|
self.assertEqual(completed.returncode, errno.EFAULT)
|
||||||
|
|
||||||
|
def test_seccomp_denylist(self) -> None:
|
||||||
|
with tempfile.TemporaryFile() as denylist_temp:
|
||||||
|
denylist = seccomp.SyscallFilter(seccomp.ALLOW)
|
||||||
|
|
||||||
|
if os.uname().machine == 'x86_64':
|
||||||
|
# Allow Python and try-syscall to be different word sizes
|
||||||
|
denylist.add_arch(seccomp.Arch.X86)
|
||||||
|
|
||||||
|
# Using ECONNREFUSED here because it's unlikely that any of
|
||||||
|
# these syscalls will legitimately fail with that code, so
|
||||||
|
# if they fail like this, it will be as a result of seccomp.
|
||||||
|
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod')
|
||||||
|
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot')
|
||||||
|
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl')
|
||||||
|
denylist.add_rule(
|
||||||
|
seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl',
|
||||||
|
seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI),
|
||||||
|
)
|
||||||
|
|
||||||
|
denylist.export_bpf(denylist_temp)
|
||||||
|
|
||||||
|
for syscall in TRY_SYSCALLS:
|
||||||
|
print('# denylist vs. {}'.format(syscall))
|
||||||
|
denylist_temp.seek(0, os.SEEK_SET)
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--seccomp', str(denylist_temp.fileno()),
|
||||||
|
self.try_syscall, syscall,
|
||||||
|
],
|
||||||
|
pass_fds=(denylist_temp.fileno(),),
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||||
|
and completed.returncode == errno.ENOENT
|
||||||
|
):
|
||||||
|
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if syscall == 'clone3':
|
||||||
|
# If the kernel supports it, we didn't block it so
|
||||||
|
# it fails with EFAULT. If the kernel doesn't support it,
|
||||||
|
# it'll fail with ENOSYS instead.
|
||||||
|
self.assertIn(
|
||||||
|
completed.returncode,
|
||||||
|
(errno.ENOSYS, errno.EFAULT),
|
||||||
|
)
|
||||||
|
elif syscall in ('ioctl TIOCNOTTY', 'listen'):
|
||||||
|
# Not on the denylist
|
||||||
|
self.assertEqual(completed.returncode, errno.EBADF)
|
||||||
|
else:
|
||||||
|
# We blocked all of these
|
||||||
|
self.assertEqual(completed.returncode, errno.ECONNREFUSED)
|
||||||
|
|
||||||
|
def test_seccomp_stacked(self, allowlist_first=False) -> None:
|
||||||
|
with tempfile.TemporaryFile(
|
||||||
|
) as allowlist_temp, tempfile.TemporaryFile(
|
||||||
|
) as denylist_temp:
|
||||||
|
# This filter is a simplified version of what Flatpak wants
|
||||||
|
|
||||||
|
allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS))
|
||||||
|
denylist = seccomp.SyscallFilter(seccomp.ALLOW)
|
||||||
|
|
||||||
|
if os.uname().machine == 'x86_64':
|
||||||
|
# Allow Python and try-syscall to be different word sizes
|
||||||
|
allowlist.add_arch(seccomp.Arch.X86)
|
||||||
|
denylist.add_arch(seccomp.Arch.X86)
|
||||||
|
|
||||||
|
for syscall in ALLOWED:
|
||||||
|
try:
|
||||||
|
allowlist.add_rule(seccomp.ALLOW, syscall)
|
||||||
|
except Exception as e:
|
||||||
|
print('# Cannot add {} to allowlist: {!r}'.format(syscall, e))
|
||||||
|
|
||||||
|
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod')
|
||||||
|
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot')
|
||||||
|
denylist.add_rule(
|
||||||
|
seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl',
|
||||||
|
seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI),
|
||||||
|
)
|
||||||
|
|
||||||
|
# All seccomp programs except the last must allow prctl(),
|
||||||
|
# because otherwise we wouldn't be able to add the remaining
|
||||||
|
# seccomp programs. We document that the last program can
|
||||||
|
# block prctl, so test that.
|
||||||
|
if allowlist_first:
|
||||||
|
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl')
|
||||||
|
|
||||||
|
allowlist.export_bpf(allowlist_temp)
|
||||||
|
denylist.export_bpf(denylist_temp)
|
||||||
|
|
||||||
|
for syscall in TRY_SYSCALLS:
|
||||||
|
print('# stacked vs. {}'.format(syscall))
|
||||||
|
allowlist_temp.seek(0, os.SEEK_SET)
|
||||||
|
denylist_temp.seek(0, os.SEEK_SET)
|
||||||
|
|
||||||
|
if allowlist_first:
|
||||||
|
fds = [allowlist_temp.fileno(), denylist_temp.fileno()]
|
||||||
|
else:
|
||||||
|
fds = [denylist_temp.fileno(), allowlist_temp.fileno()]
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--add-seccomp-fd', str(fds[0]),
|
||||||
|
'--add-seccomp-fd', str(fds[1]),
|
||||||
|
self.try_syscall, syscall,
|
||||||
|
],
|
||||||
|
pass_fds=fds,
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||||
|
and completed.returncode == errno.ENOENT
|
||||||
|
):
|
||||||
|
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if syscall == 'ioctl TIOCNOTTY':
|
||||||
|
# Not denied by the denylist, and allowed by the allowlist
|
||||||
|
self.assertEqual(completed.returncode, errno.EBADF)
|
||||||
|
elif syscall in ('clone3', 'listen'):
|
||||||
|
# We didn't deny these, so the denylist has no effect
|
||||||
|
# and we fall back to the allowlist, which doesn't
|
||||||
|
# include them either.
|
||||||
|
# clone3 might also be failing with ENOSYS because
|
||||||
|
# the kernel genuinely doesn't support it.
|
||||||
|
self.assertEqual(completed.returncode, errno.ENOSYS)
|
||||||
|
elif syscall == 'chroot':
|
||||||
|
# This is denied by the denylist *and* not allowed by
|
||||||
|
# the allowlist. The result depends which one we added
|
||||||
|
# first: the most-recently-added filter "wins".
|
||||||
|
if allowlist_first:
|
||||||
|
self.assertEqual(
|
||||||
|
completed.returncode,
|
||||||
|
errno.ECONNREFUSED,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertEqual(completed.returncode, errno.ENOSYS)
|
||||||
|
elif syscall == 'prctl':
|
||||||
|
# We can only put this on the denylist if the denylist
|
||||||
|
# is the last to be added.
|
||||||
|
if allowlist_first:
|
||||||
|
self.assertEqual(
|
||||||
|
completed.returncode,
|
||||||
|
errno.ECONNREFUSED,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertEqual(completed.returncode, errno.EFAULT)
|
||||||
|
else:
|
||||||
|
# chmod is allowed by the allowlist but blocked by the
|
||||||
|
# denylist. Denying takes precedence over allowing,
|
||||||
|
# regardless of order.
|
||||||
|
self.assertEqual(completed.returncode, errno.ECONNREFUSED)
|
||||||
|
|
||||||
|
def test_seccomp_stacked_allowlist_first(self) -> None:
|
||||||
|
self.test_seccomp_stacked(allowlist_first=True)
|
||||||
|
|
||||||
|
def test_seccomp_invalid(self) -> None:
|
||||||
|
with tempfile.TemporaryFile(
|
||||||
|
) as allowlist_temp, tempfile.TemporaryFile(
|
||||||
|
) as denylist_temp:
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--add-seccomp-fd', '-1',
|
||||||
|
'true',
|
||||||
|
],
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn(b'bwrap: Invalid fd: -1\n', completed.stderr)
|
||||||
|
self.assertEqual(completed.returncode, 1)
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--seccomp', '0a',
|
||||||
|
'true',
|
||||||
|
],
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn(b'bwrap: Invalid fd: 0a\n', completed.stderr)
|
||||||
|
self.assertEqual(completed.returncode, 1)
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--add-seccomp-fd', str(denylist_temp.fileno()),
|
||||||
|
'--seccomp', str(allowlist_temp.fileno()),
|
||||||
|
'true',
|
||||||
|
],
|
||||||
|
pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
b'bwrap: --seccomp cannot be combined with --add-seccomp-fd\n',
|
||||||
|
completed.stderr,
|
||||||
|
)
|
||||||
|
self.assertEqual(completed.returncode, 1)
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--seccomp', str(allowlist_temp.fileno()),
|
||||||
|
'--add-seccomp-fd', str(denylist_temp.fileno()),
|
||||||
|
'true',
|
||||||
|
],
|
||||||
|
pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
b'--add-seccomp-fd cannot be combined with --seccomp',
|
||||||
|
completed.stderr,
|
||||||
|
)
|
||||||
|
self.assertEqual(completed.returncode, 1)
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--add-seccomp-fd', str(allowlist_temp.fileno()),
|
||||||
|
'--add-seccomp-fd', str(allowlist_temp.fileno()),
|
||||||
|
'true',
|
||||||
|
],
|
||||||
|
pass_fds=(allowlist_temp.fileno(), allowlist_temp.fileno()),
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
b"bwrap: Can't read seccomp data: ",
|
||||||
|
completed.stderr,
|
||||||
|
)
|
||||||
|
self.assertEqual(completed.returncode, 1)
|
||||||
|
|
||||||
|
allowlist_temp.write(b'\x01')
|
||||||
|
allowlist_temp.seek(0, os.SEEK_SET)
|
||||||
|
completed = subprocess.run(
|
||||||
|
[
|
||||||
|
self.bwrap,
|
||||||
|
'--ro-bind', '/', '/',
|
||||||
|
'--add-seccomp-fd', str(denylist_temp.fileno()),
|
||||||
|
'--add-seccomp-fd', str(allowlist_temp.fileno()),
|
||||||
|
'true',
|
||||||
|
],
|
||||||
|
pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
b'bwrap: Invalid seccomp data, must be multiple of 8\n',
|
||||||
|
completed.stderr,
|
||||||
|
)
|
||||||
|
self.assertEqual(completed.returncode, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from tap.runner import TAPTestRunner
|
||||||
|
except ImportError:
|
||||||
|
TAPTestRunner = None # type: ignore
|
||||||
|
|
||||||
|
if TAPTestRunner is not None:
|
||||||
|
runner = TAPTestRunner()
|
||||||
|
runner.set_stream(True)
|
||||||
|
unittest.main(testRunner=runner)
|
||||||
|
else:
|
||||||
|
print('# tap.runner not available, using simple TAP output')
|
||||||
|
print('1..1')
|
||||||
|
program = unittest.main(exit=False)
|
||||||
|
if program.result.wasSuccessful():
|
||||||
|
print('ok 1 - %r' % program.result)
|
||||||
|
else:
|
||||||
|
print('not ok 1 - %r' % program.result)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
176
tests/try-syscall.c
Normal file
176
tests/try-syscall.c
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Simon McVittie
|
||||||
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*
|
||||||
|
* Try one or more system calls that might have been blocked by a
|
||||||
|
* seccomp filter. Return the last value of errno seen.
|
||||||
|
*
|
||||||
|
* In general, we pass a bad fd or pointer to each syscall that will
|
||||||
|
* accept one, so that it will fail with EBADF or EFAULT without side-effects.
|
||||||
|
*
|
||||||
|
* This helper is used for regression tests in both bubblewrap and flatpak.
|
||||||
|
* Please keep both copies in sync.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#if defined(_MIPS_SIM)
|
||||||
|
# if _MIPS_SIM == _MIPS_SIM_ABI32
|
||||||
|
# define MISSING_SYSCALL_BASE 4000
|
||||||
|
# elif _MIPS_SIM == _MIPS_SIM_ABI64
|
||||||
|
# define MISSING_SYSCALL_BASE 5000
|
||||||
|
# elif _MIPS_SIM == _MIPS_SIM_NABI32
|
||||||
|
# define MISSING_SYSCALL_BASE 6000
|
||||||
|
# else
|
||||||
|
# error "Unknown MIPS ABI"
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__ia64__)
|
||||||
|
# define MISSING_SYSCALL_BASE 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__alpha__)
|
||||||
|
# define MISSING_SYSCALL_BASE 110
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__x86_64__) && defined(__ILP32__)
|
||||||
|
# define MISSING_SYSCALL_BASE 0x40000000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MISSING_SYSCALL_BASE:
|
||||||
|
*
|
||||||
|
* Number to add to the syscall numbers of recently-added syscalls
|
||||||
|
* to get the appropriate syscall for the current ABI.
|
||||||
|
*/
|
||||||
|
#ifndef MISSING_SYSCALL_BASE
|
||||||
|
# define MISSING_SYSCALL_BASE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __NR_clone3
|
||||||
|
# define __NR_clone3 (MISSING_SYSCALL_BASE + 435)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The size of clone3's parameter (as of 2021)
|
||||||
|
*/
|
||||||
|
#define SIZEOF_STRUCT_CLONE_ARGS ((size_t) 88)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An invalid pointer that will cause syscalls to fail with EFAULT
|
||||||
|
*/
|
||||||
|
#define WRONG_POINTER ((char *) 1)
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
int errsv = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
const char *arg = argv[i];
|
||||||
|
|
||||||
|
if (strcmp (arg, "print-errno-values") == 0)
|
||||||
|
{
|
||||||
|
printf ("EBADF=%d\n", EBADF);
|
||||||
|
printf ("EFAULT=%d\n", EFAULT);
|
||||||
|
printf ("ENOENT=%d\n", ENOENT);
|
||||||
|
printf ("ENOSYS=%d\n", ENOSYS);
|
||||||
|
printf ("EPERM=%d\n", EPERM);
|
||||||
|
}
|
||||||
|
else if (strcmp (arg, "chmod") == 0)
|
||||||
|
{
|
||||||
|
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||||
|
if (chmod (WRONG_POINTER, 0700) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp (arg, "chroot") == 0)
|
||||||
|
{
|
||||||
|
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||||
|
if (chroot (WRONG_POINTER) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp (arg, "clone3") == 0)
|
||||||
|
{
|
||||||
|
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||||
|
if (syscall (__NR_clone3, WRONG_POINTER, SIZEOF_STRUCT_CLONE_ARGS) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp (arg, "ioctl TIOCNOTTY") == 0)
|
||||||
|
{
|
||||||
|
/* If not blocked by seccomp, this will fail with EBADF */
|
||||||
|
if (ioctl (-1, TIOCNOTTY) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp (arg, "ioctl TIOCSTI") == 0)
|
||||||
|
{
|
||||||
|
/* If not blocked by seccomp, this will fail with EBADF */
|
||||||
|
if (ioctl (-1, TIOCSTI, WRONG_POINTER) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef __LP64__
|
||||||
|
else if (strcmp (arg, "ioctl TIOCSTI CVE-2019-10063") == 0)
|
||||||
|
{
|
||||||
|
unsigned long not_TIOCSTI = (0x123UL << 32) | (unsigned long) TIOCSTI;
|
||||||
|
|
||||||
|
/* If not blocked by seccomp, this will fail with EBADF */
|
||||||
|
if (syscall (__NR_ioctl, -1, not_TIOCSTI, WRONG_POINTER) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (strcmp (arg, "listen") == 0)
|
||||||
|
{
|
||||||
|
/* If not blocked by seccomp, this will fail with EBADF */
|
||||||
|
if (listen (-1, 42) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp (arg, "prctl") == 0)
|
||||||
|
{
|
||||||
|
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||||
|
if (prctl (PR_GET_CHILD_SUBREAPER, WRONG_POINTER, 0, 0, 0) != 0)
|
||||||
|
{
|
||||||
|
errsv = errno;
|
||||||
|
perror (arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf (stderr, "Unsupported syscall \"%s\"\n", arg);
|
||||||
|
errsv = ENOENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errsv;
|
||||||
|
}
|
Reference in New Issue
Block a user