aboutgitcodelistschat:MatrixIRC
path: root/cooker/match.c
diff options
context:
space:
mode:
Diffstat (limited to 'cooker/match.c')
-rw-r--r--cooker/match.c361
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);
+ }
+}