Compare commits
15 Commits
fa06287b13
...
215032f32c
Author | SHA1 | Date | |
---|---|---|---|
215032f32c | |||
411e00715d | |||
8a9b1730de | |||
b2b501d97e | |||
26f391f736 | |||
68fd1a0a87 | |||
b0d0beab22 | |||
c44ce85628 | |||
25d8ed9bca | |||
e389140436 | |||
f6af1bb78f | |||
9192ec3aa4 | |||
51844ea3ab | |||
66c6d28dcd | |||
5cd45c09b7 |
@ -4,6 +4,8 @@
|
||||
## 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)
|
||||
@ -36,7 +38,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
|
||||
|
332
qssb.h
332
qssb.h
@ -40,6 +40,7 @@
|
||||
#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>
|
||||
@ -58,17 +59,18 @@
|
||||
#endif
|
||||
#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)
|
||||
|
||||
#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
|
||||
|
||||
#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
|
||||
@ -140,7 +142,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
|
||||
*/
|
||||
/* TODO: more execv* in some architectures */
|
||||
/* TODO: add more */
|
||||
static int default_blacklisted_syscals[] = {
|
||||
static long default_blacklisted_syscalls[] = {
|
||||
QSSB_SYS(setuid),
|
||||
QSSB_SYS(setgid),
|
||||
QSSB_SYS(chroot),
|
||||
@ -164,7 +166,7 @@ static int default_blacklisted_syscals[] = {
|
||||
*
|
||||
* However, we use it to enhance "no_fs" policy, which does not solely rely
|
||||
* on seccomp anyway */
|
||||
static int fs_access_syscalls[] = {
|
||||
static long fs_access_syscalls[] = {
|
||||
QSSB_SYS(chdir),
|
||||
QSSB_SYS(truncate),
|
||||
QSSB_SYS(stat),
|
||||
@ -188,13 +190,29 @@ 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 size */
|
||||
size_t size; /* number of bytes allocated for data */
|
||||
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
|
||||
|
||||
@ -209,6 +227,7 @@ 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;
|
||||
@ -219,9 +238,9 @@ struct qssb_policy
|
||||
struct qssb_path_policy *path_policies;
|
||||
struct qssb_path_policy **path_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;
|
||||
/* Do not manually add policies here, use qssb_append_syscall_policy() */
|
||||
struct qssb_syscall_policy *syscall_policies;
|
||||
struct qssb_syscall_policy **syscall_policies_tail;
|
||||
|
||||
};
|
||||
|
||||
@ -232,6 +251,11 @@ 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)
|
||||
{
|
||||
@ -247,32 +271,68 @@ 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, int *syscalls, size_t n)
|
||||
static int qssb_append_syscall(struct qssb_allocated_entry *entry, long *syscalls, size_t n)
|
||||
{
|
||||
return qssb_entry_append(entry, syscalls, n * sizeof(int));
|
||||
return qssb_entry_append(entry, syscalls, n * sizeof(long));
|
||||
}
|
||||
|
||||
|
||||
int qssb_append_denied_syscall(struct qssb_policy *qssb_policy, int syscall)
|
||||
static int is_valid_syscall_policy(unsigned int policy)
|
||||
{
|
||||
return qssb_append_syscall(&qssb_policy->denied_syscalls, &syscall, 1);
|
||||
return policy == QSSB_SYSCALL_ALLOW || policy == QSSB_SYSCALL_DENY_RET_ERROR || policy == QSSB_SYSCALL_DENY_KILL_PROCESS;
|
||||
}
|
||||
|
||||
int qssb_append_allowed_syscall(struct qssb_policy *qssb_policy, int syscall)
|
||||
static void get_syscall_array(struct qssb_syscall_policy *policy, long **syscall, size_t *n)
|
||||
{
|
||||
return qssb_append_syscall(&qssb_policy->allowed_syscalls, &syscall, 1);
|
||||
*syscall = (long *) policy->syscall.data;
|
||||
*n = policy->syscall.used / sizeof(long);
|
||||
}
|
||||
|
||||
int qssb_append_allowed_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n)
|
||||
int qssb_append_syscalls_policy(struct qssb_policy *qssb_policy, unsigned int syscall_policy, long *syscalls, size_t n)
|
||||
{
|
||||
|
||||
return qssb_append_syscall(&qssb_policy->allowed_syscalls, syscalls, 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(¤t_policy->syscall, syscalls, n);
|
||||
}
|
||||
current_policy = current_policy->next;
|
||||
}
|
||||
|
||||
int qssb_append_denied_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n)
|
||||
/* 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;
|
||||
}
|
||||
|
||||
return qssb_append_syscall(&qssb_policy->denied_syscalls, syscalls, n);
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
@ -287,25 +347,16 @@ 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;
|
||||
|
||||
size_t blacklisted_syscalls_count = sizeof(default_blacklisted_syscals)/sizeof(default_blacklisted_syscals[0]);
|
||||
result->syscall_policies = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -350,6 +401,8 @@ 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.
|
||||
@ -532,6 +585,14 @@ 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);
|
||||
}
|
||||
}
|
||||
@ -626,36 +687,72 @@ 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 per_syscall seccomp action for system calls
|
||||
* Enables the seccomp policy
|
||||
*
|
||||
* syscalls: array of system calls numbers.
|
||||
* per_syscall: action to apply for each system call
|
||||
* default_action: the default action at the end
|
||||
* policy: qssb policy object
|
||||
*
|
||||
* @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] =
|
||||
{
|
||||
LOAD_SYSCALL_NR,
|
||||
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 = 1;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
unsigned short int current_filter_index = 6;
|
||||
|
||||
struct qssb_syscall_policy *current_policy = policy->syscall_policies;
|
||||
while(current_policy)
|
||||
{
|
||||
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;
|
||||
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, ¤t_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 = {
|
||||
.len = current_filter_index ,
|
||||
.filter = filter,
|
||||
@ -670,26 +767,6 @@ static int seccomp_enable(int *syscalls, size_t n, unsigned int per_syscall, uns
|
||||
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)
|
||||
{
|
||||
@ -809,11 +886,16 @@ 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->denied_syscalls.used > 0 && policy->allowed_syscalls.used > 0)
|
||||
if(policy->no_new_privs != 1)
|
||||
{
|
||||
QSSB_LOG_ERROR("Error: Cannot mix allowed and denied systemcalls in policy\n");
|
||||
return -EINVAL;
|
||||
if(policy->syscall_policies != NULL)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -829,14 +911,6 @@ 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)
|
||||
{
|
||||
@ -854,6 +928,45 @@ 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;
|
||||
}
|
||||
|
||||
@ -897,16 +1010,35 @@ static int enable_no_fs(struct qssb_policy *policy)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(policy->allowed_syscalls.used == 0)
|
||||
{
|
||||
//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_denied_syscalls(policy, fs_access_syscalls, fs_access_syscalls_count);
|
||||
int ret = qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_RET_ERROR, fs_access_syscalls, fs_access_syscalls_count);
|
||||
if(ret != 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("Failed to add system calls to blacklist\n");
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@ -1058,26 +1190,18 @@ int qssb_enable_policy(struct qssb_policy *policy)
|
||||
close(landlock_ruleset_fd);
|
||||
#endif
|
||||
|
||||
if(policy->allowed_syscalls.used > 0)
|
||||
if(policy->syscall_policies == NULL && policy->disable_syscall_filter == 0)
|
||||
{
|
||||
int *syscalls = (int *)policy->allowed_syscalls.data;
|
||||
size_t n = policy->allowed_syscalls.used / sizeof(int);
|
||||
if(seccomp_enable_whitelist(syscalls, n) < 0)
|
||||
if(qssb_append_predefined_standard_syscall_policy(policy) != 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("seccomp_enable_whitelist failed\n");
|
||||
QSSB_LOG_ERROR("Failed to add standard predefined syscall policy\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(policy->denied_syscalls.used > 0)
|
||||
if(policy->syscall_policies != NULL)
|
||||
{
|
||||
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 qssb_enable_syscall_policy(policy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
199
test.c
199
test.c
@ -4,47 +4,113 @@
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
int test_default_main(int argc, char *argv[])
|
||||
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()
|
||||
{
|
||||
struct qssb_policy *policy = qssb_init_policy();
|
||||
int ret = qssb_enable_policy(policy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int test_both_syscalls(int argc, char *argv[])
|
||||
static int test_expected_kill(int (*f)())
|
||||
{
|
||||
struct qssb_policy *policy = qssb_init_policy();
|
||||
int syscalls[] = {1,2,3};
|
||||
|
||||
qssb_append_denied_syscalls(policy, syscalls, 3);
|
||||
qssb_append_allowed_syscalls(policy, syscalls, 3);
|
||||
|
||||
int ret = qssb_enable_policy(policy);
|
||||
if(ret != 0)
|
||||
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;
|
||||
}
|
||||
|
||||
int test_seccomp_blacklisted(int argc, char *argv[])
|
||||
|
||||
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();
|
||||
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));
|
||||
xqssb_enable_policy(policy);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
qssb_append_denied_syscall(policy, QSSB_SYS(getuid));
|
||||
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
|
||||
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
|
||||
|
||||
int ret = qssb_enable_policy(policy);
|
||||
//geteuid is not blacklisted, so must succeed
|
||||
@ -52,7 +118,71 @@ int test_seccomp_blacklisted_call_permitted(int argc, char *argv[])
|
||||
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();
|
||||
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/proc/self/fd");
|
||||
@ -65,7 +195,7 @@ int test_landlock(int argc, char *argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
int test_landlock_deny_write(int argc, char *argv[])
|
||||
int test_landlock_deny_write()
|
||||
{
|
||||
struct qssb_policy *policy = qssb_init_policy();
|
||||
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/tmp/");
|
||||
@ -78,7 +208,7 @@ int test_landlock_deny_write(int argc, char *argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
int test_nofs(int argc, char *argv[])
|
||||
int test_nofs()
|
||||
{
|
||||
struct qssb_policy *policy = qssb_init_policy();
|
||||
policy->no_fs = 1;
|
||||
@ -94,21 +224,21 @@ int test_nofs(int argc, char *argv[])
|
||||
if(s == -1)
|
||||
{
|
||||
fprintf(stderr, "Failed to open socket but this was not requested by policy\n");
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
fprintf(stderr, "Failed: We do not expect write access\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int test_no_new_fds(int argc, char *argv[])
|
||||
int test_no_new_fds()
|
||||
{
|
||||
struct qssb_policy *policy = qssb_init_policy();
|
||||
policy->no_new_fds = 1;
|
||||
@ -140,23 +270,24 @@ int test_no_new_fds(int argc, char *argv[])
|
||||
struct dispatcher
|
||||
{
|
||||
char *name;
|
||||
int (*f)(int, char **);
|
||||
bool must_exit_zero;
|
||||
int (*f)();
|
||||
};
|
||||
|
||||
struct dispatcher dispatchers[] = {
|
||||
{ "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}
|
||||
{ "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}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
if(argc < 2)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [testname]\n", argv[0]);
|
||||
@ -167,7 +298,7 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
for(unsigned int i = 0; i < sizeof(dispatchers)/sizeof(dispatchers[0]); i++)
|
||||
{
|
||||
printf("%s:%i\n", dispatchers[i].name, dispatchers[i].must_exit_zero ? 1 : 0);
|
||||
printf("%s\n", dispatchers[i].name);
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@ -177,7 +308,7 @@ int main(int argc, char *argv[])
|
||||
struct dispatcher *current = &dispatchers[i];
|
||||
if(strcmp(current->name, test) == 0)
|
||||
{
|
||||
return current->f(argc, argv);
|
||||
return current->f();
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "Unknown test\n");
|
||||
|
26
test.sh
26
test.sh
@ -32,34 +32,23 @@ function runtest_success()
|
||||
function runtest()
|
||||
{
|
||||
testname="$1"
|
||||
must_exit_zero="$2"
|
||||
test_log_file="$3"
|
||||
test_log_file="$2"
|
||||
|
||||
echo "Running: $testname. Date: $(date)" > "${test_log_file}"
|
||||
|
||||
echo -n "Running $1... "
|
||||
#exit 1 to suppress shell message like "./test.sh: line 18: pid Bad system call"
|
||||
(./test $1 || exit 1) &>> "${test_log_file}"
|
||||
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
|
||||
(./test $1 || exit $?) &>> "${test_log_file}"
|
||||
ret=$?
|
||||
SUCCESS=0
|
||||
if [ $must_exit_zero -eq 1 ] ; then
|
||||
SUCCESS="no"
|
||||
if [ $ret -eq 0 ] ; then
|
||||
runtest_success
|
||||
SUCCESS=1
|
||||
SUCCESS="yes"
|
||||
else
|
||||
runtest_fail
|
||||
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}"
|
||||
|
||||
}
|
||||
|
||||
GIT_ID=$( git log --pretty="format:%h" -n1 )
|
||||
@ -73,9 +62,8 @@ 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 | cut -d":" -f1 )
|
||||
must_exit_zero=$( echo "$test" | cut -d":" -f2 )
|
||||
runtest "$testname" $must_exit_zero "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
|
||||
testname=$( echo $test )
|
||||
runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
|
||||
done
|
||||
echo
|
||||
echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})"
|
||||
|
Loading…
Reference in New Issue
Block a user