// 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 */ #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); } }