1842 line
54 KiB
C
1842 line
54 KiB
C
/*
|
|
* Copyright (c) 2019-2022 Albert Schwarzkopf <mail at quitesimple dot org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "exile.h"
|
|
|
|
static struct syscall_vow_map exile_vow_map[] =
|
|
{
|
|
{EXILE_SYS(read), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(write), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(open), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH},
|
|
{EXILE_SYS(close), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(stat), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(fstat), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(lstat), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(poll), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(lseek), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mmap), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PROT_EXEC},
|
|
{EXILE_SYS(mprotect), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PROT_EXEC},
|
|
{EXILE_SYS(munmap), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(brk), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(rt_sigaction), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(rt_sigprocmask), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(rt_sigreturn), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(ioctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_IOCTL},
|
|
{EXILE_SYS(pread64), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(pwrite64), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(readv), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(writev), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(access), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(pipe), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(select), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(sched_yield), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mremap), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(msync), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mincore), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(madvise), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(shmget), EXILE_SYSCALL_VOW_SHM},
|
|
{EXILE_SYS(shmat), EXILE_SYSCALL_VOW_SHM},
|
|
{EXILE_SYS(shmctl), EXILE_SYSCALL_VOW_SHM},
|
|
{EXILE_SYS(dup), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(dup2), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(pause), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(nanosleep), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getitimer), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(alarm), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setitimer), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getpid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(sendfile), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(socket), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(connect), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(accept), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(sendto), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(recvfrom), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(sendmsg), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(recvmsg), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(shutdown), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(bind), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(listen), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(getsockname), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(getpeername), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(socketpair), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setsockopt), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(getsockopt), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX},
|
|
{EXILE_SYS(clone), EXILE_SYSCALL_VOW_CLONE|EXILE_SYSCALL_VOW_THREAD},
|
|
{EXILE_SYS(fork), EXILE_SYSCALL_VOW_CLONE},
|
|
{EXILE_SYS(vfork), EXILE_SYSCALL_VOW_CLONE},
|
|
{EXILE_SYS(execve), EXILE_SYSCALL_VOW_EXEC},
|
|
{EXILE_SYS(exit), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(wait4), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(kill), EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(uname), EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(semget), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(semop), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(semctl), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(shmdt), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(msgget), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(msgsnd), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(msgrcv), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(msgctl), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(fcntl), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(flock), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(fsync), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(fdatasync), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(truncate), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(ftruncate), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getdents), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(getcwd), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(chdir), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(fchdir), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(rename), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(mkdir), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(rmdir), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(creat), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(link), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(unlink), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(symlink), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(readlink), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(chmod), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(fchmod), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(chown), EXILE_SYSCALL_VOW_CHOWN},
|
|
{EXILE_SYS(fchown), EXILE_SYSCALL_VOW_CHOWN},
|
|
{EXILE_SYS(lchown), EXILE_SYSCALL_VOW_CHOWN},
|
|
{EXILE_SYS(umask), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(gettimeofday), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getrlimit), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getrusage), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(sysinfo), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(times), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getuid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getgid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setuid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(setgid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(geteuid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getegid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setpgid), EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(getppid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getpgrp), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setsid), EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(setreuid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(setregid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(getgroups), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setgroups), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(setresuid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(getresuid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setresgid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(getresgid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getpgid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setfsuid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(setfsgid), EXILE_SYSCALL_VOW_ID},
|
|
{EXILE_SYS(getsid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(capget), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(rt_sigpending), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(rt_sigtimedwait), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(rt_sigqueueinfo), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(rt_sigsuspend), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(utime), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(mknod), EXILE_SYSCALL_VOW_DPATH},
|
|
{EXILE_SYS(uselib), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(ustat), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(statfs), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(fstatfs), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(getpriority), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(setpriority), EXILE_SYSCALL_VOW_SCHED|EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(sched_setparam), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(sched_getparam), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(sched_setscheduler), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(sched_getscheduler), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(sched_get_priority_max), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(sched_get_priority_min), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(sched_rr_get_interval), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(mlock), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(munlock), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mlockall), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(munlockall), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(vhangup), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(prctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PRCTL|EXILE_SYSCALL_VOW_SECCOMP_INSTALL},
|
|
{EXILE_SYS(arch_prctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PRCTL},
|
|
{EXILE_SYS(setrlimit), EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(sync), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(gettid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(readahead), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(setxattr), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(lsetxattr), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(fsetxattr), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(getxattr), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(lgetxattr), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(fgetxattr), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(listxattr), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(llistxattr), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(flistxattr), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(removexattr), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(lremovexattr), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(fremovexattr), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(tkill), EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(time), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(futex), EXILE_SYSCALL_VOW_THREAD},
|
|
{EXILE_SYS(sched_getaffinity), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(set_thread_area), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(get_thread_area), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(lookup_dcookie), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(epoll_create), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(epoll_ctl_old), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(epoll_wait_old), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(remap_file_pages), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getdents64), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(set_tid_address), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(semtimedop), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(fadvise64), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(timer_create), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(timer_settime), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(timer_gettime), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(timer_getoverrun), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(timer_delete), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(clock_gettime), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(clock_getres), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(clock_nanosleep), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(exit_group), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(epoll_wait), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(epoll_ctl), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(tgkill), EXILE_SYSCALL_VOW_PROC},
|
|
{EXILE_SYS(utimes), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(mbind), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(get_mempolicy), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mq_open), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mq_unlink), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mq_timedsend), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mq_timedreceive), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mq_notify), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(mq_getsetattr), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(waitid), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(inotify_init), EXILE_SYSCALL_VOW_FSNOTIFY},
|
|
{EXILE_SYS(inotify_add_watch), EXILE_SYSCALL_VOW_FSNOTIFY},
|
|
{EXILE_SYS(inotify_rm_watch), EXILE_SYSCALL_VOW_FSNOTIFY},
|
|
{EXILE_SYS(openat), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH},
|
|
{EXILE_SYS(mkdirat), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(mknodat), EXILE_SYSCALL_VOW_DPATH},
|
|
{EXILE_SYS(fchownat), EXILE_SYSCALL_VOW_CHOWN},
|
|
{EXILE_SYS(futimesat), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(newfstatat), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(unlinkat), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(renameat), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(linkat), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(symlinkat), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(readlinkat), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(fchmodat), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(faccessat), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(pselect6), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(ppoll), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(set_robust_list), EXILE_SYSCALL_VOW_THREAD},
|
|
{EXILE_SYS(get_robust_list), EXILE_SYSCALL_VOW_THREAD},
|
|
{EXILE_SYS(splice), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(tee), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(sync_file_range), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(vmsplice), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(move_pages), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(utimensat), EXILE_SYSCALL_VOW_FATTR},
|
|
{EXILE_SYS(epoll_pwait), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(signalfd), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(timerfd_create), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(eventfd), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(fallocate), EXILE_SYSCALL_VOW_WPATH|EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(timerfd_settime), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(timerfd_gettime), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(accept4), EXILE_SYSCALL_VOW_UNIX|EXILE_SYSCALL_VOW_INET},
|
|
{EXILE_SYS(signalfd4), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(eventfd2), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(epoll_create1), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(dup3), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(pipe2), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(inotify_init1), EXILE_SYSCALL_VOW_FSNOTIFY},
|
|
{EXILE_SYS(preadv), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(pwritev), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(recvmmsg), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(fanotify_init), EXILE_SYSCALL_VOW_FSNOTIFY},
|
|
{EXILE_SYS(fanotify_mark), EXILE_SYSCALL_VOW_FSNOTIFY},
|
|
{EXILE_SYS(prlimit64), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(open_by_handle_at), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(sendmmsg), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(getcpu), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(sched_setattr), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(sched_getattr), EXILE_SYSCALL_VOW_SCHED},
|
|
{EXILE_SYS(renameat2), EXILE_SYSCALL_VOW_CPATH},
|
|
{EXILE_SYS(getrandom), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(execveat), EXILE_SYSCALL_VOW_EXEC},
|
|
{EXILE_SYS(mlock2), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(copy_file_range), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(statx), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(clone3), EXILE_SYSCALL_VOW_CLONE},
|
|
{EXILE_SYS(close_range), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(openat2), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH},
|
|
{EXILE_SYS(faccessat2), EXILE_SYSCALL_VOW_RPATH},
|
|
{EXILE_SYS(process_madvise), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(epoll_pwait2), EXILE_SYSCALL_VOW_STDIO},
|
|
{EXILE_SYS(futex_waitv), EXILE_SYSCALL_VOW_THREAD}
|
|
};
|
|
|
|
struct str_to_vow_map str_to_vow_map[] =
|
|
{
|
|
{ "chown", EXILE_SYSCALL_VOW_CHOWN},
|
|
{ "clone", EXILE_SYSCALL_VOW_CLONE},
|
|
{ "cpath", EXILE_SYSCALL_VOW_CPATH},
|
|
{ "dpath", EXILE_SYSCALL_VOW_DPATH},
|
|
{ "exec", EXILE_SYSCALL_VOW_EXEC},
|
|
{ "fattr", EXILE_SYSCALL_VOW_FATTR},
|
|
{ "fsnotify", EXILE_SYSCALL_VOW_FSNOTIFY},
|
|
{ "id", EXILE_SYSCALL_VOW_ID},
|
|
{ "inet", EXILE_SYSCALL_VOW_INET},
|
|
{ "ioctl", EXILE_SYSCALL_VOW_IOCTL},
|
|
{ "prctl", EXILE_SYSCALL_VOW_PRCTL},
|
|
{ "proc", EXILE_SYSCALL_VOW_PROC},
|
|
{ "prot_exec", EXILE_SYSCALL_VOW_PROT_EXEC},
|
|
{ "rpath", EXILE_SYSCALL_VOW_RPATH},
|
|
{ "sched", EXILE_SYSCALL_VOW_SCHED},
|
|
{ "seccomp_install", EXILE_SYSCALL_VOW_SECCOMP_INSTALL},
|
|
{ "shm", EXILE_SYSCALL_VOW_SHM},
|
|
{ "stdio", EXILE_SYSCALL_VOW_STDIO},
|
|
{ "thread", EXILE_SYSCALL_VOW_THREAD},
|
|
{ "unix", EXILE_SYSCALL_VOW_UNIX},
|
|
{ "wpath", EXILE_SYSCALL_VOW_WPATH},
|
|
{ "error", EXILE_SYSCALL_VOW_DENY_ERROR}
|
|
};
|
|
|
|
/* Converts the whitespace separated vows strings to vows flags
|
|
*
|
|
* This mainly helps readability, as lots of flags ORed together is not
|
|
* very readable.
|
|
*
|
|
* If an unkown string is found, abort() is called.
|
|
*/
|
|
uint64_t exile_vows_from_str(const char *str)
|
|
{
|
|
uint64_t result = 0;
|
|
char current[64] = { 0 };
|
|
char *ptr = current;
|
|
const char *end = ptr + sizeof(current)-1;
|
|
do
|
|
{
|
|
while(ptr <= end && *str != '\0' && *str != ' ')
|
|
{
|
|
*ptr = *str;
|
|
++ptr;
|
|
++str;
|
|
}
|
|
int found = 0;
|
|
for(size_t i = 0; i < sizeof(str_to_vow_map)/sizeof(str_to_vow_map[0]); i++)
|
|
{
|
|
if(strcmp(str_to_vow_map[i].str, current) == 0)
|
|
{
|
|
result |= str_to_vow_map[i].value;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(!found)
|
|
{
|
|
EXILE_LOG_ERROR("No such vow: %s\n", current);
|
|
abort();
|
|
}
|
|
memset(current, 0, sizeof(current));
|
|
ptr = current;
|
|
} while(*str++ != '\0');
|
|
return result;
|
|
}
|
|
|
|
inline int exile_landlock_is_available()
|
|
{
|
|
#if HAVE_LANDLOCK == 1
|
|
int ruleset = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
|
return ruleset == 1;
|
|
#endif
|
|
return 0;
|
|
}
|
|
int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n)
|
|
{
|
|
struct exile_syscall_policy *newpolicy = (struct exile_syscall_policy *) calloc(1, sizeof(struct exile_syscall_policy));
|
|
if(newpolicy == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to allocate memory for syscall policy\n");
|
|
exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL;
|
|
return -1;
|
|
}
|
|
newpolicy->policy = syscall_policy;
|
|
newpolicy->syscall = syscall;
|
|
newpolicy->argfilterscount = n;
|
|
if(n > EXILE_ARGFILTERS_COUNT)
|
|
{
|
|
EXILE_LOG_ERROR("Too many argfilters supplied\n");
|
|
exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL;
|
|
return -1;
|
|
}
|
|
for(size_t i = 0; i < n; i++)
|
|
{
|
|
newpolicy->argfilters[i] = argfilters[i];
|
|
}
|
|
newpolicy->next = NULL;
|
|
|
|
*(exile_policy->syscall_policies_tail) = newpolicy;
|
|
exile_policy->syscall_policies_tail = &(newpolicy->next);
|
|
|
|
exile_policy->disable_syscall_filter = 0;
|
|
return 0;
|
|
}
|
|
|
|
int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy)
|
|
{
|
|
return exile_append_syscall_policy(exile_policy, EXILE_SYSCALL_MATCH_ALL, default_policy, NULL, 0);
|
|
}
|
|
|
|
#define COUNT_EXILE_SYSCALL_FILTER(f) \
|
|
sizeof(f)/sizeof(f[0])
|
|
|
|
#define EXILE_SYSCALL_FILTER_LOAD_ARG(val) \
|
|
{ 0, EXILE_BPF_LOAD_SECCOMP_ARG(val), 0}
|
|
|
|
/* Returns, for the specific syscall, the correct sock_filter struct for the provided vow_promises
|
|
*
|
|
* Returns: 0 if none copied, otherwise the number of entries in "filter".
|
|
*/
|
|
int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy)
|
|
{
|
|
|
|
/* How to read this:
|
|
* Keep in mind our default action is to deny, unless it's a syscall from a vow promise. Then it will be
|
|
* accepted if the argument values are good (if we care about them at all).
|
|
* EXILE_BPF_MATCH() means the argument value is good, and the syscall can be accepted without further checks
|
|
* EXILE_BPF_NO_MATCH() means the syscall won't be allowed because the value is illegal
|
|
*
|
|
* First field (vowmask): The mask to check
|
|
* Last field (whenset): If mask is set in vow_promises, then add this filter, otherwise don't.
|
|
*/
|
|
|
|
struct exile_syscall_filter mmap_filter[] = {
|
|
EXILE_SYSCALL_FILTER_LOAD_ARG(2),
|
|
{ EXILE_SYSCALL_VOW_PROT_EXEC, EXILE_BPF_NO_MATCH_SET(PROT_EXEC), 0},
|
|
};
|
|
|
|
|
|
struct exile_syscall_filter ioctl_filter[] = {
|
|
EXILE_SYSCALL_FILTER_LOAD_ARG(1),
|
|
{ EXILE_SYSCALL_VOW_IOCTL, EXILE_BPF_RETURN_MATCHING, 1 },
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONBIO), 1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIOCLEX), 1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONCLEX), 1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_RETURN_NOT_MATCHING, 1}
|
|
};
|
|
|
|
struct exile_syscall_filter open_filter[] = {
|
|
EXILE_SYSCALL_FILTER_LOAD_ARG(1),
|
|
{ EXILE_SYSCALL_VOW_CPATH, EXILE_BPF_NO_MATCH_SET(O_CREAT), 0 },
|
|
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_TMPFILE),0 },
|
|
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_WRONLY),0 },
|
|
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_RDWR),0 },
|
|
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_APPEND),0 },
|
|
};
|
|
|
|
struct exile_syscall_filter socket_filter[] = {
|
|
EXILE_SYSCALL_FILTER_LOAD_ARG(0),
|
|
{ EXILE_SYSCALL_VOW_UNIX, EXILE_BPF_MATCH(AF_UNIX), 1 },
|
|
{ EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET), 1 },
|
|
{ EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET6), 1 },
|
|
{ 0, EXILE_BPF_RETURN_NOT_MATCHING, 0}
|
|
};
|
|
|
|
struct exile_syscall_filter setsockopt_filter[] = {
|
|
EXILE_SYSCALL_FILTER_LOAD_ARG(2),
|
|
{ 0, EXILE_BPF_NO_MATCH(SO_DEBUG), 0 },
|
|
{ 0, EXILE_BPF_NO_MATCH(SO_SNDBUFFORCE), 0 }
|
|
};
|
|
|
|
|
|
struct exile_syscall_filter clone_filter[] = {
|
|
/* It's the first (0) argument for x86_64 */
|
|
EXILE_SYSCALL_FILTER_LOAD_ARG(0),
|
|
{ EXILE_SYSCALL_VOW_CLONE, EXILE_BPF_RETURN_MATCHING, 1 },
|
|
{ EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_VM, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1},
|
|
{ EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_THREAD, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1},
|
|
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWCGROUP), 0},
|
|
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWIPC),0},
|
|
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNET),0},
|
|
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNS),0},
|
|
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWPID),0},
|
|
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUSER),0},
|
|
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUTS),0},
|
|
};
|
|
|
|
|
|
struct exile_syscall_filter prctl_filter[] ={
|
|
EXILE_SYSCALL_FILTER_LOAD_ARG(0),
|
|
{ EXILE_SYSCALL_VOW_PRCTL, EXILE_BPF_RETURN_MATCHING, 1},
|
|
{ EXILE_SYSCALL_VOW_SECCOMP_INSTALL, EXILE_BPF_MATCH(PR_SET_SECCOMP), 1 },
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NO_NEW_PRIVS),1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NO_NEW_PRIVS),1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NAME),1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NAME),1},
|
|
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_CAPBSET_READ), 1},
|
|
{ 0, EXILE_BPF_RETURN_NOT_MATCHING, 0}
|
|
};
|
|
|
|
struct exile_syscall_filter *current_filter = NULL;
|
|
size_t current_count = 0;
|
|
|
|
*policy = EXILE_SYSCALL_ALLOW;
|
|
switch(syscall)
|
|
{
|
|
case EXILE_SYS(mmap):
|
|
case EXILE_SYS(mprotect):
|
|
current_filter = mmap_filter;
|
|
current_count = COUNT_EXILE_SYSCALL_FILTER(mmap_filter);
|
|
break;
|
|
case EXILE_SYS(ioctl):
|
|
current_filter = ioctl_filter;
|
|
current_count = COUNT_EXILE_SYSCALL_FILTER(ioctl_filter);
|
|
break;
|
|
case EXILE_SYS(open):
|
|
case EXILE_SYS(openat):
|
|
case EXILE_SYS(open_by_handle_at):
|
|
if(syscall == EXILE_SYS(openat) || syscall == EXILE_SYS(open_by_handle_at))
|
|
{
|
|
/* for openat, it's the third arg */
|
|
open_filter[0] = (struct exile_syscall_filter) EXILE_SYSCALL_FILTER_LOAD_ARG(2);
|
|
}
|
|
current_filter = open_filter;
|
|
current_count = COUNT_EXILE_SYSCALL_FILTER(open_filter);
|
|
break;
|
|
case EXILE_SYS(openat2):
|
|
*policy = EXILE_SYSCALL_DENY_RET_ERROR;
|
|
return 0;
|
|
break;
|
|
case EXILE_SYS(socket):
|
|
current_filter = socket_filter;
|
|
current_count = COUNT_EXILE_SYSCALL_FILTER(socket_filter);
|
|
break;
|
|
case EXILE_SYS(setsockopt):
|
|
current_filter = setsockopt_filter;
|
|
current_count = COUNT_EXILE_SYSCALL_FILTER(setsockopt_filter);
|
|
break;
|
|
case EXILE_SYS(clone):
|
|
current_filter = clone_filter;
|
|
current_count = COUNT_EXILE_SYSCALL_FILTER(clone_filter);
|
|
break;
|
|
case EXILE_SYS(clone3):
|
|
if((vow_promises & EXILE_SYSCALL_VOW_CLONE) == 0)
|
|
{
|
|
*policy = EXILE_SYSCALL_DENY_RET_ERROR;
|
|
return 0;
|
|
}
|
|
break;
|
|
case EXILE_SYS(prctl):
|
|
current_filter = prctl_filter;
|
|
current_count = COUNT_EXILE_SYSCALL_FILTER(prctl_filter);
|
|
break;
|
|
}
|
|
|
|
int out_filter_index = 0;
|
|
for(size_t i = 0; i < current_count; i++)
|
|
{
|
|
struct exile_syscall_filter *c = ¤t_filter[i];
|
|
int set = 0;
|
|
if(c->vowmask & vow_promises)
|
|
{
|
|
set = 1;
|
|
}
|
|
if(c->whenset == set || c->vowmask == 0)
|
|
{
|
|
filter[out_filter_index++] = c->filter;
|
|
}
|
|
}
|
|
return out_filter_index;
|
|
}
|
|
|
|
int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises)
|
|
{
|
|
for(unsigned int i = 0; i < sizeof(exile_vow_map)/sizeof(exile_vow_map[0]); i++)
|
|
{
|
|
struct syscall_vow_map *current_map = &exile_vow_map[i];
|
|
if(current_map->vowmask & vow_promises)
|
|
{
|
|
struct sock_filter filter[EXILE_ARGFILTERS_COUNT];
|
|
long syscall = current_map->syscall;
|
|
int syscall_policy = EXILE_SYSCALL_ALLOW;
|
|
int argfilters = get_vow_argfilter(syscall, vow_promises, filter, &syscall_policy);
|
|
int ret = exile_append_syscall_policy(policy, syscall, syscall_policy, filter, argfilters);
|
|
if(ret != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed adding syscall policy from vow while processing %li\n", syscall);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
int vow_policy = (vow_promises & EXILE_SYSCALL_VOW_DENY_ERROR) ? EXILE_SYSCALL_DENY_RET_ERROR : EXILE_SYSCALL_DENY_KILL_PROCESS;
|
|
return exile_append_syscall_default_policy(policy, vow_policy);
|
|
}
|
|
|
|
/* Creates an empty policy struct without opinionated defaults.
|
|
*
|
|
* Must be freed using exile_free_policy()
|
|
* @returns: empty policy
|
|
*/
|
|
struct exile_policy *exile_create_policy()
|
|
{
|
|
struct exile_policy *result = (struct exile_policy *) calloc(1, sizeof(struct exile_policy));
|
|
if(result == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to allocate memory for policy\n");
|
|
return NULL;
|
|
}
|
|
result->path_policies_tail = &(result->path_policies);
|
|
result->syscall_policies_tail = &(result->syscall_policies);
|
|
return result;
|
|
}
|
|
|
|
/* Creates the default policy
|
|
* Must be freed using exile_free_policy()
|
|
*
|
|
* @returns: default policy
|
|
*/
|
|
struct exile_policy *exile_init_policy()
|
|
{
|
|
struct exile_policy *result = exile_create_policy();
|
|
if(result == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
result->drop_caps = 1;
|
|
result->not_dumpable = 1;
|
|
result->no_new_privs = 1;
|
|
result->namespace_options = EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_USER;
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Appends path policies to the exile_policy object
|
|
* The last paramater must be NULL
|
|
*
|
|
* This function does not copy parameters. All passed paths
|
|
* MUST NOT be freed until exile_enable_policy() is called!
|
|
*
|
|
* @returns: 0 on success, -1 on failure */
|
|
int (exile_append_path_policies)(struct exile_policy *exile_policy, unsigned int path_policy, ...)
|
|
{
|
|
va_list args;
|
|
const char *path;
|
|
va_start(args, path_policy);
|
|
|
|
path = va_arg(args, char*);
|
|
while(path != NULL)
|
|
{
|
|
int fd = open(path, O_PATH);
|
|
if(fd == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to open the specified path: %s\n", strerror(errno));
|
|
exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL;
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
struct exile_path_policy *newpolicy = (struct exile_path_policy *) calloc(1, sizeof(struct exile_path_policy));
|
|
if(newpolicy == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to allocate memory for path policy\n");
|
|
exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL;
|
|
return -1;
|
|
}
|
|
newpolicy->path = path;
|
|
newpolicy->policy = path_policy;
|
|
newpolicy->next = NULL;
|
|
|
|
*(exile_policy->path_policies_tail) = newpolicy;
|
|
exile_policy->path_policies_tail = &(newpolicy->next);
|
|
path = va_arg(args, char*);
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Fills buffer with random characters a-z.
|
|
* The string will be null terminated.
|
|
*
|
|
* @returns: number of written chars (excluding terminating null byte) on success
|
|
*/
|
|
int random_string(char *buffer, size_t buffer_length)
|
|
{
|
|
int r = getrandom(buffer, buffer_length-1, GRND_NONBLOCK);
|
|
if(r != -1 && (size_t) r == buffer_length-1)
|
|
{
|
|
int i = 0;
|
|
while(i < r)
|
|
{
|
|
buffer[i] = 'a' + ((unsigned int)buffer[i] % 26);
|
|
++i;
|
|
}
|
|
buffer[buffer_length-1] = '\0';
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Creates a directory/file and all necessary parent directories
|
|
* @returns: 0 on success, -ERRNO on failure
|
|
*/
|
|
int mkpath(const char *p, mode_t mode, int baseisfile)
|
|
{
|
|
char path[PATH_MAX + 1] = {0};
|
|
int ret = snprintf(path, sizeof(path), "%s%c", p, (baseisfile) ? '\0' : '/');
|
|
if(ret < 0)
|
|
{
|
|
EXILE_LOG_ERROR("error during path concatination\n");
|
|
return -EINVAL;
|
|
}
|
|
if((size_t)ret >= sizeof(path))
|
|
{
|
|
EXILE_LOG_ERROR("path concatination truncated\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
char *begin = path;
|
|
char *end = begin + 1;
|
|
|
|
while(*end)
|
|
{
|
|
if(*end == '/')
|
|
{
|
|
*end = 0;
|
|
if(mkdir(begin, mode) < 0)
|
|
{
|
|
if(errno != EEXIST)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to create directory: %s\n", begin);
|
|
return -1;
|
|
}
|
|
}
|
|
*end = '/';
|
|
while(*end == '/')
|
|
{
|
|
++end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++end;
|
|
}
|
|
}
|
|
if(baseisfile)
|
|
{
|
|
ret = creat(p, mode);
|
|
if(ret == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to create file: %s\n", begin);
|
|
return ret;
|
|
}
|
|
close(ret);
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* @returns: argument for mount(2) flags */
|
|
static int get_policy_mount_flags(struct exile_path_policy *policy)
|
|
{
|
|
int result = 0;
|
|
|
|
if( (policy->policy & EXILE_FS_ALLOW_DEV) == 0)
|
|
{
|
|
result |= MS_NODEV;
|
|
}
|
|
|
|
if( (policy->policy & EXILE_FS_ALLOW_EXEC) == 0)
|
|
{
|
|
result |= MS_NOEXEC;
|
|
}
|
|
|
|
if( (policy->policy & EXILE_FS_ALLOW_SETUID) == 0)
|
|
{
|
|
result |= MS_NOSUID;
|
|
}
|
|
|
|
if( (policy->policy & EXILE_FS_ALLOW_ALL_WRITE) == 0)
|
|
{
|
|
result |= MS_RDONLY;
|
|
}
|
|
|
|
if( (policy->policy & EXILE_MOUNT_NOT_REC) == 0)
|
|
{
|
|
result |= MS_REC;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int path_policy_needs_landlock(struct exile_path_policy *path_policy)
|
|
{
|
|
unsigned int policy = path_policy->policy;
|
|
#if HAVE_LANDLOCK == 1
|
|
if(policy >= EXILE_FS_ALLOW_REMOVE_DIR)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
//Can't need it if we don't have support at compile time
|
|
return 0;
|
|
}
|
|
|
|
/* TODO: we can do va_args */
|
|
char *concat_path(const char *first, const char *second)
|
|
{
|
|
char *result = (char *) calloc(1, PATH_MAX);
|
|
if(result == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("calloc failed\n");
|
|
return NULL;
|
|
}
|
|
//TODO: We can strip multiple redundant slashes
|
|
int written = snprintf(result, PATH_MAX, "%s/%s", first, second);
|
|
if(written < 0)
|
|
{
|
|
EXILE_LOG_ERROR("Error during path concatination\n");
|
|
return NULL;
|
|
}
|
|
if(written >= PATH_MAX)
|
|
{
|
|
EXILE_LOG_ERROR("path concatination truncated\n");
|
|
return NULL;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Creates the file system hierarchy for the chroot
|
|
* @returns: 0 on sucess, -ERRNO on failure */
|
|
static int create_chroot_dirs(const char *chroot_target_path, struct exile_path_policy *path_policy)
|
|
{
|
|
while(path_policy != NULL)
|
|
{
|
|
struct stat sb;
|
|
int ret = stat(path_policy->path, &sb);
|
|
if(ret < 0)
|
|
{
|
|
EXILE_LOG_ERROR("stat failed\n");
|
|
return ret;
|
|
}
|
|
|
|
int baseisfile = 0;
|
|
if(S_ISREG(sb.st_mode))
|
|
{
|
|
baseisfile = 1;
|
|
}
|
|
|
|
char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path);
|
|
if(path_inside_chroot == NULL)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ret = mkpath(path_inside_chroot, 0700, baseisfile);
|
|
if(ret < 0)
|
|
{
|
|
EXILE_LOG_ERROR("Error creating directory structure while mounting paths to chroot. %s\n", strerror(errno));
|
|
free(path_inside_chroot);
|
|
return ret;
|
|
}
|
|
path_policy = path_policy->next;
|
|
free(path_inside_chroot);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int perform_mounts(const char *chroot_target_path, struct exile_path_policy *path_policy)
|
|
{
|
|
while(path_policy != NULL)
|
|
{
|
|
int mount_flags = get_policy_mount_flags(path_policy);
|
|
|
|
char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path);
|
|
if(path_inside_chroot == NULL)
|
|
{
|
|
return 1;
|
|
}
|
|
//all we do is bind mounts
|
|
mount_flags |= MS_BIND;
|
|
|
|
if(path_policy->policy & EXILE_FS_ALLOW_ALL_READ || path_policy->policy & EXILE_FS_ALLOW_ALL_WRITE)
|
|
{
|
|
int ret = mount(path_policy->path, path_inside_chroot, NULL, mount_flags, NULL);
|
|
if(ret < 0 )
|
|
{
|
|
EXILE_LOG_ERROR("Failed to mount %s to %s: %s\n", path_policy->path, path_inside_chroot, strerror(errno));
|
|
free(path_inside_chroot);
|
|
return ret;
|
|
}
|
|
|
|
//remount so noexec, readonly etc. take effect
|
|
ret = mount(NULL, path_inside_chroot, NULL, mount_flags | MS_REMOUNT, NULL);
|
|
if(ret < 0 )
|
|
{
|
|
EXILE_LOG_ERROR("Failed to remount %s: %s\n", path_inside_chroot, strerror(errno));
|
|
free(path_inside_chroot);
|
|
return ret;
|
|
}
|
|
path_policy = path_policy->next;
|
|
free(path_inside_chroot);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Frees the memory taken by a exile_policy object
|
|
*/
|
|
void exile_free_policy(struct exile_policy *ctxt)
|
|
{
|
|
if(ctxt != NULL)
|
|
{
|
|
struct exile_path_policy *current = ctxt->path_policies;
|
|
while(current != NULL)
|
|
{
|
|
struct exile_path_policy *tmp = current;
|
|
current = current->next;
|
|
free(tmp);
|
|
}
|
|
|
|
struct exile_syscall_policy *sc_policy = ctxt->syscall_policies;
|
|
while(sc_policy != NULL)
|
|
{
|
|
struct exile_syscall_policy *tmp = sc_policy;
|
|
sc_policy = sc_policy->next;
|
|
free(tmp);
|
|
}
|
|
free(ctxt);
|
|
}
|
|
}
|
|
|
|
/* Enters the specified namespaces */
|
|
static int enter_namespaces(int namespace_options)
|
|
{
|
|
if(namespace_options & EXILE_UNSHARE_USER)
|
|
{
|
|
int ret = unshare(CLONE_NEWUSER);
|
|
if(ret == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to unshare user namespaces: %s\n", strerror(errno));
|
|
return ret;
|
|
}
|
|
|
|
uid_t current_uid = getuid();
|
|
gid_t current_gid = getgid();
|
|
|
|
FILE *fp = fopen("/proc/self/setgroups", "w");
|
|
if(fp == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("fopen failed while trying to deny setgroups\n");
|
|
return -1;
|
|
}
|
|
if(fprintf(fp, "deny") < 0)
|
|
{
|
|
EXILE_LOG_ERROR("fprintf failed while trying to write setgroups\n");
|
|
return -1;
|
|
}
|
|
fclose(fp);
|
|
|
|
fp = fopen("/proc/self/uid_map", "w");
|
|
if(fp == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("fopen failed while trying to write uid_map\n");
|
|
return -1;
|
|
}
|
|
if(fprintf(fp, "0 %i", current_uid) < 0)
|
|
{
|
|
EXILE_LOG_ERROR("fprintf failed while trying to write uid_map\n");
|
|
return -1;
|
|
}
|
|
fclose(fp);
|
|
|
|
fp = fopen("/proc/self/gid_map", "w");
|
|
if(fp == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("fopen failed while trying to write gid_map\n");
|
|
return -1;
|
|
}
|
|
if(fprintf(fp, "0 %i", current_gid) < 0)
|
|
{
|
|
EXILE_LOG_ERROR("fprintf failed while trying to write gid_map\n");
|
|
return -1;
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
if(namespace_options & EXILE_UNSHARE_MOUNT)
|
|
{
|
|
int ret = unshare(CLONE_NEWNS);
|
|
if(ret == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to unshare mount namespaces: %s\n", strerror(errno));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if(namespace_options & EXILE_UNSHARE_NETWORK)
|
|
{
|
|
int ret = unshare(CLONE_NEWNET);
|
|
if(ret == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to unshare network namespace: %s\n", strerror(errno));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Drops all capabiltiies held by the process
|
|
*
|
|
* @returns: 0 on sucess, -1 on error
|
|
*/
|
|
static int drop_caps()
|
|
{
|
|
int cap = 0;
|
|
int res = 0;
|
|
while((res = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) == 0)
|
|
{
|
|
++cap;
|
|
}
|
|
|
|
if(res == -1 && errno != EINVAL)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to drop the capability bounding set!\n");
|
|
return -errno;
|
|
}
|
|
|
|
//TODO: systems that are not 64 bit
|
|
struct __user_cap_header_struct h = { 0 };
|
|
h.pid = 0;
|
|
h.version = _LINUX_CAPABILITY_VERSION_3;
|
|
struct __user_cap_data_struct drop[2];
|
|
drop[0].effective = 0;
|
|
drop[0].permitted = 0;
|
|
drop[0].inheritable = 0;
|
|
drop[1].effective = 0;
|
|
drop[1].permitted = 0;
|
|
drop[1].inheritable = 0;
|
|
if(capset(&h, drop) == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to drop capabilities: %s\n", strerror(errno));
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, struct sock_filter *filter, unsigned short int *start_index)
|
|
{
|
|
unsigned int action = syscallpolicy->policy;
|
|
if(action == EXILE_SYSCALL_ALLOW)
|
|
{
|
|
action = SECCOMP_RET_ALLOW;
|
|
}
|
|
if(action == EXILE_SYSCALL_DENY_KILL_PROCESS)
|
|
{
|
|
action = SECCOMP_RET_KILL_PROCESS;
|
|
}
|
|
if(action == EXILE_SYSCALL_DENY_RET_ERROR)
|
|
{
|
|
action = SECCOMP_RET_ERRNO|EACCES;
|
|
}
|
|
long syscall = syscallpolicy->syscall;
|
|
|
|
struct sock_filter syscall_load = BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr));
|
|
filter[(*start_index)++] = syscall_load;
|
|
if(syscall != EXILE_SYSCALL_MATCH_ALL)
|
|
{
|
|
/* How many steps forward to jump when we don't match. This is either the last statement,
|
|
* i. e. the default action or the next syscall policy */
|
|
__u8 next_syscall_pc = 1;
|
|
if(__builtin_add_overflow(next_syscall_pc, syscallpolicy->argfilterscount, &next_syscall_pc))
|
|
{
|
|
EXILE_LOG_ERROR("Overflow while trying to calculate jump offset\n");
|
|
/* TODO: Return error */
|
|
return;
|
|
}
|
|
struct sock_filter syscall_check = EXILE_BPF_CMP_EQ((unsigned int) syscall, 0, next_syscall_pc);
|
|
filter[(*start_index)++] = syscall_check;
|
|
--next_syscall_pc;
|
|
|
|
struct sock_filter return_matching = EXILE_BPF_RETURN_MATCHING;
|
|
struct sock_filter return_not_matching = EXILE_BPF_RETURN_NOT_MATCHING;
|
|
|
|
for(size_t i = 0; i < syscallpolicy->argfilterscount; i++)
|
|
{
|
|
filter[*start_index] = syscallpolicy->argfilters[i];
|
|
struct sock_filter *current = &filter[*start_index];
|
|
__u8 jump_count_next_syscall = next_syscall_pc;
|
|
__u8 jump_count_return = jump_count_next_syscall - 1;
|
|
if(current->jt == EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
|
|
{
|
|
current->jt = jump_count_next_syscall;
|
|
}
|
|
if(current->jt == EXILE_SYSCALL_EXIT_BPF_RETURN)
|
|
{
|
|
current->jt = jump_count_return;
|
|
}
|
|
if(current->jf == EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
|
|
{
|
|
current->jf = jump_count_next_syscall;
|
|
}
|
|
if(current->jf == EXILE_SYSCALL_EXIT_BPF_RETURN)
|
|
{
|
|
current->jf = jump_count_return;
|
|
}
|
|
if(current->code == return_matching.code && current->k == return_matching.k)
|
|
{
|
|
current->k = jump_count_return;
|
|
}
|
|
if(current->code == return_not_matching.code && current->k == return_not_matching.k)
|
|
{
|
|
current->k = jump_count_next_syscall;
|
|
}
|
|
--next_syscall_pc;
|
|
++*start_index;
|
|
}
|
|
}
|
|
struct sock_filter syscall_action = BPF_STMT(BPF_RET+BPF_K, action);
|
|
/* TODO: we can do better than adding this below every jump */
|
|
filter[(*start_index)++] = syscall_action;
|
|
|
|
}
|
|
|
|
static int is_valid_syscall_policy(unsigned int policy)
|
|
{
|
|
return policy == EXILE_SYSCALL_ALLOW || policy == EXILE_SYSCALL_DENY_RET_ERROR || policy == EXILE_SYSCALL_DENY_KILL_PROCESS;
|
|
}
|
|
|
|
/*
|
|
* Enables the seccomp policy
|
|
*
|
|
* policy: exile policy object
|
|
*
|
|
* @returns: 0 on success, -1 on error
|
|
*/
|
|
|
|
int exile_enable_syscall_policy(struct exile_policy *policy)
|
|
{
|
|
struct sock_filter filter[1024] =
|
|
{
|
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,offsetof(struct seccomp_data, arch)),
|
|
BPF_JUMP (BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_AUDIT_ARCH, 1, 0),
|
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS),
|
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),
|
|
BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, __X32_SYSCALL_BIT, 0, 1),
|
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS),
|
|
};
|
|
|
|
unsigned short int current_filter_index = 6;
|
|
|
|
struct exile_syscall_policy *current_policy = policy->syscall_policies;
|
|
while(current_policy)
|
|
{
|
|
if(!is_valid_syscall_policy(current_policy->policy))
|
|
{
|
|
EXILE_LOG_ERROR("invalid syscall policy specified\n");
|
|
return -1;
|
|
}
|
|
/* TODO: reintroduce overflow checks */
|
|
append_syscall_to_bpf(current_policy, filter, ¤t_filter_index);
|
|
current_policy = current_policy->next;
|
|
}
|
|
|
|
struct sock_fprog prog = {
|
|
.len = current_filter_index ,
|
|
.filter = filter,
|
|
};
|
|
|
|
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1)
|
|
{
|
|
EXILE_LOG_ERROR("prctl SET_SECCOMP %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if HAVE_LANDLOCK == 1
|
|
static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
|
|
{
|
|
unsigned int result = 0;
|
|
if(flags & EXILE_FS_ALLOW_ALL_READ)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_READ_FILE;
|
|
if(S_ISDIR(statmode))
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_READ_DIR;
|
|
}
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_ALL_WRITE)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_WRITE_FILE;
|
|
if(S_ISDIR(statmode))
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_REMOVE_FILE;
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_REG;
|
|
result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_SYM;
|
|
}
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_EXEC)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_EXECUTE;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_WRITE_FILE)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_WRITE_FILE;
|
|
}
|
|
if(S_ISDIR(statmode))
|
|
{
|
|
if(flags & EXILE_FS_ALLOW_DEV)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_BLOCK;
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_CHAR;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_MAKE_BLOCK)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_BLOCK;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_MAKE_CHAR)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_CHAR;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_MAKE_DIR)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_DIR;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_MAKE_FIFO)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_FIFO;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_MAKE_REG)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_REG;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_MAKE_SOCK)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_SOCK;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_MAKE_SYM)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_MAKE_SYM;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_REMOVE)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
|
|
result |= LANDLOCK_ACCESS_FS_REMOVE_FILE;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_REMOVE_DIR)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_REMOVE_FILE)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_REMOVE_FILE;
|
|
}
|
|
if(flags & EXILE_FS_ALLOW_READ_DIR)
|
|
{
|
|
result |= LANDLOCK_ACCESS_FS_READ_DIR;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int landlock_prepare_ruleset(struct exile_path_policy *policies)
|
|
{
|
|
int ruleset_fd = -1;
|
|
struct landlock_ruleset_attr ruleset_attr;
|
|
/* We here want the maximum possible ruleset, so set the var to the max possible bitmask.
|
|
Stolen/Adapted from: [linux src]/security/landlock/limits.h
|
|
*/
|
|
ruleset_attr.handled_access_fs = ((LANDLOCK_ACCESS_FS_MAKE_SYM << 1) - 1);
|
|
|
|
ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
|
if (ruleset_fd < 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to create landlock ruleset\n");
|
|
return -1;
|
|
}
|
|
struct exile_path_policy *policy = policies;
|
|
while(policy != NULL)
|
|
{
|
|
struct landlock_path_beneath_attr path_beneath;
|
|
path_beneath.parent_fd = open(policy->path, O_PATH | O_CLOEXEC);
|
|
if(path_beneath.parent_fd < 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to open policy path %s while preparing landlock ruleset\n", policy->path);
|
|
close(ruleset_fd);
|
|
return path_beneath.parent_fd;
|
|
}
|
|
struct stat sb;
|
|
int ret = fstat(path_beneath.parent_fd, &sb);
|
|
if(ret)
|
|
{
|
|
EXILE_LOG_ERROR("fstat failed %s\n", strerror(errno));
|
|
close(ruleset_fd);
|
|
return ret;
|
|
}
|
|
path_beneath.allowed_access = exile_flags_to_landlock(policy->policy, sb.st_mode);
|
|
ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
|
|
if(ret)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to update ruleset while processsing policy path %s\n", policy->path);
|
|
close(ruleset_fd);
|
|
return ret;
|
|
}
|
|
policy = policy->next;
|
|
}
|
|
return ruleset_fd;
|
|
}
|
|
#endif
|
|
|
|
|
|
/* Checks for illogical or dangerous combinations */
|
|
static int check_policy_sanity(struct exile_policy *policy)
|
|
{
|
|
if(policy->no_new_privs != 1)
|
|
{
|
|
if(policy->syscall_policies != NULL)
|
|
{
|
|
EXILE_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int can_use_landlock = exile_landlock_is_available();
|
|
if(!can_use_landlock)
|
|
{
|
|
struct exile_path_policy *path_policy = policy->path_policies;
|
|
while(path_policy)
|
|
{
|
|
if(path_policy_needs_landlock(path_policy))
|
|
{
|
|
EXILE_LOG_ERROR("A path policy needs landlock, but landlock is not available. Fallback not possible\n");
|
|
return -1;
|
|
}
|
|
path_policy = path_policy->next;
|
|
}
|
|
}
|
|
|
|
/* TODO: check if we have ALLOWED, but no default deny */
|
|
|
|
if(policy->mount_path_policies_to_chroot == 1)
|
|
{
|
|
if(policy->path_policies == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("Cannot mount path policies to chroot if none are given\n");
|
|
return -1;
|
|
}
|
|
if(!(policy->namespace_options & EXILE_UNSHARE_MOUNT))
|
|
{
|
|
EXILE_LOG_ERROR("mount_path_policies_to_chroot = 1 requires unsharing mount namespace\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
if(policy->path_policies != NULL)
|
|
{
|
|
|
|
if(policy->mount_path_policies_to_chroot != 1)
|
|
{
|
|
#if HAVE_LANDLOCK != 1
|
|
EXILE_LOG_ERROR("Path policies cannot be enforced! System needs landlock support or set mount_path_policies_to_chroot = 1\n");
|
|
return -1;
|
|
#endif
|
|
}
|
|
if(policy->no_fs == 1)
|
|
{
|
|
EXILE_LOG_ERROR("If path_policies are specified, no_fs cannot be set to 1\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
struct exile_syscall_policy *syscall_policy = policy->syscall_policies;
|
|
if(syscall_policy != NULL)
|
|
{
|
|
/* A few sanitiy checks... but we cannot check overall whether it's reasonable */
|
|
int i = 0;
|
|
int last_match_all = -1;
|
|
int match_all_policy = 0;
|
|
int last_policy = 0;
|
|
while(syscall_policy)
|
|
{
|
|
if(syscall_policy->syscall == EXILE_SYSCALL_MATCH_ALL)
|
|
{
|
|
last_match_all = i;
|
|
match_all_policy = syscall_policy->policy;
|
|
}
|
|
else
|
|
{
|
|
last_policy = syscall_policy->policy;
|
|
}
|
|
syscall_policy = syscall_policy->next;
|
|
++i;
|
|
}
|
|
if(last_match_all == -1 || i - last_match_all != 1)
|
|
{
|
|
EXILE_LOG_ERROR("The last entry in the syscall policy list must match all syscalls (default rule)\n");
|
|
return -1;
|
|
}
|
|
/* Most likely a mistake and not intended */
|
|
if(last_policy == match_all_policy)
|
|
{
|
|
EXILE_LOG_ERROR("Last policy for a syscall matches default policy\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void close_file_fds()
|
|
{
|
|
long max_files = sysconf(_SC_OPEN_MAX);
|
|
for(long i = 3; i <= max_files; i++)
|
|
{
|
|
close((int)i);
|
|
}
|
|
}
|
|
|
|
/* Takes away file system access from the process
|
|
*
|
|
* We use this when "no_fs" is given in the policy.
|
|
*
|
|
* This is useful for restricted subprocesses that do some computational work
|
|
* and do not require filesystem access
|
|
*
|
|
* @returns: 0 on success, < 0 on error
|
|
*/
|
|
static int enable_no_fs(struct exile_policy *policy)
|
|
{
|
|
close_file_fds();
|
|
|
|
if(chdir("/proc/self/fdinfo") != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(chroot(".") != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to chroot into safe directory: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(chdir("/") != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to chdir into safe directory inside chroot: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Enables the specified exile_policy.
|
|
*
|
|
* This function is not atomic (and can't be). This means some
|
|
* policies can apply, while others may fail.
|
|
*
|
|
* This function returns success only if all policies applied.
|
|
*
|
|
* The state is undefined if this function fails. The process generally
|
|
* should exit.
|
|
*
|
|
* @returns: 0 on success (all policies applied), < 0 on error (none or some policies dit not apply)
|
|
*/
|
|
int exile_enable_policy(struct exile_policy *policy)
|
|
{
|
|
if((policy->exile_flags & EXILE_FLAG_ADD_PATH_POLICY_FAIL) || (policy->exile_flags & EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL))
|
|
{
|
|
EXILE_LOG_ERROR("At least one syscall or path policy was not successfully added!\n");
|
|
return -1;
|
|
}
|
|
if(check_policy_sanity(policy) != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Policy sanity check failed. Cannot apply policy!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if(enter_namespaces(policy->namespace_options) < 0)
|
|
{
|
|
EXILE_LOG_ERROR("Error while trying to enter namespaces\n");
|
|
return -1;
|
|
}
|
|
|
|
int can_use_landlock = exile_landlock_is_available();
|
|
|
|
|
|
/* Fallback to chroot mechanism to enforce policies. Ignore mount_path_policies_to_chroot
|
|
* if we have no other option (so no landlock) */
|
|
if((policy->mount_path_policies_to_chroot || !can_use_landlock) && policy->path_policies != NULL)
|
|
{
|
|
if(*policy->chroot_target_path == '\0')
|
|
{
|
|
char random_str[17];
|
|
if(random_string(random_str, sizeof(random_str)) == 16)
|
|
{
|
|
int res = snprintf(policy->chroot_target_path, sizeof(policy->chroot_target_path), "%s/.sandbox_%" PRIdMAX "_%s", EXILE_TEMP_DIR, (intmax_t)getpid(), random_str);
|
|
if(res < 0)
|
|
{
|
|
EXILE_LOG_ERROR("error during path concatination\n");
|
|
return -EINVAL;
|
|
}
|
|
if(res >= PATH_MAX)
|
|
{
|
|
EXILE_LOG_ERROR("path concatination truncated\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EXILE_LOG_ERROR("Error creating random sandbox directory name\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(create_chroot_dirs(policy->chroot_target_path, policy->path_policies) < 0)
|
|
{
|
|
EXILE_LOG_ERROR("bind mounting of path policies failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if(perform_mounts(policy->chroot_target_path, policy->path_policies) < 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to remount\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(*policy->chroot_target_path != '\0')
|
|
{
|
|
if(chroot(policy->chroot_target_path) < 0)
|
|
{
|
|
EXILE_LOG_ERROR("failed to enter %s\n", policy->chroot_target_path);
|
|
return -1;
|
|
}
|
|
const char *chdir_target_path = policy->chdir_path;
|
|
if(chdir_target_path == NULL)
|
|
{
|
|
chdir_target_path = "/";
|
|
}
|
|
|
|
if(chdir(chdir_target_path) < 0)
|
|
{
|
|
EXILE_LOG_ERROR("chdir to %s failed\n", policy->chdir_path);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#if HAVE_LANDLOCK == 1
|
|
int landlock_ruleset_fd = -1;
|
|
if(can_use_landlock && policy->path_policies != NULL)
|
|
{
|
|
landlock_ruleset_fd = landlock_prepare_ruleset(policy->path_policies);
|
|
if(landlock_ruleset_fd < 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to prepare landlock ruleset: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if(policy->no_fs)
|
|
{
|
|
if(enable_no_fs(policy) != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to take away filesystem access of process\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(policy->no_new_fds)
|
|
{
|
|
const struct rlimit nofile = {0, 0};
|
|
if (setrlimit(RLIMIT_NOFILE, &nofile) == -1)
|
|
{
|
|
EXILE_LOG_ERROR("setrlimit: Failed to set rlimit: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(policy->drop_caps)
|
|
{
|
|
if(drop_caps() < 0)
|
|
{
|
|
EXILE_LOG_ERROR("failed to drop capabilities\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(policy->not_dumpable)
|
|
{
|
|
if(prctl(PR_SET_DUMPABLE, 0) == -1)
|
|
{
|
|
EXILE_LOG_ERROR("prctl: PR_SET_DUMPABLE failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(policy->no_new_privs)
|
|
{
|
|
if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
|
|
{
|
|
EXILE_LOG_ERROR("prctl: PR_SET_NO_NEW_PRIVS failed: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#if HAVE_LANDLOCK == 1
|
|
if (can_use_landlock && policy->path_policies != NULL && landlock_restrict_self(landlock_ruleset_fd, 0) != 0)
|
|
{
|
|
perror("Failed to enforce ruleset");
|
|
close(landlock_ruleset_fd);
|
|
return -1;
|
|
}
|
|
close(landlock_ruleset_fd);
|
|
#endif
|
|
|
|
if(policy->vow_promises != 0)
|
|
{
|
|
int ret = exile_append_vow_promises(policy, policy->vow_promises);
|
|
if(ret != 0)
|
|
{
|
|
EXILE_LOG_ERROR("exile_append_vow_promises() failed: %i\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if(policy->syscall_policies != NULL)
|
|
{
|
|
return exile_enable_syscall_policy(policy);
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Convenience wrapper for the vow-related subset of exile.h
|
|
*
|
|
* Only installs seccomp filters for the specified vow promises.
|
|
*
|
|
* Useful if only vow is required from exile.h, but nothing else
|
|
*
|
|
* Comparable with OpenBSD's pledge(), subsequent calls can only reduce allowed syscalls.
|
|
*
|
|
* Here, adding more promises than a previous call set may return success, but
|
|
* won't be allowed during execution.
|
|
*
|
|
* Due to the nature of seccomp, it's furthermore required the EXILE_SYSCALL_VOW_SECCOMP_INSTALL promise
|
|
* is set if further calls are expected. Generally, it's reasonable for the last call to
|
|
* exile_vow() a program makes to not set EXILE_SYSCALL_VOW_SECCOMP_INSTALL.
|
|
*
|
|
* There are no seperate exec_promises. All children of the process inherit the filter.
|
|
* .
|
|
* Return value: 0 on success, any other value on failure.
|
|
*/
|
|
int exile_vow(uint64_t promises)
|
|
{
|
|
struct __user_cap_header_struct h = { 0 };
|
|
h.pid = 0;
|
|
h.version = _LINUX_CAPABILITY_VERSION_3;
|
|
struct __user_cap_data_struct cap[2];
|
|
cap[0].effective = 0;
|
|
cap[0].permitted = 0;
|
|
cap[0].inheritable = 0;
|
|
cap[1].effective = 0;
|
|
cap[1].permitted = 0;
|
|
cap[1].inheritable = 0;
|
|
if(capget(&h, cap) == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to get capabilities: %s\n", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
struct exile_policy *policy = exile_create_policy();
|
|
if(policy == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to create policy\n");
|
|
return 1;
|
|
}
|
|
|
|
policy->vow_promises = promises;
|
|
if((cap[0].effective & (1<<CAP_SYS_ADMIN)) == 0)
|
|
{
|
|
policy->no_new_privs = 1;
|
|
}
|
|
int ret = exile_enable_policy(policy);
|
|
exile_free_policy(policy);
|
|
return ret;
|
|
}
|
|
|
|
int exile_clone_handle(void *arg)
|
|
{
|
|
struct exile_launch_params *params = (struct exile_launch_params *) arg;
|
|
struct exile_policy *policy = (struct exile_policy *) params->policy;
|
|
|
|
int ret = exile_enable_policy(policy);
|
|
if(ret != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to enable policy\n");
|
|
close(params->child_read_pipe[1]);
|
|
close(params->child_write_pipe[0]);
|
|
return 1;
|
|
}
|
|
ret = dup2(params->child_read_pipe[1], 1);
|
|
if(ret == -1)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to redirect stdout to pipe\n");
|
|
return 1;
|
|
}
|
|
ret = params->func(params->funcarg);
|
|
fclose(stdout);
|
|
close(params->child_read_pipe[1]);
|
|
close(params->child_write_pipe[0]);
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/* Helper to easily execute a single function sandboxed.
|
|
*
|
|
* Creates a child-process, then activates the policy contained in launch_params,
|
|
* and jumps to the specified function, passing the specified argument to it.
|
|
* Returns a fd connected to stdout in the child process, as well as a fd allowing to write
|
|
* to the child.
|
|
*
|
|
* if cloneflags is 0, the default ones are passed to clone(), otherwise the value of cloneflags
|
|
*
|
|
* Return value: Negative on error, otherwise the file descriptor to read from*/
|
|
int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_result *launch_result)
|
|
{
|
|
int ret = pipe(launch_params->child_read_pipe);
|
|
if(ret != 0)
|
|
{
|
|
EXILE_LOG_ERROR("read pipe creation failed\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = pipe(launch_params->child_write_pipe);
|
|
if(ret != 0)
|
|
{
|
|
EXILE_LOG_ERROR("write pipe creation failed\n");
|
|
return ret;
|
|
}
|
|
|
|
struct rlimit rlimit;
|
|
ret = getrlimit(RLIMIT_STACK, &rlimit);
|
|
if(ret != 0)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to get stack size: %s\n", strerror(errno));
|
|
return ret;
|
|
}
|
|
size_t size = rlimit.rlim_cur;
|
|
char *stack = (char *) calloc(1, size);
|
|
if(stack == NULL)
|
|
{
|
|
EXILE_LOG_ERROR("Failed to allocate stack memory for child\n");
|
|
return 1;
|
|
}
|
|
stack += size;
|
|
ret = clone(&exile_clone_handle, stack, 17 /* SIGCHLD */, launch_params);
|
|
if(ret == -1)
|
|
{
|
|
EXILE_LOG_ERROR("clone failed(): %s\n", strerror(errno));
|
|
return ret;
|
|
}
|
|
close(launch_params->child_read_pipe[1]);
|
|
close(launch_params->child_write_pipe[0]);
|
|
|
|
launch_result->tid = ret;
|
|
launch_result->read_fd = launch_params->child_read_pipe[0];
|
|
launch_result->write_fd = launch_params->child_write_pipe[1];
|
|
return 0;
|
|
}
|
|
|
|
/* Helper for exile_launch, to easily read all output from a function
|
|
* This function will read all output from a sandboxed function. It's up to the caller to ensure
|
|
* that enough memory will be available.
|
|
*
|
|
* The result is \0 terminated. The "n" parameter contains the size of the result, not including the \0.
|
|
*
|
|
* Return value: All data written by the function. The result should be passed to free() once not needed. NULL will
|
|
* be returned on error.
|
|
*/
|
|
char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n)
|
|
{
|
|
*n = 0;
|
|
struct exile_launch_result launch_result;
|
|
int launch = exile_launch(launch_params, &launch_result);
|
|
if(launch < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
char *result = NULL;
|
|
size_t size = 0;
|
|
FILE *stream = open_memstream(&result, &size);
|
|
while(1)
|
|
{
|
|
char buffer[4096];
|
|
int ret = read(launch_result.read_fd, buffer, sizeof(buffer));
|
|
if(ret == 0)
|
|
{
|
|
break;
|
|
}
|
|
if(ret == -1)
|
|
{
|
|
if(errno == EINTR)
|
|
{
|
|
continue;
|
|
}
|
|
EXILE_LOG_ERROR("Failed to read from read file descriptor\n");
|
|
close(launch_result.read_fd);
|
|
fclose(stream);
|
|
return NULL;
|
|
}
|
|
size_t written = fwrite(buffer, 1, ret, stream);
|
|
if(written != (size_t) ret)
|
|
{
|
|
EXILE_LOG_ERROR("Short item write");
|
|
/* TODO: can we seek and free? */
|
|
close(launch_result.read_fd);
|
|
fclose(stream);
|
|
return NULL;
|
|
}
|
|
}
|
|
fclose(stream);
|
|
int seek = fseek(stream, 0, SEEK_SET);
|
|
if(seek == -1)
|
|
{
|
|
EXILE_LOG_ERROR("fseek failed\n");
|
|
close(launch_result.read_fd);
|
|
return NULL;
|
|
}
|
|
close(launch_result.read_fd);
|
|
*n = size;
|
|
return result;
|
|
}
|