Begin low-level seccomp arg filter interface

Squashed:
test: Adjust existing to new API with arg filters
test: Add tests for low-level seccomp args filter API
test: Add seccomp_filter_mixed()
test: Switch to syscall() everywhere
append_syscall_to_bpf(): Apply EXILE_SYSCALL_EXIT_BPF_NO_MATCH also for sock_filter.jt
This commit is contained in:
Albert S. 2021-11-21 15:28:46 +01:00
parent 48deab0dde
commit 15a6850023
2 changed files with 187 additions and 126 deletions

174
exile.h
View File

@ -673,33 +673,24 @@ struct exile_path_policy
struct exile_path_policy *next; struct exile_path_policy *next;
}; };
/* Special values */
struct exile_allocated_entry
{
void *data; /* the actual data */
size_t size; /* number of bytes allocated for data */
size_t used; /* number of bytes in use */
};
/* Special value */
#define EXILE_SYSCALL_MATCH_ALL -1 #define EXILE_SYSCALL_MATCH_ALL -1
#define EXILE_SYSCALL_EXIT_BPF_NO_MATCH 255 //exit the bpf filter, not matching policy
#define EXILE_SYSCALL_ALLOW 1 #define EXILE_SYSCALL_ALLOW 1
#define EXILE_SYSCALL_DENY_KILL_PROCESS 2 #define EXILE_SYSCALL_DENY_KILL_PROCESS 2
#define EXILE_SYSCALL_DENY_RET_ERROR 3 #define EXILE_SYSCALL_DENY_RET_ERROR 3
#define EXILE_ARGFILTERS_COUNT 60
struct exile_syscall_policy struct exile_syscall_policy
{ {
struct exile_allocated_entry syscall; struct sock_filter argfilters[EXILE_ARGFILTERS_COUNT];
size_t argfilterscount;
long syscall;
unsigned int policy; unsigned int policy;
struct exile_syscall_policy *next; struct exile_syscall_policy *next;
}; };
/* Number of bytes to grow the buffer in exile_allocated_entry with */
#define EXILE_ENTRY_ALLOC_SIZE 32
/* Policy tells exile what to do */ /* Policy tells exile what to do */
struct exile_policy struct exile_policy
{ {
@ -727,86 +718,32 @@ struct exile_policy
}; };
static int exile_entry_append(struct exile_allocated_entry *entry, void *data, size_t bytes)
{
size_t remaining = entry->size - entry->used;
if(remaining < bytes)
{
size_t expandval = EXILE_ENTRY_ALLOC_SIZE > bytes ? EXILE_ENTRY_ALLOC_SIZE : bytes;
size_t sizenew = 0;
if(__builtin_add_overflow(entry->size, expandval, &sizenew))
{
EXILE_LOG_ERROR("overflow in exile_entry_append\n");
return -EINVAL;
}
int *datanew = (int *) realloc(entry->data, sizenew);
if(datanew == NULL)
{
EXILE_LOG_ERROR("failed to resize array: %s\n", strerror(errno));
return -1;
}
entry->size = sizenew;
entry->data = datanew;
}
uint8_t *target = (uint8_t *) entry->data;
memcpy(target + entry->used, data, bytes);
entry->used = entry->used + bytes;
return 0;
}
static int exile_append_syscall(struct exile_allocated_entry *entry, long *syscalls, size_t n)
{
size_t bytes = 0;
if(__builtin_mul_overflow(n, sizeof(long), &bytes))
{
EXILE_LOG_ERROR("Overflow while trying to add system calls\n");
return -EINVAL;
}
return exile_entry_append(entry, syscalls, bytes);
}
static int is_valid_syscall_policy(unsigned int policy) static int is_valid_syscall_policy(unsigned int policy)
{ {
return policy == EXILE_SYSCALL_ALLOW || policy == EXILE_SYSCALL_DENY_RET_ERROR || policy == EXILE_SYSCALL_DENY_KILL_PROCESS; return policy == EXILE_SYSCALL_ALLOW || policy == EXILE_SYSCALL_DENY_RET_ERROR || policy == EXILE_SYSCALL_DENY_KILL_PROCESS;
} }
static void get_syscall_array(struct exile_syscall_policy *policy, long **syscall, size_t *n) int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n)
{ {
*syscall = (long *) policy->syscall.data;
*n = policy->syscall.used / sizeof(long);
}
int exile_append_syscalls_policy(struct exile_policy *exile_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 exile_syscall_policy *current_policy = exile_policy->syscall_policies;
while(current_policy)
{
if(current_policy->policy == syscall_policy)
{
return exile_append_syscall(&current_policy->syscall, syscalls, n);
}
current_policy = current_policy->next;
}
/* We don't so we create a new policy */
struct exile_syscall_policy *newpolicy = (struct exile_syscall_policy *) calloc(1, sizeof(struct exile_syscall_policy)); struct exile_syscall_policy *newpolicy = (struct exile_syscall_policy *) calloc(1, sizeof(struct exile_syscall_policy));
if(newpolicy == NULL) if(newpolicy == NULL)
{ {
EXILE_LOG_ERROR("Failed to allocate memory for syscall policy\n"); EXILE_LOG_ERROR("Failed to allocate memory for syscall policy\n");
return -1; return -1;
} }
newpolicy->policy = syscall_policy;
int ret = exile_append_syscall(&newpolicy->syscall, syscalls, n); newpolicy->syscall = syscall;
if(ret != 0) newpolicy->argfilterscount = n;
if(n > EXILE_ARGFILTERS_COUNT)
{ {
free(newpolicy); EXILE_LOG_ERROR("Too many argfilters supplied\n");
EXILE_LOG_ERROR("Failed to append syscall\n");
return -1; return -1;
} }
for(size_t i = 0; i < n; i++)
{
newpolicy->argfilters[i] = argfilters[i];
}
newpolicy->next = NULL; newpolicy->next = NULL;
newpolicy->policy = syscall_policy;
*(exile_policy->syscall_policies_tail) = newpolicy; *(exile_policy->syscall_policies_tail) = newpolicy;
exile_policy->syscall_policies_tail = &(newpolicy->next); exile_policy->syscall_policies_tail = &(newpolicy->next);
@ -815,14 +752,10 @@ int exile_append_syscalls_policy(struct exile_policy *exile_policy, unsigned int
return 0; return 0;
} }
int exile_append_syscall_policy(struct exile_policy *exile_policy, unsigned int syscall_policy, long syscall)
{
return exile_append_syscalls_policy(exile_policy, syscall_policy, &syscall, 1);
}
int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy) int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy)
{ {
return exile_append_syscall_policy(exile_policy, default_policy, EXILE_SYSCALL_MATCH_ALL); return exile_append_syscall_policy(exile_policy, EXILE_SYSCALL_MATCH_ALL, default_policy, NULL, 0);
} }
static void get_group_syscalls(uint64_t mask, long *syscalls, size_t *n) static void get_group_syscalls(uint64_t mask, long *syscalls, size_t *n)
@ -856,8 +789,17 @@ int exile_append_group_syscall_policy(struct exile_policy *exile_policy, unsigne
EXILE_LOG_ERROR("Error: No syscalls found for group mask\n"); EXILE_LOG_ERROR("Error: No syscalls found for group mask\n");
return -EINVAL; return -EINVAL;
} }
for(size_t i = 0; i < n; i++)
{
int ret = exile_append_syscall_policy(exile_policy, syscalls[i], syscall_policy, NULL, 0);
if(ret != 0)
{
EXILE_LOG_ERROR("Error: Failed while trying to append group policy\n");
return ret;
}
}
return exile_append_syscalls_policy(exile_policy, syscall_policy, syscalls, n); return 0;
} }
/* Creates the default policy /* Creates the default policy
@ -1240,8 +1182,9 @@ static int drop_caps()
static void append_syscalls_to_bpf(long *syscalls, size_t n, unsigned int action, struct sock_filter *filter, unsigned short int *start_index) static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, struct sock_filter *filter, unsigned short int *start_index)
{ {
unsigned int action = syscallpolicy->policy;
if(action == EXILE_SYSCALL_ALLOW) if(action == EXILE_SYSCALL_ALLOW)
{ {
action = SECCOMP_RET_ALLOW; action = SECCOMP_RET_ALLOW;
@ -1254,18 +1197,45 @@ static void append_syscalls_to_bpf(long *syscalls, size_t n, unsigned int action
{ {
action = SECCOMP_RET_ERRNO|EACCES; action = SECCOMP_RET_ERRNO|EACCES;
} }
for(size_t i = 0; i < n; i++) long syscall = syscallpolicy->syscall;
{
long syscall = syscalls[i]; struct sock_filter syscall_load = BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr));
filter[(*start_index)++] = syscall_load;
if(syscall != EXILE_SYSCALL_MATCH_ALL) if(syscall != EXILE_SYSCALL_MATCH_ALL)
{ {
struct sock_filter syscall_check = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (unsigned int) syscall, 0, 1); /* How many steps forward to jump when we don't match. This is either the last statement,
* i. e. the default action or the next syscall policy */
__u8 next_syscall_pc = 1;
if(__builtin_add_overflow(next_syscall_pc, syscallpolicy->argfilterscount, &next_syscall_pc))
{
EXILE_LOG_ERROR("Error: Overflow while trying to calculate jump offset\n");
/* TODO: Return error */
return;
}
struct sock_filter syscall_check = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (unsigned int) syscall, 0, next_syscall_pc);
filter[(*start_index)++] = syscall_check; filter[(*start_index)++] = syscall_check;
--next_syscall_pc;
for(size_t i = 0; i < syscallpolicy->argfilterscount; i++)
{
filter[*start_index] = syscallpolicy->argfilters[i];
__u8 jump_count = next_syscall_pc;
if(filter[*start_index].jt == EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
{
filter[*start_index].jt = jump_count;
}
if(filter[*start_index].jf == EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
{
filter[*start_index].jf = jump_count;
}
--next_syscall_pc;
++*start_index;
}
} }
struct sock_filter syscall_action = BPF_STMT(BPF_RET+BPF_K, action); struct sock_filter syscall_action = BPF_STMT(BPF_RET+BPF_K, action);
/* TODO: we can do better than adding this below every jump */ /* TODO: we can do better than adding this below every jump */
filter[(*start_index)++] = syscall_action; filter[(*start_index)++] = syscall_action;
}
} }
/* /*
* Enables the seccomp policy * Enables the seccomp policy
@ -1297,21 +1267,8 @@ static int exile_enable_syscall_policy(struct exile_policy *policy)
EXILE_LOG_ERROR("invalid syscall policy specified\n"); EXILE_LOG_ERROR("invalid syscall policy specified\n");
return -1; return -1;
} }
long *syscalls = NULL; /* TODO: reintroduce overflow checks */
size_t n = 0; append_syscall_to_bpf(current_policy, filter, &current_filter_index);
get_syscall_array(current_policy, &syscalls, &n);
unsigned short int newsize;
if(__builtin_add_overflow(current_filter_index, n, &newsize))
{
EXILE_LOG_ERROR("Overflow when trying to add new system calls\n");
return -EINVAL;
}
if(newsize > (sizeof(filter)/sizeof(filter[0]))-1)
{
EXILE_LOG_ERROR("Too many system calls added\n");
return -EINVAL;
}
append_syscalls_to_bpf(syscalls, n, current_policy->policy, filter, &current_filter_index);
current_policy = current_policy->next; current_policy = current_policy->next;
} }
@ -1504,10 +1461,7 @@ static int check_policy_sanity(struct exile_policy *policy)
int last_policy = 0; int last_policy = 0;
while(syscall_policy) while(syscall_policy)
{ {
long *syscall; if(syscall_policy->syscall == EXILE_SYSCALL_MATCH_ALL)
size_t n = 0;
get_syscall_array(syscall_policy, &syscall, &n);
if(syscall[n-1] == EXILE_SYSCALL_MATCH_ALL)
{ {
last_match_all = i; last_match_all = i;
match_all_policy = syscall_policy->policy; match_all_policy = syscall_policy->policy;

131
test.c
View File

@ -87,13 +87,13 @@ static int test_successful_exit(int (*f)())
static int do_test_seccomp_blacklisted() static int do_test_seccomp_blacklisted()
{ {
struct exile_policy *policy = exile_init_policy(); struct exile_policy *policy = exile_init_policy();
exile_append_syscall_policy(policy, EXILE_SYSCALL_DENY_KILL_PROCESS, EXILE_SYS(getuid)); exile_append_syscall_policy(policy,EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW); exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy); xexile_enable_policy(policy);
uid_t pid = geteuid(); uid_t pid = syscall(EXILE_SYS(geteuid));
pid = getuid(); pid = syscall(EXILE_SYS(getuid));
return 0; return 0;
@ -108,12 +108,12 @@ static int do_test_seccomp_blacklisted_call_permitted()
{ {
struct exile_policy *policy = exile_init_policy(); struct exile_policy *policy = exile_init_policy();
exile_append_syscall_policy(policy, EXILE_SYSCALL_DENY_KILL_PROCESS, EXILE_SYS(getuid)); exile_append_syscall_policy(policy, EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW); exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy); xexile_enable_policy(policy);
//geteuid is not blacklisted, so must succeed //geteuid is not blacklisted, so must succeed
uid_t pid = geteuid(); uid_t pid = syscall(EXILE_SYS(geteuid));
return 0; return 0;
} }
@ -127,7 +127,7 @@ static int do_test_seccomp_x32_kill()
{ {
struct exile_policy *policy = exile_init_policy(); struct exile_policy *policy = exile_init_policy();
exile_append_syscall_policy(policy, EXILE_SYSCALL_DENY_KILL_PROCESS, EXILE_SYS(getuid)); exile_append_syscall_policy(policy, EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW); exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy); xexile_enable_policy(policy);
@ -148,7 +148,7 @@ int test_seccomp_require_last_matchall()
{ {
struct exile_policy *policy = exile_init_policy(); struct exile_policy *policy = exile_init_policy();
exile_append_syscall_policy(policy, EXILE_SYSCALL_DENY_KILL_PROCESS, EXILE_SYS(getuid)); exile_append_syscall_policy(policy, EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
int status = exile_enable_policy(policy); int status = exile_enable_policy(policy);
if(status == 0) if(status == 0)
@ -163,13 +163,13 @@ static int do_test_seccomp_errno()
{ {
struct exile_policy *policy = exile_init_policy(); struct exile_policy *policy = exile_init_policy();
exile_append_syscall_policy(policy, EXILE_SYSCALL_DENY_RET_ERROR, EXILE_SYS(close)); exile_append_syscall_policy(policy, EXILE_SYS(close),EXILE_SYSCALL_DENY_RET_ERROR, NULL, 0);
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW); exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy); xexile_enable_policy(policy);
uid_t id = getuid(); uid_t id = syscall(EXILE_SYS(getuid));
int fd = close(0); int fd = syscall(EXILE_SYS(close), 0);
printf("close() return code: %i, errno: %s\n", fd, strerror(errno)); printf("close() return code: %i, errno: %s\n", fd, strerror(errno));
return fd == -1 ? 0 : 1; return fd == -1 ? 0 : 1;
} }
@ -185,7 +185,11 @@ static int test_seccomp_group()
{ {
struct exile_policy *policy = exile_init_policy(); struct exile_policy *policy = exile_init_policy();
exile_append_group_syscall_policy(policy, EXILE_SYSCALL_DENY_RET_ERROR, EXILE_SYSCGROUP_SOCKET); if(exile_append_group_syscall_policy(policy, EXILE_SYSCALL_DENY_RET_ERROR, EXILE_SYSCGROUP_SOCKET) != 0)
{
printf("nothing added\n");
return 1;
}
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW); exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy); xexile_enable_policy(policy);
@ -193,7 +197,107 @@ static int test_seccomp_group()
int s = socket(AF_INET,SOCK_STREAM,0); int s = socket(AF_INET,SOCK_STREAM,0);
if(s != -1) if(s != -1)
{ {
printf("Failed: socket was expected to return error\n"); printf("Failed: socket was expected to return error, but returned %i\n", s);
return 1;
}
return 0;
}
int test_seccomp_argfilter_allowed()
{
struct exile_policy *policy = exile_init_policy();
struct sock_filter argfilter[2] =
{
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, O_WRONLY, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
};
exile_append_syscall_policy(policy, EXILE_SYS(open),EXILE_SYSCALL_DENY_RET_ERROR, argfilter, 2);
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy);
char *t = "/dev/random";
int ret = (int) syscall(EXILE_SYS(open),t, O_RDONLY);
if(ret == -1)
{
printf("Failed: open was expected to succeed, but returned %i\n", ret);
return 1;
}
return 0;
}
int test_seccomp_argfilter_filtered()
{
struct exile_policy *policy = exile_init_policy();
struct sock_filter argfilter[2] =
{
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, O_WRONLY, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
};
exile_append_syscall_policy(policy, EXILE_SYS(open),EXILE_SYSCALL_DENY_RET_ERROR, argfilter, 2);
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy);
char *t = "/dev/random";
int ret = (int) syscall(EXILE_SYS(open),t, O_WRONLY);
if(ret != -1)
{
printf("Failed: open was expected to fail, but returned %i\n", ret);
return 1;
}
return 0;
}
int test_seccomp_argfilter_mixed()
{
struct exile_policy *policy = exile_init_policy();
struct sock_filter argfilter[2] =
{
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, O_WRONLY, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
};
exile_append_syscall_policy(policy, EXILE_SYS(stat),EXILE_SYSCALL_DENY_RET_ERROR, NULL,0);
exile_append_syscall_policy(policy, EXILE_SYS(open),EXILE_SYSCALL_DENY_RET_ERROR, argfilter, 2);
exile_append_syscall_policy(policy, EXILE_SYS(getpid),EXILE_SYSCALL_DENY_RET_ERROR, NULL, 0);
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
xexile_enable_policy(policy);
struct stat statbuf;
int s = (int) syscall(EXILE_SYS(stat), "/dev/urandom", &statbuf);
if(s != -1)
{
printf("Failed: stat was expected to fail, but returned %i\n", s);
return 1;
}
pid_t p = (pid_t) syscall(EXILE_SYS(getpid));
if(p != -1)
{
printf("Failed: getpid was expected to fail, but returned %i\n", p);
return 1;
}
char *t = "/dev/random";
int ret = (int) syscall(EXILE_SYS(open),t, O_WRONLY);
if(ret != -1)
{
printf("Failed: open was expected to fail, but returned %i\n", ret);
return 1;
}
ret = (int) syscall(EXILE_SYS(open), t, O_RDONLY);
if(ret == -1)
{
printf("Failed: open with O_RDONLY was expected to succeed, but returned %i\n", ret);
return 1; return 1;
} }
return 0; return 0;
@ -300,6 +404,9 @@ struct dispatcher dispatchers[] = {
{ "seccomp-require-last-matchall", &test_seccomp_require_last_matchall}, { "seccomp-require-last-matchall", &test_seccomp_require_last_matchall},
{ "seccomp-errno", &test_seccomp_errno}, { "seccomp-errno", &test_seccomp_errno},
{ "seccomp-group", &test_seccomp_group}, { "seccomp-group", &test_seccomp_group},
{ "seccomp-argfilter-allowed", &test_seccomp_argfilter_allowed},
{ "seccomp-argfilter-filtered", &test_seccomp_argfilter_filtered},
{ "seccomp-argfilter-mixed", &test_seccomp_argfilter_mixed},
{ "landlock", &test_landlock}, { "landlock", &test_landlock},
{ "landlock-deny-write", &test_landlock_deny_write }, { "landlock-deny-write", &test_landlock_deny_write },
{ "no_fs", &test_nofs}, { "no_fs", &test_nofs},