diff options
author | Stefano Brivio <sbrivio@redhat.com> | 2023-06-14 07:25:52 +0200 |
---|---|---|
committer | Stefano Brivio <sbrivio@redhat.com> | 2023-06-14 07:25:52 +0200 |
commit | 00614c5e6702db8ac3f18a9e193c7a8382f16e6b (patch) | |
tree | b8275d1060e994e18d7fbfead0244629752cb555 | |
parent | 8bc937c1442d212926dadb6227b759966bc13925 (diff) | |
download | seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.gz seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.bz2 seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.lz seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.xz seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.tar.zst seitan-00614c5e6702db8ac3f18a9e193c7a8382f16e6b.zip |
cooker: Support for read(), OP_STORE, field-based filters
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
-rw-r--r-- | common/gluten.h | 8 | ||||
-rw-r--r-- | cooker/Makefile | 4 | ||||
-rw-r--r-- | cooker/call.c | 11 | ||||
-rw-r--r-- | cooker/calls.c | 5 | ||||
-rw-r--r-- | cooker/emit.c | 24 | ||||
-rw-r--r-- | cooker/emit.h | 2 | ||||
-rw-r--r-- | cooker/example.hjson | 42 | ||||
-rw-r--r-- | cooker/filter.c | 432 | ||||
-rw-r--r-- | cooker/match.c | 5 | ||||
-rw-r--r-- | cooker/parse.c | 41 | ||||
-rw-r--r-- | demo/socket.hjson | 2 | ||||
-rw-r--r-- | operations.c | 40 | ||||
-rw-r--r-- | seitan.c | 1 |
13 files changed, 386 insertions, 231 deletions
diff --git a/common/gluten.h b/common/gluten.h index 91bc85e..57b94f4 100644 --- a/common/gluten.h +++ b/common/gluten.h @@ -68,6 +68,7 @@ enum op_type { OP_FD, OP_RETURN, OP_LOAD, + OP_STORE, OP_BITWISE, OP_CMP, OP_RESOLVEDFD, @@ -185,6 +186,12 @@ struct op_load { size_t size; }; +struct op_store { + struct gluten_offset src; + struct gluten_offset dst; + struct gluten_offset count; +}; + enum op_cmp_type { CMP_EQ, CMP_NE, @@ -250,6 +257,7 @@ struct op { struct op_return ret; struct op_fd fd; struct op_load load; + struct op_store store; struct op_bitwise bitwise; struct op_cmp cmp; struct op_resolvefd resfd; diff --git a/cooker/Makefile b/cooker/Makefile index 64342dc..31b52c9 100644 --- a/cooker/Makefile +++ b/cooker/Makefile @@ -32,11 +32,11 @@ CFLAGS += -DSEITAN_AUDIT_ARCH=AUDIT_ARCH_$(AUDIT_ARCH) SRCS := call.c calls.c emit.c gluten.c filter.c main.c match.c \ parse.c parson.c \ $(COMMON)/util.c \ - calls/net.c calls/ioctl.c calls/process.c calls/fs.c + calls/net.c calls/ioctl.c calls/process.c calls/fs.c calls/io.c HEADERS := call.h calls.h cooker.h emit.h filter.h gluten.h match.h \ parse.h parson.h \ $(COMMON)/gluten.h $(COMMON)/util.h \ - calls/net.h calls/ioctl.h calls/process.h calls/fs.h + calls/net.h calls/ioctl.h calls/process.h calls/fs.h calls/io.h $(BIN): $(SRCS) $(HEADERS) $(CC) $(CFLAGS) -o $(BIN) $(SRCS) diff --git a/cooker/call.c b/cooker/call.c index 4e9cb9c..c3f290c 100644 --- a/cooker/call.c +++ b/cooker/call.c @@ -100,6 +100,9 @@ static union value parse_field(struct gluten_ctx *g, struct arg *args, if (!dry_run) gluten_add_tag_post(g, tag_set, offset); + + if (f->flags & RBUF) + return v; } if ((tag_get = json_object_get_string(tmp2, "get"))) { @@ -199,13 +202,13 @@ static union value parse_field(struct gluten_ctx *g, struct arg *args, } break; case STRING: + if (dry_run) + break; + v.v_str = json_value_get_string(jvalue); if (strlen(v.v_str) + 1 > f->size) die(" string %s too long for field", v.v_str); - if (dry_run) - break; - emit_data_at(g, offset, f->type, &v); break; case STRUCT: @@ -327,7 +330,7 @@ static struct gluten_offset parse_arg(struct gluten_ctx *g, struct arg *args, } if (arg_needs_temp(&a->f, a->pos, jvalue, &top_level_tag, 0) || - multi_field) { + multi_field || (a->f.flags & RBUF)) { if (a->f.size) offset = gluten_rw_alloc(g, a->f.size); else diff --git a/cooker/calls.c b/cooker/calls.c index 337ea21..f486777 100644 --- a/cooker/calls.c +++ b/cooker/calls.c @@ -15,7 +15,10 @@ #include "calls/ioctl.h" #include "calls/process.h" #include "calls/fs.h" +#include "calls/io.h" struct call *call_sets[] = { - syscalls_net, syscalls_ioctl, syscalls_process, syscalls_fs, NULL, + syscalls_net, syscalls_ioctl, syscalls_process, syscalls_fs, + syscalls_io, + NULL, }; diff --git a/cooker/emit.c b/cooker/emit.c index 57533a1..a257398 100644 --- a/cooker/emit.c +++ b/cooker/emit.c @@ -164,6 +164,24 @@ void emit_load(struct gluten_ctx *g, struct gluten_offset dst, die("Too many instructions"); } +void emit_store(struct gluten_ctx *g, struct gluten_offset dst, + struct gluten_offset src, struct gluten_offset count) +{ + struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); + struct op_store *store = &op->op.store; + + op->type = OP_STORE; + + store->dst = dst; + store->src = src; + store->count = count; + + debug(" %i: OP_STORE: #%i", g->ip.offset); + + if (++g->ip.offset > INST_MAX) + die("Too many instructions"); +} + /** * emit_resolved() - Emit OP_RESOLVEFD instruction: resolve file descriptor with path * @g: gluten context @@ -540,7 +558,7 @@ static void gluten_link(struct gluten_ctx *g, enum jump_type type, void emit_bpf_arg(int index, enum type type, union value v, union value mask, enum op_cmp_type cmp) { - struct bpf_arg bpf; + struct bpf_field bpf; /* gluten uses the comparison to skip to the next match, the BPF filter * uses it to notify instead. @@ -550,6 +568,8 @@ void emit_bpf_arg(int index, enum type type, union value v, union value mask, else bpf.cmp = (cmp == CMP_EQ) ? NE : EQ; + bpf.arg = index; + if (TYPE_IS_64BIT(type)) { bpf.value.v64 = v.v_num; bpf.op2.v64 = mask.v_num; @@ -560,7 +580,7 @@ void emit_bpf_arg(int index, enum type type, union value v, union value mask, bpf.type = BPF_U32; } - filter_add_arg(index, bpf); + filter_add_check(&bpf); } void link_block(struct gluten_ctx *g) diff --git a/cooker/emit.h b/cooker/emit.h index 1cdadb9..b4f1ae7 100644 --- a/cooker/emit.h +++ b/cooker/emit.h @@ -13,6 +13,8 @@ void emit_call(struct gluten_ctx *g, struct context_desc *cdesc, long nr, struct gluten_offset offset[6], struct gluten_offset ret_offset); void emit_load(struct gluten_ctx *g, struct gluten_offset dst, int index, size_t len); +void emit_store(struct gluten_ctx *g, struct gluten_offset dst, + struct gluten_offset src, struct gluten_offset count); struct gluten_offset emit_seccomp_data(int index); struct gluten_offset emit_bitwise(struct gluten_ctx *g, enum type type, enum bitwise_type op_type, diff --git a/cooker/example.hjson b/cooker/example.hjson index 458961c..c3dc657 100644 --- a/cooker/example.hjson +++ b/cooker/example.hjson @@ -20,26 +20,46 @@ }, { "match": [ /* CVE-2022-0185-style */ - { "unshare": { "flags": { "has": { "newuser": true, "newnet": false } } } } + { "unshare": { "flags": "CLONE_NEWUSER" } } ], - "block": { } + "return": { "value": 0, "error": -1 } }, { "match": [ /* passt */ - { "unshare": { "flags": { "has": [ "ipc", "mount", "uts", "pid" ] } } } + { "unshare": { "flags": { "all": [ "CLONE_NEWIPC", "CLONE_NEWNS", "CLONE_NEWUTS", "CLONE_NEWPID" ] } } } ], - "block": { } + "return": { "value": 0, "error": 0 } }, { "match": [ /* Giuseppe's example */ - { "mknodat": { "path": { "tag": "path" }, "mode": "c", "major": 1, "minor": { "value": { "in": [ 3, 5, 7, 8, 9 ] }, "tag": "minor" } } } + { "mknodat": + { "path": { "tag": "path" }, + "mode": { "tag": "mode" }, + "type": { "tag": "type" }, + "major": 1, + "minor": { "value": { "in": [ 3, 5, 7, 8, 9 ] }, "tag": "minor" } + } + }, + { "mknod": + { "path": { "tag": "path" }, + "mode": { "tag": "mode" }, + "type": { "tag": "type" }, + "major": 1, + "minor": { "value": { "in": [ 3, 5, 7, 8, 9 ] }, "tag": "minor" } + } + } ], - "call": { - "mknod": { "path": { "tag": { "get": "path" } }, "mode": "c", "major": 1, "minor": { "tag": { "get": "minor" } } }, - "ret": "x", - "context": { "user": "init", "mnt": "caller" } - }, - "return": { "tag": "x" } + "call": + { "mknod": + { "path": { "tag": { "get": "path" } }, + "mode": { "tag": { "get": "mode" } }, + "type": { "tag": { "get": "type" } }, + "major": 1, + "minor": { "tag": { "get": "minor" } } + }, + "context": { "mnt": "caller" } + }, + "return": { "value": 0 } } ] diff --git a/cooker/filter.c b/cooker/filter.c index 7f1f419..bffdfe4 100644 --- a/cooker/filter.c +++ b/cooker/filter.c @@ -17,98 +17,195 @@ #include "filter.h" -struct bpf_entry entries[MAX_ENTRIES]; -static unsigned int index_entries = 0; +#define N_SYSCALL 512 +#define MAX_ENTRIES_PER_SYSCALL 16 +#define MAX_FIELDS_PER_SYSCALL 16 const char *bpf_cmp_str[] = { "no check", "==", "!=", "<=", "<", ">=", ">", "==", "!=" }; +struct bpf_entry { + struct bpf_field field[MAX_FIELDS_PER_SYSCALL]; +}; + /** * struct filter_call_input - First input stage for cooker notification requests - * @notify: Notify on this system call - * @count: How many entry for the same syscall - * @entries: Index for the arguments for every entry + * @notify: Notify on this syscall + * @ignore_args: Don't filter on arguments for this syscall + * @entry: syscall notification entry with field checks */ struct filter_call_input { - bool ignore_args; bool notify; - unsigned int count; - int entries[MAX_ENTRIES_SYSCALL]; + bool ignore_args; + struct bpf_entry entry[MAX_ENTRIES_PER_SYSCALL]; } filter_input[N_SYSCALL] = { 0 }; static long current_nr; -static unsigned int get_number_entries(long nr) +/** + * entry_has_check() - Input stage: does the syscall entry need argument checks? + * @entry: syscall entry with field checks + * + * Return: true if at least one argument comparison is requested + */ +static bool entry_has_check(const struct bpf_entry *entry) { - struct filter_call_input *call = filter_input + nr; - return call->count; -} + unsigned i; -static bool need_check_arg(const struct bpf_entry *entry) -{ - for (int i = 0; i < 6; i++) - if (entry->args[i].cmp != NO_CHECK) + for (i = 0; i < MAX_FIELDS_PER_SYSCALL; i++) { + if (entry->field[i].cmp != NO_CHECK) return true; + } + return false; } -static bool has_args(long nr) +/** + * call_entry_count() - Input stage: count of entries for the same syscall + * @nr: syscall number + * + * Return: count of entries for the same syscall + */ +static unsigned int call_entry_count(long nr) { struct filter_call_input *call = filter_input + nr; + unsigned i, count = 0; - if (call->count < 1) - return false; - if (call->ignore_args) - return false; + if (!call->notify || call->ignore_args) + return 0; - /* Check if the first entry has some arguments */ - return need_check_arg(&entries[call->entries[0]]); + for (i = 0; i < MAX_ENTRIES_PER_SYSCALL; i++) { + if (entry_has_check(&call->entry[i])) + count++; + else + break; + } + + return count; } -static unsigned get_args_for_entry(const struct bpf_entry *entry) +/** + * entry_check_count() - Input stage: count of field checks for entry + * @entry: syscall entry with field checks + * + * Return: count of argument checks + */ +static unsigned entry_check_count(const struct bpf_entry *entry) { unsigned i, n = 0; - for (i = 0; i < 6; i++) - if (entry->args[i].cmp != NO_CHECK) + for (i = 0; i < MAX_FIELDS_PER_SYSCALL; i++) + if (entry->field[i].cmp != NO_CHECK) n++; + return n; } +/** + * filter_notify() - Start of notification request + * @nr: syscall number + */ +void filter_notify(long nr) +{ + struct filter_call_input *call = filter_input + nr; + + debug(" BPF: start filter information for #%lu", nr); + current_nr = nr; + call->notify = true; +} + +/** + * filter_add_check() - Add a new field check to the current syscall + * @field: Field check specification + */ +void filter_add_check(struct bpf_field *field) +{ + struct filter_call_input *call = filter_input + current_nr; + struct bpf_entry *entry; + struct bpf_field *check; + char buf[BUFSIZ]; + unsigned n; + + n = snprintf(buf, BUFSIZ, " BPF: adding #%i %s %lu", + field->arg, bpf_cmp_str[field->cmp], + (field->type == BPF_U32) ? field->value.v32 : + field->value.v64); + + if (field->cmp == AND_EQ || field->cmp == AND_NE) { + snprintf(buf + n, BUFSIZ - n, " & %lu", + (field->type == BPF_U32) ? field->op2.v32 : + field->op2.v64); + } + + debug("%s", buf); + + /* Too many entries or checks: ignore argument checks from now on */ + if (call_entry_count(current_nr) > MAX_ENTRIES_PER_SYSCALL) + call->ignore_args = true; + + entry = &call->entry[call_entry_count(current_nr)]; + if (entry_check_count(entry) > MAX_FIELDS_PER_SYSCALL) + call->ignore_args = true; + + if (call->ignore_args) { + debug(" BPF: ignoring fields for syscall #%lu", current_nr); + return; + } + + check = &entry->field[entry_check_count(entry)]; + + debug(" BPF: inserting check at %i for entry %i, syscall %lu", + entry_check_count(entry), call_entry_count(current_nr), + current_nr); + + memcpy(check, field, sizeof(*field)); +} + +void filter_needs_deref(void) +{ + struct filter_call_input *call = filter_input + current_nr; + + debug(" BPF: arguments for #%lu now ignored", current_nr); + + call->ignore_args = true; +} + /* Calculate how many instruction for the syscall */ static unsigned int get_n_args_syscall_instr(long nr) { struct filter_call_input *call = filter_input + nr; - const struct bpf_entry *entry; unsigned int n = 0, total_instr = 0; - unsigned int i, k; + unsigned int i, j; - for (i = 0; i < call->count; i++) { - entry = &entries[call->entries[i]]; + for (i = 0; i < call_entry_count(nr); i++) { + const struct bpf_entry *entry = &call->entry[i]; n = 0; - for (k = 0; k < 6; k++) { - if (entry->args[k].cmp == NO_CHECK) + + for (j = 0; j < entry_check_count(entry); j++) { + const struct bpf_field *field = &entry->field[j]; + enum bpf_cmp cmp = field->cmp; + + if (cmp == NO_CHECK) continue; - switch (entry->args[k].type) { + + switch (field->type) { case BPF_U32: - /* For 32 bit arguments + /* For 32 bit fields * comparison instructions (2): * 1 loading the value + 1 for evaluation * arithemtic instructions (3): * 1 loading the value + 1 for the operation + 1 for evaluation */ - if (entry->args[k].cmp == AND_EQ || - entry->args[k].cmp == AND_NE) + if (cmp == AND_EQ || cmp == AND_NE) n += 3; else n += 2; break; case BPF_U64: - /* For 64 bit arguments: 32 instructions * 2 + /* For 64 bit fields: 32 instructions * 2 * for loading and evaluating the high and low 32 bits chuncks. */ - if (entry->args[k].cmp == AND_EQ || - entry->args[k].cmp == AND_NE) + if (cmp == AND_EQ || cmp == AND_NE) n += 6; else n += 4; @@ -116,87 +213,19 @@ static unsigned int get_n_args_syscall_instr(long nr) } } total_instr += n; - /* If there at least an argument, then there is the jump to the + /* TODO: rewrite comment: If there at least an argument, then there is the jump to the * notification */ if (n > 0) total_instr++; } - /* If there at least an argument for that syscall, then there is the jump to the + /* TODO: rewrite comment: If there at least an argument for that syscall, then there is the jump to the * accept */ - if (has_args(nr)) + if (call_entry_count(nr)) total_instr++; - return total_instr; -} - -/** - * filter_notify() - Start of notification request, check/flush previous one - * @nr: System call number, -1 to just flush previous request - */ -void filter_notify(long nr) -{ - struct filter_call_input *call = filter_input + nr; - - if (nr > 0) { - debug(" BPF: start filter information for #%lu", nr); - current_nr = nr; - call->notify = true; - } -} - -/** - * filter_add_arg(): Add a new argument to the current syscall - * @index: position of the argument - * @arg: the argument to add - */ -void filter_add_arg(int index, struct bpf_arg arg) -{ - struct filter_call_input *call = filter_input + current_nr; - struct bpf_entry *entry = &entries[index_entries]; - char buf[BUFSIZ]; - unsigned n; - - n = snprintf(buf, BUFSIZ, " BPF: adding #%i %s %lu", - index, bpf_cmp_str[arg.cmp], - (arg.type == BPF_U32) ? arg.value.v32 : arg.value.v64); - if (arg.cmp == AND_EQ || arg.cmp == AND_NE) { - snprintf(buf + n, BUFSIZ - n, " & %lu", - (arg.type == BPF_U32) ? arg.op2.v32 : arg.op2.v64); - } - debug("%s", buf); - - /* If it reaches the maximum number of entries per syscall, then we simply - * notify for all the arguments and ignore the other arguments. - */ - if (call->count >= MAX_ENTRIES_SYSCALL) { - call->ignore_args = true; - return; - } - if (call->ignore_args) - return; - call->entries[call->count] = index_entries; - memcpy(&entry->args[index], &arg, sizeof(arg)); -} - -void filter_flush_args() -{ - struct filter_call_input *call = filter_input + current_nr; - debug(" BPF: flush filter information for #%lu", current_nr); - - if (!has_args(current_nr)) - return; - call->count++; - index_entries++; -} - -void filter_needs_deref(void) -{ - struct filter_call_input *call = filter_input + current_nr; - - debug(" BPF: arguments for #%lu now ignored", current_nr); + debug(" BPF: counted %i instructions for syscall %lu", total_instr, nr); - call->ignore_args = true; - call->count = 0; + return total_instr; } static int table[N_SYSCALL]; @@ -284,161 +313,161 @@ static void create_lookup_nodes(int jumps[], unsigned int n) } } -static unsigned int eq(struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, +static unsigned int eq(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; uint32_t hi, lo; - switch (entry->args[idx].type) { + switch (field->type) { case BPF_U64: - hi = get_hi((entry->args[idx]).value.v64); - lo = get_lo((entry->args[idx]).value.v64); - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + hi = get_hi(field->value.v64); + lo = get_lo(field->value.v64); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)EQ(lo, 0, jfalse); - filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg)); filter[size++] = (struct sock_filter)EQ(hi, jtrue, jfalse); break; case BPF_U32: - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)EQ( - entry->args[idx].value.v32, jtrue, jfalse); + field->value.v32, jtrue, jfalse); break; } return size; } -static unsigned int gt(struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, +static unsigned int gt(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; uint32_t hi, lo; - switch (entry->args[idx].type) { + switch (field->type) { case BPF_U64: - hi = get_hi((entry->args[idx]).value.v64); - lo = get_lo((entry->args[idx]).value.v64); - filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); + hi = get_hi(field->value.v64); + lo = get_lo(field->value.v64); + filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg)); filter[size++] = (struct sock_filter)GT(hi, jtrue + 2, 0); - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)GT(lo, jtrue, jfalse); break; case BPF_U32: - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)GT( - entry->args[idx].value.v32, jtrue, jfalse); + field->value.v32, jtrue, jfalse); break; } return size; } -static unsigned int lt(struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, +static unsigned int lt(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; uint32_t hi, lo; - switch (entry->args[idx].type) { + switch (field->type) { case BPF_U64: - hi = get_hi((entry->args[idx]).value.v64); - lo = get_lo((entry->args[idx]).value.v64); - filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); + hi = get_hi(field->value.v64); + lo = get_lo(field->value.v64); + filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg)); filter[size++] = (struct sock_filter)LT(hi, jtrue + 2, jfalse); - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)LT(lo, jtrue, jfalse); break; case BPF_U32: - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)LT( - entry->args[idx].value.v32, jtrue, jfalse); + field->value.v32, jtrue, jfalse); break; } return size; } -static unsigned int neq(struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, +static unsigned int neq(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, unsigned int jfalse) { - return eq(filter, idx, entry, jfalse, jtrue); + return eq(filter, field, jfalse, jtrue); } -static unsigned int ge(struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, +static unsigned int ge(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, unsigned int jfalse) { - return lt(filter, idx, entry, jfalse, jtrue); + return lt(filter, field, jfalse, jtrue); } -static unsigned int le(struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, +static unsigned int le(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, unsigned int jfalse) { - return gt(filter, idx, entry, jfalse, jtrue); + return gt(filter, field, jfalse, jtrue); } -static unsigned int and_eq (struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, - unsigned int jfalse) +static unsigned int and_eq(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, + unsigned int jfalse) { unsigned int size = 0; - switch (entry->args[idx].type) { + switch (field->type) { case BPF_U64: - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)AND( - get_lo(entry->args[idx].op2.v64)); + get_lo(field->op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_lo((entry->args[idx]).value.v64), 0, jfalse); - filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); + get_lo(field->value.v64), 0, jfalse); + filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg)); filter[size++] = (struct sock_filter)AND( - get_hi(entry->args[idx].op2.v64)); + get_hi(field->op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_hi(entry->args[idx].value.v64), jtrue, jfalse); + get_hi(field->value.v64), jtrue, jfalse); break; case BPF_U32: - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = - (struct sock_filter)AND(entry->args[idx].op2.v32); + (struct sock_filter)AND(field->op2.v32); filter[size++] = (struct sock_filter)EQ( - entry->args[idx].value.v32, jtrue, jfalse); + field->value.v32, jtrue, jfalse); break; } return size; } -static unsigned int and_ne(struct sock_filter filter[], int idx, - const struct bpf_entry *entry, unsigned int jtrue, +static unsigned int and_ne(struct sock_filter filter[], + const struct bpf_field *field, unsigned int jtrue, unsigned int jfalse) { unsigned int size = 0; - switch (entry->args[idx].type) { + switch (field->type) { case BPF_U64: - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = (struct sock_filter)AND( - get_lo(entry->args[idx].op2.v64)); + get_lo(field->op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_lo((entry->args[idx]).value.v64), 0, jtrue + 3); - filter[size++] = (struct sock_filter)LOAD(HI_ARG(idx)); + get_lo(field->value.v64), 0, jtrue + 3); + filter[size++] = (struct sock_filter)LOAD(HI_ARG(field->arg)); filter[size++] = (struct sock_filter)AND( - get_hi(entry->args[idx].op2.v64)); + get_hi(field->op2.v64)); filter[size++] = (struct sock_filter)EQ( - get_hi(entry->args[idx].value.v64), jfalse, jtrue); + get_hi(field->value.v64), jfalse, jtrue); break; case BPF_U32: - filter[size++] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[size++] = (struct sock_filter)LOAD(LO_ARG(field->arg)); filter[size++] = - (struct sock_filter)AND(entry->args[idx].op2.v32); + (struct sock_filter)AND(field->op2.v32); filter[size++] = (struct sock_filter)EQ( - entry->args[idx].value.v32, jfalse, jtrue); + field->value.v32, jfalse, jtrue); break; } @@ -449,52 +478,51 @@ static unsigned int insert_args(struct sock_filter filter[], long nr) { struct filter_call_input *call = filter_input + nr; unsigned int next_offset, n_checks = 0; - unsigned int count = get_number_entries(nr); + unsigned int count = call_entry_count(nr); struct bpf_entry *entry; unsigned int offset = 0; unsigned int size = 0; - unsigned int i, k; + unsigned int i, j; for (i = 0; i < count; i++) { n_checks = 0; - entry = &entries[call->entries[i]]; + entry = &call->entry[i]; /* If there are multiple entries for the syscall @nr, then the next group * of arguments to check (i.e. the next offset) is after the number of * arguments of the current entry. The next_offset is used to * jump to the next group if the check is false. */ - next_offset = get_args_for_entry(entry); - for (k = 0; k < 6; k++) { + next_offset = entry_check_count(entry); + for (j = 0; j < entry_check_count(entry); j++) { + struct bpf_field *field = &entry->field[j]; + offset = next_offset - n_checks; - switch (entry->args[k].cmp) { + switch (field->cmp) { case NO_CHECK: continue; case EQ: - size += eq(&filter[size], k, entry, 0, offset); + size += eq(&filter[size], field, 0, offset); break; case NE: - size += neq(&filter[size], k, entry, 0, offset); + size += neq(&filter[size], field, 0, offset); break; case GT: - size += gt(&filter[size], k, entry, 0, offset); + size += gt(&filter[size], field, 0, offset); break; case LT: - size += lt(&filter[size], k, entry, 0, offset); + size += lt(&filter[size], field, 0, offset); break; case GE: - size += ge(&filter[size], k, entry, 0, offset); + size += ge(&filter[size], field, 0, offset); break; case LE: - size += le(&filter[size], k, entry, 0, offset); + size += le(&filter[size], field, 0, offset); break; case AND_EQ: - size += and_eq - (&filter[size], k, entry, 0, offset); + size += and_eq(&filter[size], field, 0, offset); break; case AND_NE: - size += and_ne(&filter[size], k, entry, 0, - offset); - + size += and_ne(&filter[size], field, 0, offset); break; } n_checks++; @@ -503,7 +531,7 @@ static unsigned int insert_args(struct sock_filter filter[], long nr) * to add the notification */ if (n_checks > 0) filter[size++] = (struct sock_filter)BPF_STMT( - BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF); + BPF_RET | BPF_K, SECCOMP_RET_ALLOW); } return size; @@ -524,6 +552,8 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n) /* No nodes if there is a single syscall */ n_nodes = (1 << count_shift_right(n - 1)) - 1; + debug(" BPF: tree has %i nodes", n_nodes); + /* Pre */ /* cppcheck-suppress badBitmaskCheck */ filter[size++] = (struct sock_filter)BPF_STMT( @@ -559,8 +589,8 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n) /* Insert leaves */ for (i = 0; i < n; i++) { nr = get_syscall(i); - if (get_number_entries(nr) > 0) { - offset = next_offset; + if (call_entry_count(nr) > 0) { + offset = next_offset - 1; } else { /* If the syscall doesn't have any arguments, then notify */ offset = notify - size - 1; @@ -573,10 +603,12 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n) next_offset += get_n_args_syscall_instr(nr) - 1; } /* Seccomp accept and notify instruction */ - filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, + filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW); - filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, - SECCOMP_RET_USER_NOTIF); + if (!call_entry_count(nr)) + filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, + SECCOMP_RET_USER_NOTIF); + /* * Insert args. It sequentially checks all the arguments for a syscall @@ -584,13 +616,15 @@ unsigned int filter_build(struct sock_filter filter[], unsigned n) * check the following entry of the syscall and its arguments. */ for (i = 0; i < n; i++) { - nr = get_syscall(i); size += insert_args(&filter[size], nr); - if (has_args(nr)) + if (call_entry_count(nr)) filter[size++] = (struct sock_filter)BPF_STMT( - BPF_RET | BPF_K, SECCOMP_RET_ALLOW); + BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF); } + debug(" BPF: filter with %i call%s has %i instructions", + n, n != 1 ? "s" : "", size); + return size; } diff --git a/cooker/match.c b/cooker/match.c index 374f277..776823d 100644 --- a/cooker/match.c +++ b/cooker/match.c @@ -41,7 +41,7 @@ static struct gluten_offset arg_load(struct gluten_ctx *g, struct arg *a) size = a->f.size; } - if (!size) { + if (!size || (a->f.flags & RBUF)) { g->match_dst[index].offset.type = OFFSET_SECCOMP_DATA; g->match_dst[index].offset.offset = index; g->match_dst[index].len = 0; @@ -152,7 +152,7 @@ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx } /* Nothing to match on: just store as reference */ - if (!jvalue) + if (!jvalue || (f->flags & RBUF)) return v; offset.offset += f->offset; @@ -381,7 +381,6 @@ void handle_matches(struct gluten_ctx *g, JSON_Value *value) filter_notify(call->number); parse_match(g, args, call->args); - filter_flush_args(); break; } diff --git a/cooker/parse.c b/cooker/parse.c index 7cac43f..8dc89f9 100644 --- a/cooker/parse.c +++ b/cooker/parse.c @@ -72,9 +72,32 @@ static void handle_limit(struct gluten_ctx *g, JSON_Value *value) (void)value; } +static void handle_write(struct gluten_ctx *g, JSON_Value *value) +{ + JSON_Object *obj = json_value_get_object(value); + struct gluten_offset src, dst, count; + const char *tag; + + if (!(tag = json_object_get_string(obj, "src"))) + die("invalid tag specification"); + src = gluten_get_tag(g, tag); + + if (!(tag = json_object_get_string(obj, "dst"))) + die("invalid tag specification"); + dst = gluten_get_tag(g, tag); + + if (!(tag = json_object_get_string(obj, "count"))) + die("invalid tag specification"); + count = gluten_get_tag(g, tag); + + emit_store(g, dst, src, count); +} + static void handle_return(struct gluten_ctx *g, JSON_Value *value) { JSON_Object *obj = json_value_get_object(value); + struct gluten_offset data = NULL_OFFSET; + const char *tag; JSON_Value *jvalue; union value v = { .v_u64 = 0 }; int32_t error = 0; @@ -83,14 +106,16 @@ static void handle_return(struct gluten_ctx *g, JSON_Value *value) debug(" Parsing \"return\""); jvalue = json_object_get_value(obj, "error"); - if (json_value_get_type(jvalue) == JSONNumber) { - error = json_value_get_number(jvalue); - } + if (json_value_get_type(jvalue) == JSONNumber) + data = emit_data(g, U64, sizeof(v.v_u64), &v); + else if ((tag = json_object_get_string(obj, "error"))) + data = gluten_get_tag(g, tag); jvalue = json_object_get_value(obj, "value"); - if (json_value_get_type(jvalue) == JSONNumber) { - v.v_u64 = json_value_get_number(jvalue); - } + if (json_value_get_type(jvalue) == JSONNumber) + data = emit_data(g, U64, sizeof(v.v_u64), &v); + else if ((tag = json_object_get_string(obj, "value"))) + data = gluten_get_tag(g, tag); jvalue = json_object_get_value(obj, "continue"); if (json_value_get_type(jvalue) == JSONBoolean) { @@ -101,7 +126,8 @@ static void handle_return(struct gluten_ctx *g, JSON_Value *value) debug(" emit return: val=%ld errno=%d cont=%s", v.v_u64, error, cont ? "true" : "false"); - emit_return(g, emit_data(g, U64, sizeof(v.v_u64), &v), error, cont); + + emit_return(g, data, error, cont); } static void handle_context(struct gluten_ctx *g, JSON_Value *value) @@ -121,6 +147,7 @@ struct rule_parser { } parsers[] = { { "match", handle_matches }, { "call", handle_calls }, + { "write", handle_write }, { "fd", handle_fd }, { "limit", handle_limit }, { "return", handle_return }, diff --git a/demo/socket.hjson b/demo/socket.hjson index 7a16ccc..d9a3345 100644 --- a/demo/socket.hjson +++ b/demo/socket.hjson @@ -1,7 +1,7 @@ [ { "match": [ - { "socket": { "family": "unix", "type": "stream", "flags": 0, "protocol": 0 } } + { "socket": { "family": "unix", "type": "stream", "flags": "cloexec", "protocol": 0 } } ], "call": [ { "socket": { "family": "unix", "type": "stream", "flags": [ "cloexec", "nonblock" ], "protocol": 0 }, "ret": "new_fd" } diff --git a/operations.c b/operations.c index e1132d5..70c8f08 100644 --- a/operations.c +++ b/operations.c @@ -292,6 +292,45 @@ out: return ret; } +int op_store(const struct seccomp_notif *req, int notifier, struct gluten *g, + struct op_store *store) +{ + const long unsigned int *dst = gluten_ptr(&req->data, g, store->dst); + const size_t *count = gluten_ptr(&req->data, g, store->count); + const void *src = gluten_ptr(&req->data, g, store->src); + char path[PATH_MAX]; + int fd, ret = 0; + + debug(" op_store: argument (%s %d) in (%s %d) size=%d", + gluten_offset_name[store->src.type], store->src.offset, + gluten_offset_name[store->dst.type], store->dst.offset); + + if (dst == NULL) + ret_err(-1, " op_store: empty destination"); + + snprintf(path, sizeof(path), "/proc/%d/mem", req->pid); + if ((fd = open(path, O_WRONLY | O_CLOEXEC)) < 0) + ret_err(-1, "error opening mem for %d", req->pid); + /* + * Avoid the TOCTOU and check if the read mappings are still valid + */ + if (!is_cookie_valid(notifier, req->id)) { + err("the seccomp request isn't valid anymore"); + ret = -1; + goto out; + } + + if (pwrite(fd, src, *count, *dst) < 0) { + err("pwrite"); + ret = -1; + goto out; + } + +out: + close(fd); + return ret; +} + int op_return(const struct seccomp_notif *req, int notifier, struct gluten *g, struct op_return *op) { @@ -503,6 +542,7 @@ int eval(struct gluten *g, const struct seccomp_notif *req, HANDLE_OP(OP_RETURN, op_return, ret, g); HANDLE_OP(OP_FD, op_fd, fd, g); HANDLE_OP(OP_LOAD, op_load, load, g); + HANDLE_OP(OP_STORE, op_store, store, g); HANDLE_OP(OP_BITWISE, op_bitwise, bitwise, g); HANDLE_OP(OP_CMP, op_cmp, cmp, g); HANDLE_OP(OP_RESOLVEDFD, op_resolve_fd, resfd, g); @@ -217,7 +217,6 @@ int main(int argc, char **argv) if ((notifier = recvfd(fd)) < 0) die(" failed recieving the notifier fd"); } - sleep(1); if ((epollfd = epoll_create1(0)) < 0) die(" epoll_create"); |