Commitok összehasonlítása

...

15 Commit-ok

Szerző SHA1 Üzenet Dátum
215032f32c enable_no_fs(): Fix corresponding test by adding missing default policy 2021-09-06 21:43:50 +02:00
411e00715d Rename qssb_append_default_syscall_policy() to better distinguish it from qssb_append_syscall_default_policy() 2021-09-05 17:24:42 +02:00
8a9b1730de test: Remove argc,argv from tests as there was no use for them 2021-09-05 17:12:25 +02:00
b2b501d97e test: Refactor: Put seccomp tests into child processes ; Simplfy .sh
Refactor the test logic. Seccomp tests that can be
killed run in their own subprocess now.

All test functions now return 0 on success. Therefore,
the shell script can be simplified.
2021-09-05 17:12:25 +02:00
26f391f736 test: implement test_seccomp_errno() 2021-09-05 17:12:25 +02:00
68fd1a0a87 test: test_seccomp_blacklisted_call_permitted(): Add missing default policy 2021-09-05 17:12:25 +02:00
b0d0beab22 README.md: Update 2021-09-05 17:12:25 +02:00
c44ce85628 test: Add test ensuring seccomp ends with default rule, minor fixes 2021-09-05 17:12:25 +02:00
25d8ed9bca check_policy_sanity(): Add syscall policy checks 2021-09-05 17:12:25 +02:00
e389140436 test.sh: Log exit code, print yes/no instead of 1/0 2021-09-05 17:12:25 +02:00
f6af1bb78f policy: Add disable_syscall_filter policy. Add defaults only on enable.
Only add default syscall policy when disable_syscall_filter is 0 (default)
and no user-custom policy has been added.
2021-09-05 17:12:25 +02:00
9192ec3aa4 Rewrite syscall policy logic
Instead of having a blacklist and whitelist, we now allow
setting a policy that runs as a chain.

This adds qssb_append_syscalls_policy()

Furthermore, add a feature to decide per syscall which action to take.
This allows now to return an error instead of just killing the process.

In the future, it may allow us to set optimize/shrink the BPF filter.
2021-09-05 17:12:03 +02:00
51844ea3ab bpf: Deny x32 system calls for now
The arch field is the same for x86_64 and x32, thus checking it
is not enough.

Simply using x32 system calls would allow a bypass. Thus,
we must check whether the system call number is in __X32_SYSCALL_BIT.

This is of course a lazy solution, we could also add the
same system call number + _X32_SYSCALL_BIT to our black/whitelists.

For now however, this however will do.
2021-08-12 12:25:12 +02:00
66c6d28dcd bpf: Check arch value
The filter was missing this check for arch, allowing bypasses
by using different calling conventions of other architectures.

A trivial example is execve() of x86 from and x86_64 process.
2021-08-12 11:57:13 +02:00
5cd45c09b7 bpf: Use SECCOMP_RET_KILL_PROCESS instead SECCOMP_RET_KILL
We generally want to kill the process not the thread.
2021-08-12 11:40:29 +02:00
4 fájl változott, egészen pontosan 421 új sor hozzáadva és 176 régi sor törölve

Fájl megtekintése

@ -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

344
qssb.h
Fájl megtekintése

@ -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)
{
/* 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;
}
return qssb_append_syscall(&qssb_policy->allowed_syscalls, syscalls, 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;
}
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_denied_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n)
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);
}
return qssb_append_syscall(&qssb_policy->denied_syscalls, syscalls, n);
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, &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 = {
.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,12 +886,17 @@ 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)
{
if(policy->path_policies == NULL)
@ -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,20 +1010,39 @@ 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_syscalls_policy(policy, QSSB_SYSCALL_DENY_RET_ERROR, fs_access_syscalls, fs_access_syscalls_count);
if(ret != 0)
{
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;
}
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;
}
/* Enables the specified qssb_policy.
*
* This function is not atomic (and can't be). This means some
@ -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)
{
QSSB_LOG_ERROR("seccomp_enable_whitelist failed\n");
return -1;
}
if(qssb_append_predefined_standard_syscall_policy(policy) != 0)
{
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;

217
test.c
Fájl megtekintése

@ -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 0;
return f();
}
return 1;
}
int status = 0;
waitpid(pid, &status, 0);
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();
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_call_permitted(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);
xqssb_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()
{
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");

32
test.sh
Fájl megtekintése

@ -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
if [ $ret -eq 0 ] ; then
runtest_success
SUCCESS=1
else
runtest_fail
fi
SUCCESS="no"
if [ $ret -eq 0 ] ; then
runtest_success
SUCCESS="yes"
else
if [ $ret -eq 0 ] ; then
runtest_fail
else
runtest_success
SUCCESS=1
fi
runtest_fail
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})"