diff --git a/qssb.h b/qssb.h index e0afd9d..33ed914 100644 --- a/qssb.h +++ b/qssb.h @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -158,6 +160,29 @@ static int default_blacklisted_syscals[] = { -1 }; +/* TODO: Check for completion + * Known blacklisting problem (catch up game, etc.) + * + * However, we use it to enhance "no_fs" policy, which does not solely rely + * on seccomp anyway */ +static int fs_access_syscalls[] = { + QSSB_SYS(chdir), + QSSB_SYS(truncate), + QSSB_SYS(stat), + QSSB_SYS(flock), + QSSB_SYS(chmod), + QSSB_SYS(chown), + QSSB_SYS(setxattr), + QSSB_SYS(utime), + QSSB_SYS(ioctl), + QSSB_SYS(fcntl), + QSSB_SYS(access), + QSSB_SYS(open), + QSSB_SYS(openat), + QSSB_SYS(unlink), + -1 +}; + struct qssb_path_policy { const char *path; @@ -173,6 +198,8 @@ struct qssb_policy int preserve_cwd; int not_dumpable; int no_new_privs; + int no_fs; + int no_new_fds; int namespace_options; /* Bind mounts all paths in path_policies into the chroot and applies non-landlock policies */ @@ -197,6 +224,8 @@ struct qssb_policy *qssb_init_policy() result->drop_caps = 1; result->not_dumpable = 1; result->no_new_privs = 1; + result->no_fs = 0; + result->no_new_fds = 0; result->namespace_options = QSSB_UNSHARE_MOUNT | QSSB_UNSHARE_USER; result->chdir_path = NULL; result->mount_path_policies_to_chroot = 0; @@ -737,16 +766,101 @@ static int check_policy_sanity(struct qssb_policy *policy) } } - if(policy->path_policies != NULL && policy->mount_path_policies_to_chroot != 1) + if(policy->path_policies != NULL) { - #if HAVE_LANDLOCK != 1 - QSSB_LOG_ERROR("Path policies cannot be enforced! System needs landlock support or set mount_path_policies_to_chroot = 1\n"); + + if(policy->mount_path_policies_to_chroot != 1) + { + #if HAVE_LANDLOCK != 1 + QSSB_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) + { + QSSB_LOG_ERROR("If path_policies are specified, no_fs cannot be set to 1"); return -1; - #endif + } } 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 qssb_policy *policy) +{ + close_file_fds(); + + if(chdir("/proc/self/fdinfo") != 0) + { + QSSB_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno)); + return -1; + } + + if(chroot(".") != 0) + { + QSSB_LOG_ERROR("Failed to chroot into safe directory: %s\n", strerror(errno)); + return -1; + } + + if(chdir("/") != 0) + { + QSSB_LOG_ERROR("Failed to chdir into safe directory inside chroot: %s\n", strerror(errno)); + return -1; + } + + if(policy->whitelisted_syscalls == NULL) + { + if(policy->blacklisted_syscalls == NULL) + { + policy->blacklisted_syscalls = fs_access_syscalls; + } + else + { + size_t nbl = 0; + size_t nfs = sizeof(fs_access_syscalls)/sizeof(fs_access_syscalls[0]); + int *tmp = policy->blacklisted_syscalls; + while(*tmp != -1) + { + ++nbl; + ++tmp; + } + size_t n = (nbl + nfs) + 1; + tmp = (int *) calloc(n, sizeof(int)); + int *tmp_begin = tmp; + if(tmp == NULL) + { + QSSB_LOG_ERROR("Failed to expand blacklisted syscall array\n"); + return -1; + } + memcpy(tmp, policy->blacklisted_syscalls, nbl*sizeof(int)); + tmp+=nbl; + memcpy(tmp, fs_access_syscalls, nfs*sizeof(int)); + tmp+=(nfs+1); + *tmp = -1; + + policy->blacklisted_syscalls = tmp_begin; + } + } + return 0; +} + /* Enables the specified qssb_policy. * * This function is not atomic (and can't be). This means some @@ -827,7 +941,6 @@ int qssb_enable_policy(struct qssb_policy *policy) } } #endif - if(policy->chdir_path == NULL) { policy->chdir_path = "/"; @@ -839,6 +952,25 @@ int qssb_enable_policy(struct qssb_policy *policy) return -1; } + if(policy->no_fs) + { + if(enable_no_fs(policy) != 0) + { + QSSB_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) + { + QSSB_LOG_ERROR("setrlimit: Failed to set rlimit: %s\n", strerror(errno)); + return -1; + } + } + if(policy->drop_caps) { if(drop_caps() < 0) diff --git a/test.c b/test.c index 072d177..234599f 100644 --- a/test.c +++ b/test.c @@ -1,5 +1,10 @@ #include "qssb.h" #include +#include +#include +#include +#include + int test_default_main(int argc, char *argv[]) { struct qssb_policy *policy = qssb_init_policy(); @@ -69,6 +74,65 @@ int test_landlock_deny_write(int argc, char *argv[]) return 1; } +int test_nofs(int argc, char *argv[]) +{ + struct qssb_policy *policy = qssb_init_policy(); + policy->no_fs = 1; + + int ret = qssb_enable_policy(policy); + if(ret != 0) + { + fprintf(stderr, "Failed to activate nofs sandbox\n"); + return -1; + } + + int s = socket(AF_INET,SOCK_STREAM,0); + if(s == -1) + { + fprintf(stderr, "Failed to open socket but this was not requested by policy\n"); + return 0; + } + + /* Expect seccomp to take care of this */ + if(open("/test", O_CREAT | O_WRONLY) >= 0) + { + fprintf(stderr, "Failed: Do not expect write access\n"); + return -1; + } + + return 0; +} + + +int test_no_new_fds(int argc, char *argv[]) +{ + struct qssb_policy *policy = qssb_init_policy(); + policy->no_new_fds = 1; + + int ret = qssb_enable_policy(policy); + if(ret != 0) + { + fprintf(stderr, "Failed to activate no_new_fd sandbox\n"); + return -1; + } + + if(open("/tmp/test", O_CREAT | O_WRONLY) >= 0) + { + fprintf(stderr, "Failed: Could open new file descriptor\n"); + return -1; + } + + int s = socket(AF_INET,SOCK_STREAM,0); + if(s >= 0) + { + fprintf(stderr, "Failed: socket got opened but policy denied\n"); + return -1; + } + + return 0; + +} + struct dispatcher { char *name; @@ -81,7 +145,9 @@ struct dispatcher dispatchers[] = { { "seccomp-blacklisted", &test_seccomp_blacklisted, false }, { "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted, true }, { "landlock", &test_landlock, true }, - { "landlock-deny-write", &test_landlock_deny_write, true } + { "landlock-deny-write", &test_landlock_deny_write, true }, + { "no_fs", &test_nofs, false}, + { "no_new_fds", &test_no_new_fds, true} }; int main(int argc, char *argv[])