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 ## Status
No release yet, expiremental, API is unstable, builds will break on updates of this library. 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 ## Features
- Systemcall filtering (using seccomp-bpf) - 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. `echo 1 > /proc/sys/kernel/unprivileged_userns_clone` to disable that patch for now.
### Examples ### Examples
- looqs: https://gitea.quitesimple.org/crtxcr/looqs
- qswiki: https://gitea.quitesimple.org/crtxcr/qswiki - qswiki: https://gitea.quitesimple.org/crtxcr/qswiki
- cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb - cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb
- qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb - qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb

332
qssb.h
View File

@ -40,7 +40,6 @@
#include <linux/filter.h> #include <linux/filter.h>
#include <linux/seccomp.h> #include <linux/seccomp.h>
#include <linux/version.h> #include <linux/version.h>
#include <linux/audit.h>
#include <sys/capability.h> #include <sys/capability.h>
#include <stddef.h> #include <stddef.h>
#include <inttypes.h> #include <inttypes.h>
@ -59,18 +58,17 @@
#endif #endif
#endif #endif
//TODO: stolen from kernel samples/seccomp, GPLv2...?
#if defined(__i386__) #define ALLOW \
#define SECCOMP_AUDIT_ARCH AUDIT_ARCH_I386 BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
#elif defined(__x86_64__) #define DENY \
#define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64 BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#else
#warning Seccomp support has not been tested for qssb.h for this platform yet
#endif
#define SYSCALL(nr, jt) \ #define SYSCALL(nr, jt) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (nr), 0, 1), 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_NETWORK 1<<1
#define QSSB_UNSHARE_USER 1<<2 #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: more execv* in some architectures */
/* TODO: add more */ /* TODO: add more */
static long default_blacklisted_syscalls[] = { static int default_blacklisted_syscals[] = {
QSSB_SYS(setuid), QSSB_SYS(setuid),
QSSB_SYS(setgid), QSSB_SYS(setgid),
QSSB_SYS(chroot), 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 * However, we use it to enhance "no_fs" policy, which does not solely rely
* on seccomp anyway */ * on seccomp anyway */
static long fs_access_syscalls[] = { static int fs_access_syscalls[] = {
QSSB_SYS(chdir), QSSB_SYS(chdir),
QSSB_SYS(truncate), QSSB_SYS(truncate),
QSSB_SYS(stat), QSSB_SYS(stat),
@ -190,29 +188,13 @@ struct qssb_path_policy
struct qssb_path_policy *next; struct qssb_path_policy *next;
}; };
struct qssb_allocated_entry struct qssb_allocated_entry
{ {
void *data; /* the actual data */ 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 */ 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 */ /* Number of bytes to grow the buffer in qssb_allocated_entry with */
#define QSSB_ENTRY_ALLOC_SIZE 32 #define QSSB_ENTRY_ALLOC_SIZE 32
@ -227,7 +209,6 @@ struct qssb_policy
int no_fs; int no_fs;
int no_new_fds; int no_new_fds;
int namespace_options; int namespace_options;
int disable_syscall_filter;
/* Bind mounts all paths in path_policies into the chroot and applies /* Bind mounts all paths in path_policies into the chroot and applies
non-landlock policies */ non-landlock policies */
int mount_path_policies_to_chroot; 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;
struct qssb_path_policy **path_policies_tail; struct qssb_path_policy **path_policies_tail;
/* Do not manually add policies here, use qssb_append_syscall_policy() */ /* Do not manually add entries here, use qssb_append_denied_syscall() etc. */
struct qssb_syscall_policy *syscall_policies; struct qssb_allocated_entry denied_syscalls;
struct qssb_syscall_policy **syscall_policies_tail; 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 expandval = QSSB_ENTRY_ALLOC_SIZE > bytes ? QSSB_ENTRY_ALLOC_SIZE : bytes;
size_t sizenew = entry->size + expandval; 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); int *datanew = (int *) realloc(entry->data, sizenew);
if(datanew == NULL) if(datanew == NULL)
{ {
@ -271,68 +247,32 @@ static int qssb_entry_append(struct qssb_allocated_entry *entry, void *data, siz
return 0; 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; return qssb_append_syscall(&qssb_policy->allowed_syscalls, &syscall, 1);
*n = policy->syscall.used / sizeof(long);
} }
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; return qssb_append_syscall(&qssb_policy->allowed_syscalls, syscalls, n);
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 */ int qssb_append_denied_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n)
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); return qssb_append_syscall(&qssb_policy->denied_syscalls, 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;
}
int qssb_append_syscall_policy(struct qssb_policy *qssb_policy, unsigned int syscall_policy, long syscall)
{
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);
} }
/* Creates the default policy /* Creates the default policy
@ -347,16 +287,25 @@ struct qssb_policy *qssb_init_policy()
result->no_fs = 0; result->no_fs = 0;
result->no_new_fds = 0; result->no_new_fds = 0;
result->namespace_options = QSSB_UNSHARE_MOUNT | QSSB_UNSHARE_USER; result->namespace_options = QSSB_UNSHARE_MOUNT | QSSB_UNSHARE_USER;
result->disable_syscall_filter = 0;
result->chdir_path = NULL; result->chdir_path = NULL;
result->mount_path_policies_to_chroot = 0; result->mount_path_policies_to_chroot = 0;
result->chroot_target_path[0] = '\0'; result->chroot_target_path[0] = '\0';
result->path_policies = NULL; result->path_policies = NULL;
result->path_policies_tail = &(result->path_policies); 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; size_t blacklisted_syscalls_count = sizeof(default_blacklisted_syscals)/sizeof(default_blacklisted_syscals[0]);
result->syscall_policies_tail = &(result->syscall_policies);
int appendresult = qssb_append_denied_syscalls(result, default_blacklisted_syscals, blacklisted_syscalls_count);
if(appendresult != 0)
{
return NULL;
}
return result; 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); return qssb_append_path_policies(qssb_policy, path_policy, path, NULL);
} }
/* /*
* Fills buffer with random characters a-z. * Fills buffer with random characters a-z.
* The string will be null terminated. * The string will be null terminated.
@ -585,14 +532,6 @@ void qssb_free_policy(struct qssb_policy *ctxt)
current = current->next; current = current->next;
free(tmp); 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); free(ctxt);
} }
} }
@ -687,72 +626,36 @@ static int drop_caps()
return 0; 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 * @returns: 0 on success, -1 on error
*/ */
static int seccomp_enable(int *syscalls, size_t n, unsigned int per_syscall, unsigned int default_action)
static int qssb_enable_syscall_policy(struct qssb_policy *policy)
{ {
struct sock_filter filter[1024] = struct sock_filter filter[1024] =
{ {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,offsetof(struct seccomp_data, arch)), LOAD_SYSCALL_NR,
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; unsigned short int current_filter_index = 1;
for(size_t i = 0; i < n; i++)
struct qssb_syscall_policy *current_policy = policy->syscall_policies;
while(current_policy)
{ {
if(!is_valid_syscall_policy(current_policy->policy)) unsigned int sysc = (unsigned int) syscalls[i];
{ struct sock_filter syscall = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, sysc, 0, 1);
QSSB_LOG_ERROR("invalid syscall policy specified"); struct sock_filter action = BPF_STMT(BPF_RET+BPF_K, per_syscall);
return -1; filter[current_filter_index++] = syscall;
} filter[current_filter_index++] = action;
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;
} }
struct sock_filter da = BPF_STMT(BPF_RET+BPF_K, default_action);
filter[current_filter_index] = da;
++current_filter_index;
struct sock_fprog prog = { struct sock_fprog prog = {
.len = current_filter_index , .len = current_filter_index ,
.filter = filter, .filter = filter,
@ -767,6 +670,26 @@ static int qssb_enable_syscall_policy(struct qssb_policy *policy)
return 0; 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 #if HAVE_LANDLOCK == 1
static unsigned int qssb_flags_to_landlock(unsigned int flags) static unsigned int qssb_flags_to_landlock(unsigned int flags)
{ {
@ -886,16 +809,11 @@ static int landlock_prepare_ruleset(struct qssb_path_policy *policies)
/* Checks for illogical or dangerous combinations */ /* Checks for illogical or dangerous combinations */
static int check_policy_sanity(struct qssb_policy *policy) 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("Error: Cannot mix allowed and denied systemcalls in policy\n");
{ return -EINVAL;
QSSB_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n");
return -1;
} }
}
/* TODO: check if we have ALLOWED, but no default deny */
if(policy->mount_path_policies_to_chroot == 1) if(policy->mount_path_policies_to_chroot == 1)
{ {
@ -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) if(policy->path_policies != NULL)
{ {
@ -928,45 +854,6 @@ static int check_policy_sanity(struct qssb_policy *policy)
return -1; 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; return 0;
} }
@ -1010,35 +897,16 @@ static int enable_no_fs(struct qssb_policy *policy)
return -1; 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 if(policy->allowed_syscalls.used == 0)
{
size_t fs_access_syscalls_count = sizeof(fs_access_syscalls)/sizeof(fs_access_syscalls[0]); 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);
int ret = qssb_append_denied_syscalls(policy, fs_access_syscalls, fs_access_syscalls_count);
if(ret != 0) if(ret != 0)
{ {
QSSB_LOG_ERROR("Failed to add system calls to policy\n"); QSSB_LOG_ERROR("Failed to add system calls to blacklist\n");
return -1; 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;
}
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; return 0;
} }
@ -1190,18 +1058,26 @@ int qssb_enable_policy(struct qssb_policy *policy)
close(landlock_ruleset_fd); close(landlock_ruleset_fd);
#endif #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) 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("Failed to add standard predefined syscall policy\n"); QSSB_LOG_ERROR("seccomp_enable_whitelist failed\n");
return -1; 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; return 0;

213
test.c
View File

@ -4,113 +4,47 @@
#include <dirent.h> #include <dirent.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h>
int xqssb_enable_policy(struct qssb_policy *policy) int test_default_main(int argc, char *argv[])
{
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()
{ {
struct qssb_policy *policy = qssb_init_policy(); struct qssb_policy *policy = qssb_init_policy();
int ret = qssb_enable_policy(policy); int ret = qssb_enable_policy(policy);
return ret; return ret;
} }
static int test_expected_kill(int (*f)()) int test_both_syscalls(int argc, char *argv[])
{
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()
{ {
struct qssb_policy *policy = qssb_init_policy(); struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid)); int syscalls[] = {1,2,3};
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
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(); uid_t pid = geteuid();
pid = getuid(); pid = getuid();
return 0; return 0;
}
int test_seccomp_blacklisted()
{
return test_expected_kill(&do_test_seccomp_blacklisted);
} }
int test_seccomp_blacklisted_call_permitted(int argc, char *argv[])
static int do_test_seccomp_blacklisted_call_permitted()
{ {
struct qssb_policy *policy = qssb_init_policy(); struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid)); qssb_append_denied_syscall(policy, QSSB_SYS(getuid));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
int ret = qssb_enable_policy(policy); int ret = qssb_enable_policy(policy);
//geteuid is not blacklisted, so must succeed //geteuid is not blacklisted, so must succeed
@ -118,71 +52,7 @@ static int do_test_seccomp_blacklisted_call_permitted()
return 0; return 0;
} }
int test_landlock(int argc, char *argv[])
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()
{ {
struct qssb_policy *policy = qssb_init_policy(); struct qssb_policy *policy = qssb_init_policy();
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/proc/self/fd"); qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/proc/self/fd");
@ -195,7 +65,7 @@ int test_landlock()
return 1; return 1;
} }
int test_landlock_deny_write() int test_landlock_deny_write(int argc, char *argv[])
{ {
struct qssb_policy *policy = qssb_init_policy(); struct qssb_policy *policy = qssb_init_policy();
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/tmp/"); qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/tmp/");
@ -208,7 +78,7 @@ int test_landlock_deny_write()
return 1; return 1;
} }
int test_nofs() int test_nofs(int argc, char *argv[])
{ {
struct qssb_policy *policy = qssb_init_policy(); struct qssb_policy *policy = qssb_init_policy();
policy->no_fs = 1; policy->no_fs = 1;
@ -224,21 +94,21 @@ int test_nofs()
if(s == -1) if(s == -1)
{ {
fprintf(stderr, "Failed to open socket but this was not requested by policy\n"); 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 */ /* Expect seccomp to take care of this */
if(open("/test", O_CREAT | O_WRONLY) >= 0) if(open("/test", O_CREAT | O_WRONLY) >= 0)
{ {
fprintf(stderr, "Failed: We do not expect write access\n"); fprintf(stderr, "Failed: Do not expect write access\n");
return 1; return -1;
} }
return 0; return 0;
} }
int test_no_new_fds() int test_no_new_fds(int argc, char *argv[])
{ {
struct qssb_policy *policy = qssb_init_policy(); struct qssb_policy *policy = qssb_init_policy();
policy->no_new_fds = 1; policy->no_new_fds = 1;
@ -270,24 +140,23 @@ int test_no_new_fds()
struct dispatcher struct dispatcher
{ {
char *name; char *name;
int (*f)(); int (*f)(int, char **);
bool must_exit_zero;
}; };
struct dispatcher dispatchers[] = { struct dispatcher dispatchers[] = {
{ "default", &test_default_main }, { "default", &test_default_main, true },
{ "seccomp-blacklisted", &test_seccomp_blacklisted}, { "seccomp-blacklisted", &test_seccomp_blacklisted, false },
{ "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted}, { "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted, true },
{ "seccomp-x32-kill", &test_seccomp_x32_kill}, { "landlock", &test_landlock, true },
{ "seccomp-require-last-matchall", &test_seccomp_require_last_matchall}, { "landlock-deny-write", &test_landlock_deny_write, true },
{ "seccomp-errno", &test_seccomp_errno}, { "no_fs", &test_nofs, false},
{ "landlock", &test_landlock}, { "no_new_fds", &test_no_new_fds, true}
{ "landlock-deny-write", &test_landlock_deny_write },
{ "no_fs", &test_nofs},
{ "no_new_fds", &test_no_new_fds}
}; };
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
if(argc < 2) if(argc < 2)
{ {
fprintf(stderr, "Usage: %s [testname]\n", argv[0]); 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++) 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; return EXIT_SUCCESS;
} }
@ -308,7 +177,7 @@ int main(int argc, char *argv[])
struct dispatcher *current = &dispatchers[i]; struct dispatcher *current = &dispatchers[i];
if(strcmp(current->name, test) == 0) if(strcmp(current->name, test) == 0)
{ {
return current->f(); return current->f(argc, argv);
} }
} }
fprintf(stderr, "Unknown test\n"); fprintf(stderr, "Unknown test\n");

26
test.sh
View File

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