mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-23 18:19:39 +00:00
This commit adds basic support for dumping and restoring seccomp filters via the new ptrace interface. There are two current known limitations with this approach: 1. This approach doesn't support restoring tasks who first do a seccomp() and then a setuid(); the test elaborates on this and I don't think it is tough to do, but it is not done yet. 2. Filters are compared via memcmp(), so two tasks which have the same parent task and install identical (via memory) filters will have those filters considered to be the "same". Since we force all tasks to have the same creds (including seccomp filters) right now, this isn't a problem. The approach used here is very similar to the cgroup approach: the actual filters are stored in a seccomp.img, and each task has an id that points to the part of the filter tree it needs to restore. This keeps us from dumping the same filter multiple times, since filters are inherited on fork. v2: * remove unused seccomp_filters field from struct rst_info * rework memory layout for passing filters to restorer blob * add a sanity check when finding inherited filters Signed-off-by: Tycho Andersen <tycho.andersen@canonical.com> Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
270 lines
5.6 KiB
C
270 lines
5.6 KiB
C
#include <linux/filter.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "config.h"
|
|
#include "imgset.h"
|
|
#include "kcmp.h"
|
|
#include "pstree.h"
|
|
#include "ptrace.h"
|
|
#include "proc_parse.h"
|
|
#include "seccomp.h"
|
|
#include "servicefd.h"
|
|
#include "util.h"
|
|
#include "rst-malloc.h"
|
|
|
|
#include "protobuf.h"
|
|
#include "protobuf/seccomp.pb-c.h"
|
|
|
|
/* populated on dump during collect_seccomp_filters() */
|
|
static int next_filter_id = 0;
|
|
static struct seccomp_info **filters = NULL;
|
|
|
|
static struct seccomp_info *find_inherited(struct pstree_item *parent,
|
|
struct sock_filter *filter, int len)
|
|
{
|
|
struct seccomp_info *info;
|
|
|
|
/* if we have no filters yet, this one has no parent */
|
|
if (!filters)
|
|
return NULL;
|
|
|
|
for (info = filters[dmpi(parent)->pi_creds->last_filter]; info; info = info->prev) {
|
|
|
|
if (len != info->filter.filter.len)
|
|
continue;
|
|
if (!memcmp(filter, info->filter.filter.data, len))
|
|
return info;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int collect_filter_for_pstree(struct pstree_item *item)
|
|
{
|
|
struct seccomp_info *infos = NULL, *cursor;
|
|
int info_count, i, ret = -1;
|
|
struct sock_filter buf[BPF_MAXINSNS];
|
|
void *m;
|
|
|
|
if (item->state == TASK_DEAD ||
|
|
dmpi(item)->pi_creds->seccomp_mode != SECCOMP_MODE_FILTER)
|
|
return 0;
|
|
|
|
for (i = 0; true; i++) {
|
|
int len;
|
|
struct seccomp_info *info, *inherited = NULL;
|
|
|
|
len = ptrace(PTRACE_SECCOMP_GET_FILTER, item->pid.real, i, buf);
|
|
if (len < 0) {
|
|
if (errno == ENOENT) {
|
|
/* end of the search */
|
|
BUG_ON(i == 0);
|
|
goto save_infos;
|
|
} else if (errno == EINVAL) {
|
|
pr_err("dumping seccomp infos not supported\n");
|
|
goto out;
|
|
} else {
|
|
pr_perror("couldn't dump seccomp filter");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
inherited = find_inherited(item->parent, buf, len);
|
|
if (inherited) {
|
|
bool found = false;
|
|
|
|
/* Small sanity check: if infos is already populated,
|
|
* we should have inherited that filter too. */
|
|
for (cursor = infos; cursor; cursor = cursor->prev) {
|
|
if (inherited->prev== cursor) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
BUG_ON(!found);
|
|
|
|
infos = inherited;
|
|
continue;
|
|
}
|
|
|
|
info = xmalloc(sizeof(*info));
|
|
if (!info)
|
|
goto out;
|
|
seccomp_filter__init(&info->filter);
|
|
|
|
info->filter.filter.len = len * sizeof(struct sock_filter);
|
|
info->filter.filter.data = xmalloc(info->filter.filter.len);
|
|
if (!info->filter.filter.data)
|
|
goto out;
|
|
|
|
memcpy(info->filter.filter.data, buf, info->filter.filter.len);
|
|
|
|
info->prev = infos;
|
|
infos = info;
|
|
}
|
|
|
|
save_infos:
|
|
info_count = i;
|
|
|
|
m = xrealloc(filters, sizeof(*filters) * (next_filter_id + info_count));
|
|
if (!m)
|
|
goto out;
|
|
filters = m;
|
|
|
|
for (cursor = infos, i = info_count + next_filter_id - 1;
|
|
i >= next_filter_id; i--) {
|
|
BUG_ON(!cursor);
|
|
cursor->id = i;
|
|
filters[i] = cursor;
|
|
cursor = cursor->prev;
|
|
}
|
|
|
|
next_filter_id += info_count;
|
|
|
|
dmpi(item)->pi_creds->last_filter = infos->id;
|
|
|
|
/* Don't free the part of the tree we just successfully acquired */
|
|
infos = NULL;
|
|
ret = 0;
|
|
out:
|
|
while (infos) {
|
|
struct seccomp_info *freeme = infos;
|
|
infos = infos->prev;
|
|
xfree(freeme->filter.filter.data);
|
|
xfree(freeme);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dump_seccomp_filters(void)
|
|
{
|
|
SeccompEntry se = SECCOMP_ENTRY__INIT;
|
|
int ret = -1, i;
|
|
|
|
/* If we didn't collect any filters, don't create a seccomp image at all. */
|
|
if (next_filter_id == 0)
|
|
return 0;
|
|
|
|
se.seccomp_filters = xzalloc(sizeof(*se.seccomp_filters) * next_filter_id);
|
|
if (!se.seccomp_filters)
|
|
return -1;
|
|
|
|
se.n_seccomp_filters = next_filter_id;
|
|
|
|
for (i = 0; i < next_filter_id; i++) {
|
|
SeccompFilter *sf;
|
|
struct seccomp_info *cur = filters[i];
|
|
|
|
sf = se.seccomp_filters[cur->id] = &cur->filter;
|
|
if (cur->prev) {
|
|
sf->has_prev = true;
|
|
sf->prev = cur->prev->id;
|
|
}
|
|
}
|
|
|
|
ret = pb_write_one(img_from_set(glob_imgset, CR_FD_SECCOMP), &se, PB_SECCOMP);
|
|
|
|
xfree(se.seccomp_filters);
|
|
|
|
for (i = 0; i < next_filter_id; i++) {
|
|
struct seccomp_info *freeme = filters[i];
|
|
|
|
xfree(freeme->filter.filter.data);
|
|
xfree(freeme);
|
|
}
|
|
xfree(filters);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int collect_seccomp_filters(void)
|
|
{
|
|
if (preorder_pstree_traversal(root_item, collect_filter_for_pstree) < 0)
|
|
return -1;
|
|
|
|
if (dump_seccomp_filters())
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Populated on restore by prepare_seccomp_filters */
|
|
static SeccompEntry *se;
|
|
|
|
int prepare_seccomp_filters(void)
|
|
{
|
|
struct cr_img *img;
|
|
int ret;
|
|
|
|
img = open_image(CR_FD_SECCOMP, O_RSTR);
|
|
if (!img)
|
|
return -1;
|
|
|
|
ret = pb_read_one_eof(img, &se, PB_SECCOMP);
|
|
close_image(img);
|
|
if (ret <= 0)
|
|
return 0; /* there were no filters */
|
|
|
|
BUG_ON(!se);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int seccomp_filters_get_rst_pos(CoreEntry *core, int *count, unsigned long *pos)
|
|
{
|
|
SeccompFilter *sf = NULL;
|
|
struct sock_fprog *arr = NULL;
|
|
void *filter_data = NULL;
|
|
int ret = -1, i;
|
|
size_t filter_size = 0;
|
|
|
|
if (!core->tc->has_seccomp_filter) {
|
|
*count = 0;
|
|
return 0;
|
|
}
|
|
|
|
*count = 0;
|
|
*pos = rst_mem_cpos(RM_PRIVATE);
|
|
|
|
BUG_ON(core->tc->seccomp_filter > se->n_seccomp_filters);
|
|
sf = se->seccomp_filters[core->tc->seccomp_filter];
|
|
|
|
while (1) {
|
|
(*count)++;
|
|
|
|
filter_size += sf->filter.len;
|
|
|
|
if (!sf->has_prev)
|
|
break;
|
|
|
|
sf = se->seccomp_filters[sf->prev];
|
|
}
|
|
|
|
arr = rst_mem_alloc(sizeof(struct sock_fprog) * (*count) + filter_size, RM_PRIVATE);
|
|
if (!arr)
|
|
goto out;
|
|
|
|
filter_data = &arr[*count];
|
|
sf = se->seccomp_filters[core->tc->seccomp_filter];
|
|
for (i = 0; i < *count; i++) {
|
|
struct sock_fprog *fprog = &arr[i];
|
|
|
|
BUG_ON(sf->filter.len % sizeof(struct sock_filter));
|
|
fprog->len = sf->filter.len / sizeof(struct sock_filter);
|
|
|
|
memcpy(filter_data, sf->filter.data, sf->filter.len);
|
|
|
|
filter_data += sf->filter.len;
|
|
sf = se->seccomp_filters[sf->prev];
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
seccomp_entry__free_unpacked(se, NULL);
|
|
return ret;
|
|
}
|