diff options
Diffstat (limited to 'cooker/match.c')
-rw-r--r-- | cooker/match.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/cooker/match.c b/cooker/match.c new file mode 100644 index 0000000..bd96a03 --- /dev/null +++ b/cooker/match.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* seitan - Syscall Expressive Interpreter, Transformer and Notifier + * + * cooker/match.c - Parse "match" rules from JSON recipe into bytecode + * + * Copyright 2023 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#include "parson.h" +#include "calls.h" +#include "cooker.h" +#include "gluten.h" +#include "emit.h" +#include "filter.h" +#include "util.h" + +#include "calls/net.h" + +/** + * struct rule_parser - Parsing handler for JSON rule type + * @type: JSON key name + * @fn: Parsing function + */ +struct rule_parser { + const char *type; + int (*fn)(struct gluten_ctx *g, JSON_Value *value); +}; + +/** + * arg_load() - Allocate and build bytecode for one syscall argument + * @g: gluten context + * @a: Argument description from model + * + * Return: offset where (full) argument is stored + */ +static struct gluten_offset arg_load(struct gluten_ctx *g, struct arg *a) +{ + int index = a->pos; + size_t size; + + if (a->type == SELECTED) { + if (g->selected_arg[index]->type != UNDEF) + size = g->selected_arg[index]->size; + else + die(" no storage size for argument %s", a->name); + } else { + size = a->size; + } + + if (!size) { + g->match_dst[index].offset.type = OFFSET_SECCOMP_DATA; + g->match_dst[index].offset.offset = index; + g->match_dst[index].len = 0; + return g->match_dst[index].offset; + } + + filter_needs_deref(); + + if (g->match_dst[index].len) /* Already allocated */ + return g->match_dst[index].offset; + + g->match_dst[index].offset = gluten_alloc(g, size); + g->match_dst[index].len = size; + + emit_load(g, g->match_dst[index].offset, index, size); + + return g->match_dst[index].offset; +} + +/** + * value_get_num() - Get numeric value from description matching JSON input + * @desc: Description of possible values from model + * @value: JSON value + * + * Return: numeric value + */ +static long long value_get_num(struct num *desc, JSON_Value *value) +{ + const char *s = NULL; + long long n; + + if (desc) { + s = json_value_get_string(value); + for (; desc->name && s && strcmp(s, desc->name); desc++); + if (s && !desc->name) + die(" Invalid value %s", s); + + n = desc->value; + } + + if (!s) { + if (json_value_get_type(value) != JSONNumber) + die(" Invalid value type"); + + n = json_value_get_number(value); + } + + return n; +} + +/** + * value_get() - Get generic value from description matching JSON input + * @desc: Description of possible values from model + * @type: Data type from model + * @value: JSON value + * @out: Corresponding bytecode value, set on return + */ +static void value_get(union desc desc, enum type type, JSON_Value *value, + union value *out) +{ + if (TYPE_IS_NUM(type)) + out->v_num = value_get_num(desc.d_num, value); +} + +/** + * select_desc() - Get description and type for selected value + * @g: gluten context + * @s: Possible selection choices + * @v: Selector value + * @pos: Index of syscall argument being parsed + * @type: Type of selected value, set on return + * @desc: Description of selected value, set on return + */ +static void select_desc(struct gluten_ctx *g, struct select *s, union value v, + int pos, enum type *type, union desc *desc) +{ + if (TYPE_IS_NUM(s->field->type)) { + struct select_num *d_num; + + for (d_num = s->desc.d_num; d_num->target.type; d_num++) { + if (d_num->value == v.v_num) { + if (d_num->target.pos == pos) { + *type = d_num->target.type; + *desc = d_num->target.desc; + } else { + pos = d_num->target.pos; + g->selected_arg[pos] = &d_num->target; + *type = NONE; + } + + return; + } + } + + if (!d_num->target.type) + die(" No match for numeric selector %i", v.v_num); + } + + die(" not supported yet"); +} + +/** + * parse_value() - Parse JSON value for generic item of data description + * @g: gluten context + * @index: Index of parent syscall argument + * @offset: Base offset of container field (actual offset for non-compound) + * @type: Data type, from model + * @str_len: Length of string, valid for STRING type only + * @desc: Description of possible values, from model + * @value: JSON value + */ +static void parse_value(struct gluten_ctx *g, int index, + struct gluten_offset offset, enum type type, + size_t str_len, union desc desc, JSON_Value *value) +{ + struct gluten_offset data_offset, const_offset; + const char *tag_name; + JSON_Object *tmp; + struct field *f; + union value v; + + if (type == SELECT) { + struct select *select = desc.d_select; + struct field *field = select->field; + JSON_Value *sel; + + if ((tmp = json_value_get_object(value))) { + if (!(sel = json_object_get_value(tmp, field->name))) + die(" no selector for '%s'", field->name); + } else { + sel = value; + } + + value_get(field->desc, field->type, sel, &v); + const_offset = emit_data(g, field->type, field->strlen, &v); + + data_offset = offset; + data_offset.offset += field->offset; + + emit_cmp_field(g, CMP_NE, field, data_offset, const_offset, + JUMP_NEXT_BLOCK); + + select_desc(g, select, v, index, &type, &desc); + + if (type == NONE) + return; + } + + if (json_value_get_type(value) == JSONObject && + (tmp = json_value_get_object(value)) && + (tag_name = json_object_get_string(tmp, "tag"))) { + if (TYPE_IS_COMPOUND(type)) + die("Tag reference '%s' to compound value", tag_name); + + debug(" setting tag reference '%s'", tag_name); + gluten_add_tag(g, tag_name, offset); + + value = json_object_get_value(tmp, "value"); + } + + /* Nothing to match on: just store as reference */ + if (!value) + return; + + switch (type) { + case INTFLAGS: + case LONGFLAGS: + case U32FLAGS: + /* fetch/combine expr algebra loop */ + case INTMASK: + /* calculate mask first */ + break; + case INT: + case LONG: + case U32: + v.v_num = value_get_num(desc.d_num, value); + const_offset = emit_data(g, type, 0, &v); + emit_cmp(g, CMP_NE, offset, const_offset, gluten_size[type], + JUMP_NEXT_BLOCK); + break; + case SELECT: + /* TODO: check how nested selects should work */ + parse_value(g, index, offset, type, 0, desc, value); + break; + case STRING: + v.v_str = json_value_get_string(value); + if (strlen(v.v_str) + 1 > str_len) + die(" string %s too long for field", v.v_str); + + const_offset = emit_data(g, STRING, strlen(v.v_str) + 1, &v); + emit_cmp(g, CMP_NE, offset, const_offset, strlen(v.v_str) + 1, + JUMP_NEXT_BLOCK); + break; + case STRUCT: + for (f = desc.d_struct; f->name; f++) { + JSON_Value *field_value; + + tmp = json_value_get_object(value); + field_value = json_object_get_value(tmp, f->name); + if (!field_value) + continue; + + parse_value(g, index, offset, f->type, f->strlen, + f->desc, field_value); + } + default: + ; + } +} + +/** + * parse_arg() - Parse syscall argument from JSON, following model + * @g: gluten context + * @name: Name of argument (key) in JSON and model + * @value: JSON value for argument + * @a: Argument description from model + */ +static void parse_arg(struct gluten_ctx *g, const char *name, JSON_Value *value, + struct arg *a) +{ + struct gluten_offset offset; + + debug(" Parsing match argument %s", name); + + offset = arg_load(g, a); + + parse_value(g, a->pos, offset, a->type, a->size, a->desc, value); +} + +/** + * parse_match() - Parse one syscall rule in "match" array + * @g: gluten context + * @obj: Matching rule for one syscall + * @args: Description of arguments from syscall model + */ +static void parse_match(struct gluten_ctx *g, JSON_Object *obj, + struct arg *args) +{ + unsigned count = 0; + struct arg *a; + + for (a = args; a->name; a++) { + struct arg *real_arg = a; + JSON_Value *value; + + if (a->type == SELECTED) { + if (!(real_arg = g->selected_arg[a->pos])) + die(" No selection for argument %s", a->name); + } + + if ((value = json_object_get_value(obj, real_arg->name))) { + count++; + parse_arg(g, real_arg->name, value, real_arg); + } + } + + if (count != json_object_get_count(obj)) + die(" Stray elements in match"); +} + +/** + * handle_matches() - Parse "match" array, find syscall models + * @g: gluten context + * @value: "match" object containing array of rules + */ +void handle_matches(struct gluten_ctx *g, JSON_Value *value) +{ + JSON_Array *matches = json_value_get_array(value); + unsigned i; + + for (i = 0; i < json_array_get_count(matches); i++) { + JSON_Object *match, *args; + struct call **set, *call; + const char *name; + + g->mr = g->ip; + + match = json_array_get_object(matches, i); + name = json_object_get_name(match, 0); + args = json_object_get_object(match, name); + debug(" Parsing match %i: %s", i, name); + + for (set = call_sets, call = set[0]; *set; ) { + if (!call->name) { + set++; + call = set[0]; + continue; + } + + if (!strcmp(name, call->name)) { + union value v = { .v_num = call->number }; + + debug(" Found description for %s", name); + emit_nr(g, emit_data(g, U64, 0, &v)); + + filter_notify(call->number); + + parse_match(g, args, call->args); + break; + } + call++; + } + + if (!*set) + die(" Unknown system call: %s", name); + + link_match(g); + } +} |