Compare commits

..

No commits in common. "215032f32c84548e1344f3d70fb0291d2e79d6da" and "fa06287b13570f4d45e9d14b749f6a9b5b618de7" have entirely different histories.

4 changed files with 174 additions and 419 deletions

View File

@ -4,8 +4,6 @@
## Status
No release yet, expiremental, API is unstable, builds will break on updates of this library.
Currently, it's mainly evolving according to the needs of my other projects.
## Features
- Systemcall filtering (using seccomp-bpf)
@ -38,7 +36,7 @@ the library may check against that. Execute
`echo 1 > /proc/sys/kernel/unprivileged_userns_clone` to disable that patch for now.
### Examples
- looqs: https://gitea.quitesimple.org/crtxcr/looqs
- qswiki: https://gitea.quitesimple.org/crtxcr/qswiki
- cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb
- qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb

344
qssb.h
View File

@ -40,7 +40,6 @@
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/version.h>
#include <linux/audit.h>
#include <sys/capability.h>
#include <stddef.h>
#include <inttypes.h>
@ -59,18 +58,17 @@
#endif
#endif
#if defined(__i386__)
#define SECCOMP_AUDIT_ARCH AUDIT_ARCH_I386
#elif defined(__x86_64__)
#define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64
#else
#warning Seccomp support has not been tested for qssb.h for this platform yet
#endif
//TODO: stolen from kernel samples/seccomp, GPLv2...?
#define ALLOW \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
#define DENY \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define SYSCALL(nr, jt) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (nr), 0, 1), jt
#define LOAD_SYSCALL_NR \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
offsetof(struct seccomp_data, nr))
#define QSSB_UNSHARE_NETWORK 1<<1
#define QSSB_UNSHARE_USER 1<<2
@ -142,7 +140,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
*/
/* TODO: more execv* in some architectures */
/* TODO: add more */
static long default_blacklisted_syscalls[] = {
static int default_blacklisted_syscals[] = {
QSSB_SYS(setuid),
QSSB_SYS(setgid),
QSSB_SYS(chroot),
@ -166,7 +164,7 @@ static long default_blacklisted_syscalls[] = {
*
* However, we use it to enhance "no_fs" policy, which does not solely rely
* on seccomp anyway */
static long fs_access_syscalls[] = {
static int fs_access_syscalls[] = {
QSSB_SYS(chdir),
QSSB_SYS(truncate),
QSSB_SYS(stat),
@ -190,29 +188,13 @@ struct qssb_path_policy
struct qssb_path_policy *next;
};
struct qssb_allocated_entry
{
void *data; /* the actual data */
size_t size; /* number of bytes allocated for data */
size_t size; /* number of bytes allocated for size */
size_t used; /* number of bytes in use */
};
/* Special value */
#define QSSB_SYSCALL_MATCH_ALL -1
#define QSSB_SYSCALL_ALLOW 1
#define QSSB_SYSCALL_DENY_KILL_PROCESS 2
#define QSSB_SYSCALL_DENY_RET_ERROR 3
struct qssb_syscall_policy
{
struct qssb_allocated_entry syscall;
unsigned int policy;
struct qssb_syscall_policy *next;
};
/* Number of bytes to grow the buffer in qssb_allocated_entry with */
#define QSSB_ENTRY_ALLOC_SIZE 32
@ -227,7 +209,6 @@ struct qssb_policy
int no_fs;
int no_new_fds;
int namespace_options;
int disable_syscall_filter;
/* Bind mounts all paths in path_policies into the chroot and applies
non-landlock policies */
int mount_path_policies_to_chroot;
@ -238,9 +219,9 @@ struct qssb_policy
struct qssb_path_policy *path_policies;
struct qssb_path_policy **path_policies_tail;
/* Do not manually add policies here, use qssb_append_syscall_policy() */
struct qssb_syscall_policy *syscall_policies;
struct qssb_syscall_policy **syscall_policies_tail;
/* Do not manually add entries here, use qssb_append_denied_syscall() etc. */
struct qssb_allocated_entry denied_syscalls;
struct qssb_allocated_entry allowed_syscalls;
};
@ -251,11 +232,6 @@ static int qssb_entry_append(struct qssb_allocated_entry *entry, void *data, siz
{
size_t expandval = QSSB_ENTRY_ALLOC_SIZE > bytes ? QSSB_ENTRY_ALLOC_SIZE : bytes;
size_t sizenew = entry->size + expandval;
if(sizenew < entry->size)
{
QSSB_LOG_ERROR("overflow in qssb_entry_append\n");
return -EINVAL;
}
int *datanew = (int *) realloc(entry->data, sizenew);
if(datanew == NULL)
{
@ -271,68 +247,32 @@ static int qssb_entry_append(struct qssb_allocated_entry *entry, void *data, siz
return 0;
}
static int qssb_append_syscall(struct qssb_allocated_entry *entry, long *syscalls, size_t n)
static int qssb_append_syscall(struct qssb_allocated_entry *entry, int *syscalls, size_t n)
{
return qssb_entry_append(entry, syscalls, n * sizeof(long));
return qssb_entry_append(entry, syscalls, n * sizeof(int));
}
static int is_valid_syscall_policy(unsigned int policy)
int qssb_append_denied_syscall(struct qssb_policy *qssb_policy, int syscall)
{
return policy == QSSB_SYSCALL_ALLOW || policy == QSSB_SYSCALL_DENY_RET_ERROR || policy == QSSB_SYSCALL_DENY_KILL_PROCESS;
return qssb_append_syscall(&qssb_policy->denied_syscalls, &syscall, 1);
}
static void get_syscall_array(struct qssb_syscall_policy *policy, long **syscall, size_t *n)
int qssb_append_allowed_syscall(struct qssb_policy *qssb_policy, int syscall)
{
*syscall = (long *) policy->syscall.data;
*n = policy->syscall.used / sizeof(long);
return qssb_append_syscall(&qssb_policy->allowed_syscalls, &syscall, 1);
}
int qssb_append_syscalls_policy(struct qssb_policy *qssb_policy, unsigned int syscall_policy, long *syscalls, size_t n)
int qssb_append_allowed_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n)
{
/* Check whether we already have this policy. If so, merge new entries to the existing ones */
struct qssb_syscall_policy *current_policy = qssb_policy->syscall_policies;
while(current_policy)
{
if(current_policy->policy == syscall_policy)
{
return qssb_append_syscall(&current_policy->syscall, syscalls, n);
}
current_policy = current_policy->next;
}
/* We don't so we create a new policy */
struct qssb_syscall_policy *newpolicy = (struct qssb_syscall_policy *) calloc(1, sizeof(struct qssb_syscall_policy));
if(newpolicy == NULL)
{
QSSB_LOG_ERROR("Failed to allocate memory for syscall policy\n");
return -1;
}
int ret = qssb_append_syscall(&newpolicy->syscall, syscalls, n);
if(ret != 0)
{
QSSB_LOG_ERROR("Failed to append syscall\n");
return -1;
}
newpolicy->next = NULL;
newpolicy->policy = syscall_policy;
*(qssb_policy->syscall_policies_tail) = newpolicy;
qssb_policy->syscall_policies_tail = &(newpolicy->next);
qssb_policy->disable_syscall_filter = 0;
return 0;
return qssb_append_syscall(&qssb_policy->allowed_syscalls, syscalls, n);
}
int qssb_append_syscall_policy(struct qssb_policy *qssb_policy, unsigned int syscall_policy, long syscall)
int qssb_append_denied_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n)
{
return qssb_append_syscalls_policy(qssb_policy, syscall_policy, &syscall, 1);
}
int qssb_append_syscall_default_policy(struct qssb_policy *qssb_policy, unsigned int default_policy)
{
return qssb_append_syscall_policy(qssb_policy, default_policy, QSSB_SYSCALL_MATCH_ALL);
return qssb_append_syscall(&qssb_policy->denied_syscalls, syscalls, n);
}
/* Creates the default policy
@ -347,16 +287,25 @@ struct qssb_policy *qssb_init_policy()
result->no_fs = 0;
result->no_new_fds = 0;
result->namespace_options = QSSB_UNSHARE_MOUNT | QSSB_UNSHARE_USER;
result->disable_syscall_filter = 0;
result->chdir_path = NULL;
result->mount_path_policies_to_chroot = 0;
result->chroot_target_path[0] = '\0';
result->path_policies = NULL;
result->path_policies_tail = &(result->path_policies);
result->allowed_syscalls.data = NULL;
result->allowed_syscalls.size = 0;
result->allowed_syscalls.used = 0;
result->denied_syscalls.data = NULL;
result->denied_syscalls.size = 0;
result->denied_syscalls.used = 0;
result->syscall_policies = NULL;
result->syscall_policies_tail = &(result->syscall_policies);
size_t blacklisted_syscalls_count = sizeof(default_blacklisted_syscals)/sizeof(default_blacklisted_syscals[0]);
int appendresult = qssb_append_denied_syscalls(result, default_blacklisted_syscals, blacklisted_syscalls_count);
if(appendresult != 0)
{
return NULL;
}
return result;
}
@ -401,8 +350,6 @@ int qssb_append_path_policy(struct qssb_policy *qssb_policy, unsigned int path_p
return qssb_append_path_policies(qssb_policy, path_policy, path, NULL);
}
/*
* Fills buffer with random characters a-z.
* The string will be null terminated.
@ -585,14 +532,6 @@ void qssb_free_policy(struct qssb_policy *ctxt)
current = current->next;
free(tmp);
}
struct qssb_syscall_policy *sc_policy = ctxt->syscall_policies;
while(sc_policy != NULL)
{
struct qssb_syscall_policy *tmp = sc_policy;
sc_policy = sc_policy->next;
free(tmp);
}
free(ctxt);
}
}
@ -687,72 +626,36 @@ static int drop_caps()
return 0;
}
static void append_syscalls_to_bpf(long *syscalls, size_t n, unsigned int action, struct sock_filter *filter, unsigned short int *start_index)
{
if(action == QSSB_SYSCALL_ALLOW)
{
action = SECCOMP_RET_ALLOW;
}
if(action == QSSB_SYSCALL_DENY_KILL_PROCESS)
{
action = SECCOMP_RET_KILL_PROCESS;
}
if(action == QSSB_SYSCALL_DENY_RET_ERROR)
{
action = SECCOMP_RET_ERRNO|EACCES;
}
for(size_t i = 0; i < n; i++)
{
long syscall = syscalls[i];
if(syscall != QSSB_SYSCALL_MATCH_ALL)
{
struct sock_filter syscall_check = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, syscall, 0, 1);
filter[(*start_index)++] = syscall_check;
}
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
* Enables the per_syscall seccomp action for system calls
*
* policy: qssb policy object
* syscalls: array of system calls numbers.
* per_syscall: action to apply for each system call
* default_action: the default action at the end
*
* @returns: 0 on success, -1 on error
*/
static int qssb_enable_syscall_policy(struct qssb_policy *policy)
static int seccomp_enable(int *syscalls, size_t n, unsigned int per_syscall, unsigned int default_action)
{
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),
LOAD_SYSCALL_NR,
};
unsigned short int current_filter_index = 6;
struct qssb_syscall_policy *current_policy = policy->syscall_policies;
while(current_policy)
unsigned short int current_filter_index = 1;
for(size_t i = 0; i < n; i++)
{
if(!is_valid_syscall_policy(current_policy->policy))
{
QSSB_LOG_ERROR("invalid syscall policy specified");
return -1;
}
long *syscalls = NULL;
size_t n = 0;
get_syscall_array(current_policy, &syscalls, &n);
append_syscalls_to_bpf(syscalls, n, current_policy->policy, filter, &current_filter_index);
current_policy = current_policy->next;
unsigned int sysc = (unsigned int) syscalls[i];
struct sock_filter syscall = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, sysc, 0, 1);
struct sock_filter action = BPF_STMT(BPF_RET+BPF_K, per_syscall);
filter[current_filter_index++] = syscall;
filter[current_filter_index++] = action;
}
struct sock_filter da = BPF_STMT(BPF_RET+BPF_K, default_action);
filter[current_filter_index] = da;
++current_filter_index;
struct sock_fprog prog = {
.len = current_filter_index ,
.filter = filter,
@ -767,6 +670,26 @@ static int qssb_enable_syscall_policy(struct qssb_policy *policy)
return 0;
}
/*
* Blacklists the specified systemcalls.
*
* syscalls: array of system calls numbers.
*/
static int seccomp_enable_blacklist(int *syscalls, size_t n)
{
return seccomp_enable(syscalls, n, SECCOMP_RET_KILL, SECCOMP_RET_ALLOW);
}
/*
* Whitelists the specified systemcalls.
*
* syscalls: array of system calls numbers.
*/
static int seccomp_enable_whitelist(int *syscalls, size_t n)
{
return seccomp_enable(syscalls, n, SECCOMP_RET_ALLOW, SECCOMP_RET_KILL);
}
#if HAVE_LANDLOCK == 1
static unsigned int qssb_flags_to_landlock(unsigned int flags)
{
@ -886,17 +809,12 @@ static int landlock_prepare_ruleset(struct qssb_path_policy *policies)
/* Checks for illogical or dangerous combinations */
static int check_policy_sanity(struct qssb_policy *policy)
{
if(policy->no_new_privs != 1)
if(policy->denied_syscalls.used > 0 && policy->allowed_syscalls.used > 0)
{
if(policy->syscall_policies != NULL)
{
QSSB_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n");
return -1;
}
QSSB_LOG_ERROR("Error: Cannot mix allowed and denied systemcalls in policy\n");
return -EINVAL;
}
/* TODO: check if we have ALLOWED, but no default deny */
if(policy->mount_path_policies_to_chroot == 1)
{
if(policy->path_policies == NULL)
@ -911,6 +829,14 @@ static int check_policy_sanity(struct qssb_policy *policy)
}
}
if(policy->no_new_privs != 1)
{
if(policy->allowed_syscalls.used > 0 || policy->denied_syscalls.used > 0)
{
QSSB_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n");
return -1;
}
}
if(policy->path_policies != NULL)
{
@ -928,45 +854,6 @@ static int check_policy_sanity(struct qssb_policy *policy)
return -1;
}
}
struct qssb_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;
while(syscall_policy)
{
long *syscall;
size_t n = 0;
get_syscall_array(syscall_policy, &syscall, &n);
if(syscall[n-1] == QSSB_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)
{
QSSB_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)
{
QSSB_LOG_ERROR("Last policy for a syscall matches default policy\n");
return -1;
}
}
return 0;
}
@ -1010,39 +897,20 @@ static int enable_no_fs(struct qssb_policy *policy)
return -1;
}
//TODO: we don't have to do this if there whitelisted policies, in that case we will be behind the default deny anyway
size_t fs_access_syscalls_count = sizeof(fs_access_syscalls)/sizeof(fs_access_syscalls[0]);
int ret = qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_RET_ERROR, fs_access_syscalls, fs_access_syscalls_count);
if(ret != 0)
if(policy->allowed_syscalls.used == 0)
{
QSSB_LOG_ERROR("Failed to add system calls to policy\n");
return -1;
}
if(qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW) != 0)
{
QSSB_LOG_ERROR("Failed to add default policy when adding denied filesystem-related system calls\n");
return -1;
size_t fs_access_syscalls_count = sizeof(fs_access_syscalls)/sizeof(fs_access_syscalls[0]);
int ret = qssb_append_denied_syscalls(policy, fs_access_syscalls, fs_access_syscalls_count);
if(ret != 0)
{
QSSB_LOG_ERROR("Failed to add system calls to blacklist\n");
return -1;
}
}
return 0;
}
static int qssb_append_predefined_standard_syscall_policy(struct qssb_policy *policy)
{
size_t blacklisted_syscalls_count = sizeof(default_blacklisted_syscalls)/sizeof(default_blacklisted_syscalls[0]);
int appendresult = qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, default_blacklisted_syscalls, blacklisted_syscalls_count);
if(appendresult != 0)
{
return 1;
}
appendresult = qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
if(appendresult != 0)
{
return 1;
}
return 0;
}
/* Enables the specified qssb_policy.
*
* This function is not atomic (and can't be). This means some
@ -1190,18 +1058,26 @@ int qssb_enable_policy(struct qssb_policy *policy)
close(landlock_ruleset_fd);
#endif
if(policy->syscall_policies == NULL && policy->disable_syscall_filter == 0)
if(policy->allowed_syscalls.used > 0)
{
if(qssb_append_predefined_standard_syscall_policy(policy) != 0)
{
QSSB_LOG_ERROR("Failed to add standard predefined syscall policy\n");
return -1;
}
int *syscalls = (int *)policy->allowed_syscalls.data;
size_t n = policy->allowed_syscalls.used / sizeof(int);
if(seccomp_enable_whitelist(syscalls, n) < 0)
{
QSSB_LOG_ERROR("seccomp_enable_whitelist failed\n");
return -1;
}
}
if(policy->syscall_policies != NULL)
if(policy->denied_syscalls.used > 0)
{
return qssb_enable_syscall_policy(policy);
int *syscalls = (int *)policy->denied_syscalls.data;
size_t n = policy->denied_syscalls.used / sizeof(int);
if(seccomp_enable_blacklist(syscalls, n) < 0)
{
QSSB_LOG_ERROR("seccomp_enable_blacklist failed\n");
return -1;
}
}
return 0;

213
test.c
View File

@ -4,113 +4,47 @@
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
int xqssb_enable_policy(struct qssb_policy *policy)
{
int ret = qssb_enable_policy(policy);
if(ret != 0)
{
fprintf(stderr, "qssb_enable_policy() failed: %i\n", ret);
exit(EXIT_FAILURE);
}
return 0;
}
int test_default_main()
int test_default_main(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
int ret = qssb_enable_policy(policy);
return ret;
}
static int test_expected_kill(int (*f)())
{
pid_t pid = fork();
if(pid == 0)
{
return f();
}
int status = 0;
waitpid(pid, &status, 0);
if(WIFSIGNALED(status))
{
int c = WTERMSIG(status);
if(c == SIGSYS)
{
printf("Got expected signal\n");
return 0;
}
printf("Unexpected status code: %i\n", c);
return 1;
}
else
{
int c = WEXITSTATUS(status);
printf("Process was not killed, test fails. Status code of exit: %i\n", c);
return 1;
}
return 0;
}
static int test_successful_exit(int (*f)())
{
pid_t pid = fork();
if(pid == 0)
{
return f();
}
int status = 0;
waitpid(pid, &status, 0);
if(WIFSIGNALED(status))
{
int c = WTERMSIG(status);
printf("Received signal, which was not expected. Signal was: %i\n", c);
return 1;
}
else
{
int c = WEXITSTATUS(status);
if(c != 0)
{
printf("Process failed to exit properly. Status code is: %i\n", c);
}
return c;
}
printf("Process exited sucessfully as expected");
return 0;
}
static int do_test_seccomp_blacklisted()
int test_both_syscalls(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
int syscalls[] = {1,2,3};
xqssb_enable_policy(policy);
qssb_append_denied_syscalls(policy, syscalls, 3);
qssb_append_allowed_syscalls(policy, syscalls, 3);
int ret = qssb_enable_policy(policy);
if(ret != 0)
{
return 0;
}
return 1;
}
int test_seccomp_blacklisted(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_denied_syscall(policy, QSSB_SYS(getuid));
int ret = qssb_enable_policy(policy);
uid_t pid = geteuid();
pid = getuid();
return 0;
}
int test_seccomp_blacklisted()
{
return test_expected_kill(&do_test_seccomp_blacklisted);
}
static int do_test_seccomp_blacklisted_call_permitted()
int test_seccomp_blacklisted_call_permitted(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
qssb_append_denied_syscall(policy, QSSB_SYS(getuid));
int ret = qssb_enable_policy(policy);
//geteuid is not blacklisted, so must succeed
@ -118,71 +52,7 @@ static int do_test_seccomp_blacklisted_call_permitted()
return 0;
}
int test_seccomp_blacklisted_call_permitted()
{
return test_successful_exit(&do_test_seccomp_blacklisted_call_permitted);
}
static int do_test_seccomp_x32_kill()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
xqssb_enable_policy(policy);
/* Attempt to bypass by falling back to x32 should be blocked */
syscall(QSSB_SYS(getuid)+__X32_SYSCALL_BIT);
return 0;
}
int test_seccomp_x32_kill()
{
return test_expected_kill(&do_test_seccomp_x32_kill);
}
/* Tests whether seccomp rules end with a policy matching all syscalls */
int test_seccomp_require_last_matchall()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
int status = qssb_enable_policy(policy);
if(status == 0)
{
printf("Failed. Should not have been enabled!");
return 1;
}
return 0;
}
static int do_test_seccomp_errno()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_RET_ERROR, QSSB_SYS(close));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
xqssb_enable_policy(policy);
uid_t id = getuid();
int fd = close(0);
printf("close() return code: %i, errno: %s\n", fd, strerror(errno));
return fd == -1 ? 0 : 1;
}
int test_seccomp_errno()
{
return test_successful_exit(&do_test_seccomp_errno);
}
int test_landlock()
int test_landlock(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/proc/self/fd");
@ -195,7 +65,7 @@ int test_landlock()
return 1;
}
int test_landlock_deny_write()
int test_landlock_deny_write(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/tmp/");
@ -208,7 +78,7 @@ int test_landlock_deny_write()
return 1;
}
int test_nofs()
int test_nofs(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
policy->no_fs = 1;
@ -224,21 +94,21 @@ int test_nofs()
if(s == -1)
{
fprintf(stderr, "Failed to open socket but this was not requested by policy\n");
return 1;
return 0;
}
/* Expect seccomp to take care of this */
if(open("/test", O_CREAT | O_WRONLY) >= 0)
{
fprintf(stderr, "Failed: We do not expect write access\n");
return 1;
fprintf(stderr, "Failed: Do not expect write access\n");
return -1;
}
return 0;
}
int test_no_new_fds()
int test_no_new_fds(int argc, char *argv[])
{
struct qssb_policy *policy = qssb_init_policy();
policy->no_new_fds = 1;
@ -270,24 +140,23 @@ int test_no_new_fds()
struct dispatcher
{
char *name;
int (*f)();
int (*f)(int, char **);
bool must_exit_zero;
};
struct dispatcher dispatchers[] = {
{ "default", &test_default_main },
{ "seccomp-blacklisted", &test_seccomp_blacklisted},
{ "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted},
{ "seccomp-x32-kill", &test_seccomp_x32_kill},
{ "seccomp-require-last-matchall", &test_seccomp_require_last_matchall},
{ "seccomp-errno", &test_seccomp_errno},
{ "landlock", &test_landlock},
{ "landlock-deny-write", &test_landlock_deny_write },
{ "no_fs", &test_nofs},
{ "no_new_fds", &test_no_new_fds}
{ "default", &test_default_main, true },
{ "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 },
{ "no_fs", &test_nofs, false},
{ "no_new_fds", &test_no_new_fds, true}
};
int main(int argc, char *argv[])
{
if(argc < 2)
{
fprintf(stderr, "Usage: %s [testname]\n", argv[0]);
@ -298,7 +167,7 @@ int main(int argc, char *argv[])
{
for(unsigned int i = 0; i < sizeof(dispatchers)/sizeof(dispatchers[0]); i++)
{
printf("%s\n", dispatchers[i].name);
printf("%s:%i\n", dispatchers[i].name, dispatchers[i].must_exit_zero ? 1 : 0);
}
return EXIT_SUCCESS;
}
@ -308,7 +177,7 @@ int main(int argc, char *argv[])
struct dispatcher *current = &dispatchers[i];
if(strcmp(current->name, test) == 0)
{
return current->f();
return current->f(argc, argv);
}
}
fprintf(stderr, "Unknown test\n");

32
test.sh
View File

@ -32,23 +32,34 @@ function runtest_success()
function runtest()
{
testname="$1"
test_log_file="$2"
must_exit_zero="$2"
test_log_file="$3"
echo "Running: $testname. Date: $(date)" > "${test_log_file}"
echo -n "Running $1... "
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
(./test $1 || exit $?) &>> "${test_log_file}"
#exit 1 to suppress shell message like "./test.sh: line 18: pid Bad system call"
(./test $1 || exit 1) &>> "${test_log_file}"
ret=$?
SUCCESS="no"
if [ $ret -eq 0 ] ; then
runtest_success
SUCCESS="yes"
SUCCESS=0
if [ $must_exit_zero -eq 1 ] ; then
if [ $ret -eq 0 ] ; then
runtest_success
SUCCESS=1
else
runtest_fail
fi
else
runtest_fail
if [ $ret -eq 0 ] ; then
runtest_fail
else
runtest_success
SUCCESS=1
fi
fi
echo "Finished: ${testname}. Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
}
GIT_ID=$( git log --pretty="format:%h" -n1 )
@ -62,8 +73,9 @@ LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/qssb_test_${GIT_ID}_${TIMESTAMP}"
[ -d "$LOG_OUTPUT_DIR_PATH" ] || mkdir -p "$LOG_OUTPUT_DIR_PATH"
for test in $( ./test --dumptests ) ; do
testname=$( echo $test )
runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
testname=$( echo $test | cut -d":" -f1 )
must_exit_zero=$( echo "$test" | cut -d":" -f2 )
runtest "$testname" $must_exit_zero "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
done
echo
echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})"