Compare commits

..

No commits in common. "4824c6eaa9043878daaba7b3778338f5bf913f06" and "b2306299d5cba0fdb40c348cfc17d105078104e0" have entirely different histories.

299
exile.h
View File

@ -266,16 +266,6 @@ struct exile_path_policy
#define EXILE_BPF_NOP \ #define EXILE_BPF_NOP \
BPF_STMT(BPF_JMP+BPF_JA,0) BPF_STMT(BPF_JMP+BPF_JA,0)
/* A few more dirty markers to simplify array block initializers. We replace those
in append_syscall_to_bpf(). The k value is meaningless here and we don't expect
to ever have filter code actually wanting to jump that many steps forward. So
they serve as an special value we will replace with actual ones. */
#define EXILE_BPF_RETURN_MATCHING \
BPF_STMT(BPF_JMP+BPF_JA,1234)
#define EXILE_BPF_RETURN_NOT_MATCHING \
BPF_STMT(BPF_JMP+BPF_JA,5678)
#define EXILE_BPF_LOAD_SECCOMP_ARG(nr) \ #define EXILE_BPF_LOAD_SECCOMP_ARG(nr) \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[nr]))) BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[nr])))
@ -689,176 +679,198 @@ int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsig
return exile_append_syscall_policy(exile_policy, EXILE_SYSCALL_MATCH_ALL, default_policy, NULL, 0); return exile_append_syscall_policy(exile_policy, EXILE_SYSCALL_MATCH_ALL, default_policy, NULL, 0);
} }
struct exile_syscall_filter
{
uint64_t vowmask; /* Apply filter if this mask is set. 0 = ignore mask, apply always */
struct sock_filter filter;
int whenset; /* 1 = Filter should be added if vowmask is contained in pledge mask, otherwise won't be added. */
};
#define COUNT_EXILE_SYSCALL_FILTER(f) \
sizeof(f)/sizeof(f[0])
#define EXILE_SYSCALL_FILTER_LOAD_ARG(val) \
{ 0, EXILE_BPF_LOAD_SECCOMP_ARG(val), 0}
/* Returns, for the specific syscall, the correct sock_filter struct for the provided vow_promises /* Returns, for the specific syscall, the correct sock_filter struct for the provided vow_promises
*
* Returns: 0 if none copied, otherwise the number of entries in "filter". Returns: 0 if none copied, otherwise the number of entries in "filter".
*/ */
static int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy) static int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy)
{ {
/* How to read this: /* How to read this:
* Keep in mind our default action is to deny, unless it's a syscall from a vow promise. Then it will be * Keep in mind our default action is do deny, unless it's a syscall from an vow promise. Then it will be
* accepted if the argument values are good (if we care about them at all). * accepted if the argument values are good (if we care about them at all).
* EXILE_BPF_MATCH() means the argument value is good, and the syscall can be accepted without further checks * EXILE_BPF_MATCH() means the argument value is good, and the syscall can be accepted
* EXILE_BPF_NO_MATCH() means the syscall won't be allowed because the value is illegal * EXILE_BPF_NO_MATCH() means the syscall won't be allowed because the value is illegal
*
* First field (vowmask): The mask to check
* Last field (whenset): If mask is set in vow_promises, then add this filter, otherwise don't.
*/ */
struct sock_filter mmap_no_exec[] = {
EXILE_BPF_LOAD_SECCOMP_ARG(2),
EXILE_BPF_NO_MATCH(PROT_EXEC)
};
struct exile_syscall_filter mmap_filter[] = { struct sock_filter ioctl_default[] = {
EXILE_SYSCALL_FILTER_LOAD_ARG(2), EXILE_BPF_LOAD_SECCOMP_ARG(1),
{ EXILE_SYSCALL_VOW_PROT_EXEC, EXILE_BPF_NO_MATCH_SET(PROT_EXEC), 0}, EXILE_BPF_MATCH(FIONREAD),
EXILE_BPF_MATCH(FIONBIO),
EXILE_BPF_MATCH(FIOCLEX),
EXILE_BPF_CMP_EQ(FIONCLEX, EXILE_SYSCALL_EXIT_BPF_RETURN, EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
};
/* open() and friends with read-only flags */
struct sock_filter open_rdonly[] = {
EXILE_BPF_LOAD_SECCOMP_ARG(1),
EXILE_BPF_NO_MATCH_SET(O_CREAT),
EXILE_BPF_NO_MATCH_SET(O_TMPFILE),
EXILE_BPF_NO_MATCH_SET(O_WRONLY),
EXILE_BPF_NO_MATCH_SET(O_RDWR),
EXILE_BPF_NO_MATCH_SET(O_APPEND),
};
struct sock_filter socket_filter[4] = {
EXILE_BPF_LOAD_SECCOMP_ARG(0),
EXILE_BPF_NOP,
EXILE_BPF_NOP,
EXILE_BPF_NOP
};
struct sock_filter setsockopt_filter[] = {
EXILE_BPF_LOAD_SECCOMP_ARG(2),
EXILE_BPF_NO_MATCH(SO_DEBUG),
EXILE_BPF_NO_MATCH(SO_SNDBUFFORCE)
}; };
struct exile_syscall_filter ioctl_filter[] = { struct sock_filter clone_filter[] = {
EXILE_SYSCALL_FILTER_LOAD_ARG(1), /* It's the first argument for x86_64 */
{ EXILE_SYSCALL_VOW_IOCTL, EXILE_BPF_RETURN_MATCHING, 1 }, EXILE_BPF_LOAD_SECCOMP_ARG(0),
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1}, EXILE_BPF_CMP_SET(CLONE_VM, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH),
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONBIO), 1}, EXILE_BPF_CMP_SET(CLONE_THREAD, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH),
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1}, EXILE_BPF_NO_MATCH_SET(CLONE_NEWCGROUP),
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIOCLEX), 1}, EXILE_BPF_NO_MATCH_SET(CLONE_NEWIPC),
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONCLEX), 1}, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNET),
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_RETURN_NOT_MATCHING, 1} EXILE_BPF_NO_MATCH_SET(CLONE_NEWNS),
EXILE_BPF_NO_MATCH_SET(CLONE_NEWPID),
EXILE_BPF_NO_MATCH_SET(CLONE_NEWUSER),
EXILE_BPF_NO_MATCH_SET(CLONE_NEWUTS),
}; };
struct exile_syscall_filter open_filter[] = { struct sock_filter prctl_default[] ={
EXILE_SYSCALL_FILTER_LOAD_ARG(1), EXILE_BPF_LOAD_SECCOMP_ARG(0),
{ EXILE_SYSCALL_VOW_CPATH, EXILE_BPF_NO_MATCH_SET(O_CREAT), 0 }, EXILE_BPF_MATCH(PR_SET_NO_NEW_PRIVS),
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_TMPFILE),0 }, EXILE_BPF_MATCH(PR_GET_NO_NEW_PRIVS),
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_WRONLY),0 }, EXILE_BPF_MATCH(PR_GET_NAME),
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_RDWR),0 }, EXILE_BPF_MATCH(PR_SET_NAME),
{ EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_APPEND),0 }, EXILE_BPF_CMP_EQ(PR_CAPBSET_READ, EXILE_SYSCALL_EXIT_BPF_RETURN, EXILE_SYSCALL_EXIT_BPF_NO_MATCH),
}; };
struct exile_syscall_filter socket_filter[] = {
EXILE_SYSCALL_FILTER_LOAD_ARG(0),
{ EXILE_SYSCALL_VOW_UNIX, EXILE_BPF_MATCH(AF_UNIX), 1 },
{ EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET), 1 },
{ EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET6), 1 },
{ 0, EXILE_BPF_RETURN_NOT_MATCHING, 0}
};
struct exile_syscall_filter setsockopt_filter[] = {
EXILE_SYSCALL_FILTER_LOAD_ARG(2),
{ 0, EXILE_BPF_NO_MATCH(SO_DEBUG), 0 },
{ 0, EXILE_BPF_NO_MATCH(SO_SNDBUFFORCE), 0 }
};
struct exile_syscall_filter clone_filter[] = {
/* It's the first (0) argument for x86_64 */
EXILE_SYSCALL_FILTER_LOAD_ARG(0),
{ EXILE_SYSCALL_VOW_CLONE, EXILE_BPF_RETURN_MATCHING, 1 },
{ EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_VM, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1},
{ EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_THREAD, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1},
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWCGROUP), 0},
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWIPC),0},
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNET),0},
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNS),0},
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWPID),0},
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUSER),0},
{ 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUTS),0},
};
struct exile_syscall_filter prctl_filter[] ={
EXILE_SYSCALL_FILTER_LOAD_ARG(0),
{ EXILE_SYSCALL_VOW_PRCTL, EXILE_BPF_RETURN_MATCHING, 1},
{ EXILE_SYSCALL_VOW_SECCOMP_INSTALL, EXILE_BPF_MATCH(PR_SET_SECCOMP), 1 },
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NO_NEW_PRIVS),1},
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NO_NEW_PRIVS),1},
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NAME),1},
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NAME),1},
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_CAPBSET_READ), 1},
{ 0, EXILE_BPF_RETURN_NOT_MATCHING, 0}
};
struct exile_syscall_filter *current_filter = NULL;
size_t current_count = 0;
*policy = EXILE_SYSCALL_ALLOW; *policy = EXILE_SYSCALL_ALLOW;
int result = 0;
int current_filter_index = 1;
switch(syscall) switch(syscall)
{ {
case EXILE_SYS(mmap): case EXILE_SYS(mmap):
case EXILE_SYS(mprotect): case EXILE_SYS(mprotect):
current_filter = mmap_filter; if(vow_promises & EXILE_SYSCALL_VOW_PROT_EXEC)
current_count = COUNT_EXILE_SYSCALL_FILTER(mmap_filter); {
/* If prot exec is allowed, there is no need to filter anything here */
result = 0;
break;
}
if(vow_promises & EXILE_SYSCALL_VOW_STDIO)
{
result = sizeof(mmap_no_exec)/sizeof(mmap_no_exec[0]);
memcpy(filter, mmap_no_exec, sizeof(mmap_no_exec));
}
break; break;
case EXILE_SYS(ioctl): case EXILE_SYS(ioctl):
current_filter = ioctl_filter; if(vow_promises & EXILE_SYSCALL_VOW_IOCTL)
current_count = COUNT_EXILE_SYSCALL_FILTER(ioctl_filter); {
result = 0;
break;
}
if(vow_promises & EXILE_SYSCALL_VOW_STDIO)
{
result = sizeof(ioctl_default)/sizeof(ioctl_default[0]);
memcpy(filter, ioctl_default, sizeof(ioctl_default));
}
break; break;
case EXILE_SYS(open): case EXILE_SYS(open):
case EXILE_SYS(openat): case EXILE_SYS(openat):
case EXILE_SYS(open_by_handle_at): case EXILE_SYS(open_by_handle_at):
/* TODO: This is still a mess with all those combinations.
We should think of something better */
if(syscall == EXILE_SYS(openat) || syscall == EXILE_SYS(open_by_handle_at)) if(syscall == EXILE_SYS(openat) || syscall == EXILE_SYS(open_by_handle_at))
{ {
/* for openat, it's the third arg */ /* for openat, it's the third arg */
open_filter[0] = (struct exile_syscall_filter) EXILE_SYSCALL_FILTER_LOAD_ARG(2); open_rdonly[0].k = offsetof(struct seccomp_data, args[2]);
} }
current_filter = open_filter; /* The combination of those three implies no filtering of open args */
current_count = COUNT_EXILE_SYSCALL_FILTER(open_filter); if((vow_promises & (EXILE_SYSCALL_VOW_CPATH|EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH)) == (EXILE_SYSCALL_VOW_CPATH|EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH))
{
result = 0;
break;
}
/* If cpath is legal, don't filter O_CREAT */
if(vow_promises & EXILE_SYSCALL_VOW_CPATH)
{
open_rdonly[1] = (struct sock_filter) EXILE_BPF_NOP;
}
if(vow_promises & EXILE_SYSCALL_VOW_WPATH)
{
open_rdonly[2] = (struct sock_filter) EXILE_BPF_NOP;
open_rdonly[3] = (struct sock_filter) EXILE_BPF_NOP;
open_rdonly[4] = (struct sock_filter) EXILE_BPF_NOP;
open_rdonly[5] = (struct sock_filter) EXILE_BPF_NOP;
}
result = sizeof(open_rdonly)/sizeof(open_rdonly[0]);
memcpy(filter, open_rdonly, sizeof(open_rdonly));
break; break;
case EXILE_SYS(openat2): case EXILE_SYS(openat2):
result = 0;
*policy = EXILE_SYSCALL_DENY_RET_ERROR; *policy = EXILE_SYSCALL_DENY_RET_ERROR;
return 0;
break; break;
case EXILE_SYS(socket): case EXILE_SYS(socket):
current_filter = socket_filter; if(vow_promises & EXILE_SYSCALL_VOW_UNIX)
current_count = COUNT_EXILE_SYSCALL_FILTER(socket_filter); {
socket_filter[current_filter_index] = (struct sock_filter) EXILE_BPF_MATCH(AF_UNIX);
++current_filter_index;
}
if(vow_promises & EXILE_SYSCALL_VOW_INET)
{
socket_filter[current_filter_index] = (struct sock_filter) EXILE_BPF_MATCH(AF_INET);
++current_filter_index;
socket_filter[current_filter_index] = (struct sock_filter) EXILE_BPF_MATCH(AF_INET6);
++current_filter_index;
}
socket_filter[current_filter_index-1].jf = EXILE_SYSCALL_EXIT_BPF_NO_MATCH;
result = current_filter_index;
memcpy(filter, socket_filter, result * sizeof(struct sock_filter));
break; break;
case EXILE_SYS(setsockopt): case EXILE_SYS(setsockopt):
current_filter = setsockopt_filter; result = sizeof(setsockopt_filter)/sizeof(setsockopt_filter[0]);
current_count = COUNT_EXILE_SYSCALL_FILTER(setsockopt_filter); memcpy(filter, setsockopt_filter, sizeof(setsockopt_filter));
break; break;
case EXILE_SYS(clone): case EXILE_SYS(clone):
current_filter = clone_filter; if(vow_promises & EXILE_SYSCALL_VOW_CLONE)
current_count = COUNT_EXILE_SYSCALL_FILTER(clone_filter); {
result = 0;
break;
}
result = sizeof(clone_filter)/sizeof(clone_filter[0]);
memcpy(filter, clone_filter, sizeof(clone_filter));
break; break;
case EXILE_SYS(clone3): case EXILE_SYS(clone3):
if((vow_promises & EXILE_SYSCALL_VOW_CLONE) == 0) if((vow_promises & EXILE_SYSCALL_VOW_CLONE) == 0)
{ {
result = 0;
*policy = EXILE_SYSCALL_DENY_RET_ERROR; *policy = EXILE_SYSCALL_DENY_RET_ERROR;
return 0;
} }
break; break;
case EXILE_SYS(prctl): case EXILE_SYS(prctl):
current_filter = prctl_filter; if(vow_promises & EXILE_SYSCALL_VOW_PRCTL)
current_count = COUNT_EXILE_SYSCALL_FILTER(prctl_filter); {
result = 0;
break; break;
} }
if(vow_promises & EXILE_SYSCALL_VOW_SECCOMP_INSTALL)
int out_filter_index = 0;
for(size_t i = 0; i < current_count; i++)
{ {
struct exile_syscall_filter *c = &current_filter[i]; prctl_default[3] = (struct sock_filter) EXILE_BPF_MATCH(PR_SET_SECCOMP);
int set = 0;
if(c->vowmask & vow_promises)
{
set = 1;
} }
if(c->whenset == set || c->vowmask == 0) result = sizeof(prctl_default)/sizeof(prctl_default[0]);
{ memcpy(filter, prctl_default, sizeof(prctl_default));
filter[out_filter_index++] = c->filter; break;
} }
} return result;
return out_filter_index;
} }
int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises) int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises)
@ -878,6 +890,7 @@ int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises
EXILE_LOG_ERROR("Failed adding syscall policy from vow while processing %li\n", syscall); EXILE_LOG_ERROR("Failed adding syscall policy from vow while processing %li\n", syscall);
return ret; return ret;
} }
} }
} }
int vow_policy = (vow_promises & EXILE_SYSCALL_VOW_DENY_ERROR) ? EXILE_SYSCALL_DENY_RET_ERROR : EXILE_SYSCALL_DENY_KILL_PROCESS; int vow_policy = (vow_promises & EXILE_SYSCALL_VOW_DENY_ERROR) ? EXILE_SYSCALL_DENY_RET_ERROR : EXILE_SYSCALL_DENY_KILL_PROCESS;
@ -1323,38 +1336,26 @@ static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, st
filter[(*start_index)++] = syscall_check; filter[(*start_index)++] = syscall_check;
--next_syscall_pc; --next_syscall_pc;
struct sock_filter return_matching = EXILE_BPF_RETURN_MATCHING;
struct sock_filter return_not_matching = EXILE_BPF_RETURN_NOT_MATCHING;
for(size_t i = 0; i < syscallpolicy->argfilterscount; i++) for(size_t i = 0; i < syscallpolicy->argfilterscount; i++)
{ {
filter[*start_index] = syscallpolicy->argfilters[i]; filter[*start_index] = syscallpolicy->argfilters[i];
struct sock_filter *current = &filter[*start_index];
__u8 jump_count_next_syscall = next_syscall_pc; __u8 jump_count_next_syscall = next_syscall_pc;
__u8 jump_count_return = jump_count_next_syscall - 1; __u8 jump_count_return = jump_count_next_syscall - 1;
if(current->jt == EXILE_SYSCALL_EXIT_BPF_NO_MATCH) if(filter[*start_index].jt == EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
{ {
current->jt = jump_count_next_syscall; filter[*start_index].jt = jump_count_next_syscall;
} }
if(current->jt == EXILE_SYSCALL_EXIT_BPF_RETURN) if(filter[*start_index].jt == EXILE_SYSCALL_EXIT_BPF_RETURN)
{ {
current->jt = jump_count_return; filter[*start_index].jt = jump_count_return;
} }
if(current->jf == EXILE_SYSCALL_EXIT_BPF_NO_MATCH) if(filter[*start_index].jf == EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
{ {
current->jf = jump_count_next_syscall; filter[*start_index].jf = jump_count_next_syscall;
} }
if(current->jf == EXILE_SYSCALL_EXIT_BPF_RETURN) if(filter[*start_index].jf == EXILE_SYSCALL_EXIT_BPF_RETURN)
{ {
current->jf = jump_count_return; filter[*start_index].jf = jump_count_return;
}
if(current->code == return_matching.code && current->k == return_matching.k)
{
current->k = jump_count_return;
}
if(current->code == return_not_matching.code && current->k == return_not_matching.k)
{
current->k = jump_count_next_syscall;
} }
--next_syscall_pc; --next_syscall_pc;
++*start_index; ++*start_index;
@ -1546,18 +1547,18 @@ static int check_policy_sanity(struct exile_policy *policy)
} }
int can_use_landlock = exile_landlock_is_available(); int can_use_landlock = exile_landlock_is_available();
if(!can_use_landlock)
{
struct exile_path_policy *path_policy = policy->path_policies; struct exile_path_policy *path_policy = policy->path_policies;
while(path_policy) while(path_policy)
{ {
if(path_policy_needs_landlock(path_policy)) if(path_policy_needs_landlock(path_policy))
{
if(!can_use_landlock)
{ {
EXILE_LOG_ERROR("Error: A path policy needs landlock, but landlock is not available. Fallback not possible\n"); EXILE_LOG_ERROR("Error: A path policy needs landlock, but landlock is not available. Fallback not possible\n");
return -1; return -1;
} }
path_policy = path_policy->next;
} }
path_policy = path_policy->next;
} }
/* TODO: check if we have ALLOWED, but no default deny */ /* TODO: check if we have ALLOWED, but no default deny */