From 69829374c731254955812a22889f66f30e69902e Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 14 Mar 2022 21:31:56 +0100 Subject: [PATCH] exile.h: Move definitions to new file exile.c Especially with exile_launch(), we will be included from more than one translation unit. Thus, ODR becomes a headache now. So move definitions to exile.c. --- Makefile | 2 +- exile.c | 1841 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ exile.h | 1746 +-------------------------------------------------- test.c | 1 + 4 files changed, 1864 insertions(+), 1726 deletions(-) create mode 100644 exile.c diff --git a/Makefile b/Makefile index 90cce91..090dd03 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ clean: rm -f test testcpp test: test.c exile.h - $(CC) test.c -g $(CFLAGS) -o test + $(CC) test.c exile.c -g $(CFLAGS) -o test testcpp: test.cpp exile.h exile.hpp $(CXX) test.cpp -g $(CXXFLAGS) -o testcpp diff --git a/exile.c b/exile.c new file mode 100644 index 0000000..79309c0 --- /dev/null +++ b/exile.c @@ -0,0 +1,1841 @@ +/* + * Copyright (c) 2019-2022 Albert Schwarzkopf + * + * 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<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(child_read_pipe[1]); + close(child_write_pipe[0]); + return 1; + } + ret = dup2(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(child_read_pipe[1]); + close(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(child_read_pipe); + if(ret != 0) + { + EXILE_LOG_ERROR("read pipe creation failed\n"); + return ret; + } + + ret = pipe(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(child_read_pipe[1]); + close(child_write_pipe[0]); + + launch_result->tid = ret; + launch_result->read_fd = child_read_pipe[0]; + launch_result->write_fd = 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; +} diff --git a/exile.h b/exile.h index 7b6ef6b..3809f54 100644 --- a/exile.h +++ b/exile.h @@ -383,304 +383,6 @@ struct exile_policy uint32_t exile_flags; }; - -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 @@ -688,95 +390,16 @@ struct str_to_vow_map str_to_vow_map[] = * * 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; -} - -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; -} +uint64_t exile_vows_from_str(const char *str); /* * If we can use landlock, return 1, otherwise 0 */ -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_landlock_is_available(); -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); -} +int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n); +int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy); struct exile_syscall_filter { @@ -795,219 +418,26 @@ struct exile_syscall_filter * * Returns: 0 if none copied, otherwise the number of entries in "filter". */ -static 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}, - }; +int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy); - 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} - }; +int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises); - 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; -} +struct exile_policy *exile_create_policy(); + /* 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; -} +struct exile_policy *exile_init_policy(); /* Appends path policies to the exile_policy object @@ -1017,500 +447,17 @@ struct exile_policy *exile_init_policy() * 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; -} - +int exile_append_path_policies(struct exile_policy *exile_policy, unsigned int path_policy, ...); #define exile_append_path_policies(e, p, ...) exile_append_path_policies(e, p, __VA_ARGS__, NULL) -/* - * 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 -*/ -static 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; -} - -static 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; -} - - +int path_policy_needs_landlock(struct exile_path_policy *path_policy); /* * 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; -} +void exile_free_policy(struct exile_policy *ctxt); - -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; - -} /* * Enables the seccomp policy * @@ -1518,509 +465,10 @@ static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, st * * @returns: 0 on success, -1 on error */ - -static 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 +int exile_enable_syscall_policy(struct exile_policy *policy); -/* 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; -} -#endif +int exile_enable_policy(struct exile_policy *policy); /* Convenience wrapper for the vow-related subset of exile.h @@ -2042,42 +490,7 @@ int exile_enable_policy(struct exile_policy *policy) * . * 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<no_new_privs = 1; - } - int ret = exile_enable_policy(policy); - exile_free_policy(policy); - return ret; -} - - +int exile_vow(uint64_t promises); struct exile_launch_params { @@ -2096,32 +509,7 @@ struct exile_launch_result static int child_read_pipe[2]; static int child_write_pipe[2]; -static 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(child_read_pipe[1]); - close(child_write_pipe[0]); - return 1; - } - ret = dup2(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(child_read_pipe[1]); - close(child_write_pipe[0]); - return ret; -} - +int exile_clone_handle(void *arg); /* Helper to easily execute a single function sandboxed. * * Creates a child-process, then activates the policy contained in launch_params, @@ -2132,51 +520,8 @@ static int exile_clone_handle(void *arg) * 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(child_read_pipe); - if(ret != 0) - { - EXILE_LOG_ERROR("read pipe creation failed\n"); - return ret; - } +int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_result *launch_result); - ret = pipe(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(child_read_pipe[1]); - close(child_write_pipe[0]); - - launch_result->tid = ret; - launch_result->read_fd = child_read_pipe[0]; - launch_result->write_fd = 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 @@ -2187,59 +532,10 @@ int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_ * 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; -} +char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n); + #ifdef __cplusplus } #endif + +#endif diff --git a/test.c b/test.c index 58a099a..b2167e3 100644 --- a/test.c +++ b/test.c @@ -489,6 +489,7 @@ int test_no_new_fds() } +extern int mkpath(const char *p, mode_t mode, int baseisfile); int test_mkpath() { system("rm -rf /tmp/.exile.h/");