aboutgitcodelistschat:MatrixIRC
path: root/cooker/match.c
diff options
context:
space:
mode:
authorStefano Brivio <sbrivio@redhat.com>2023-05-16 03:19:01 +0200
committerStefano Brivio <sbrivio@redhat.com>2023-05-16 07:20:25 +0200
commit7ab2bda2e69d4a862572be4b5e389a6aa864470d (patch)
treefa9653204a4ab9581b76499c95d76d16d467301d /cooker/match.c
parent049bd1ca828da835f2903b88adcf9ce0bdacd6e4 (diff)
downloadseitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar
seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.gz
seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.bz2
seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.lz
seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.xz
seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.tar.zst
seitan-7ab2bda2e69d4a862572be4b5e389a6aa864470d.zip
cooker, seitan: Now with 100% more gluten
Pseudorandom changes and progress around cooker and seitan: - cooker: - rename matching functions, split match.c - fix up SELECT semantics - add some form of handling for all syscalls in the example (some stubs) - OP_CMP for all basic and compound types except for flags - link jumps to next block and next match - completed implementation of tags - gluten write - filter clean-ups, write filters (probably not working) - seitan: - load gluten and source instructions and data from there $ ./seitan-cooker cooker/example.hjson example.gluten example.bpf Parsing block 0 Parsing match 0: connect Found description for connect 0: OP_NR: if syscall number is not 0, jump to next block Parsing match argument fd setting tag reference 'fd' tag 'fd' now refers to seccomp data at 0 Parsing match argument addr allocating 128 at offset 0 1: OP_LOAD: #0 < args[1] (size: 128) C#0: (INT) 1 2: OP_CMP: if temporary data: #0 NE (size: 4) read-only data: #0, jump to next block C#4: (STRING:24) /var/run/pr-helper.sock 3: OP_CMP: if temporary data: #0 NE (size: 24) read-only data: #4, jump to next block Linking match... Linking block... linked jump of instruction #0 to #4 linked jump of instruction #2 to #4 linked jump of instruction #3 to #4 Parsing block 1 Parsing match 0: ioctl Found description for ioctl 4: OP_NR: if syscall number is not 112, jump to next block Parsing match argument path Parsing match argument request C#28: (INT) 1074025674 5: OP_CMP: if seccomp data: #1 NE (size: 4) read-only data: #28, jump to next block Parsing match argument ifr allocating 40 at offset 128 6: OP_LOAD: #128 < args[2] (size: 40) C#32: (STRING:5) tap0 7: OP_CMP: if temporary data: #128 NE (size: 5) read-only data: #32, jump to next block C#37: (INT) 1 8: OP_CMP: if temporary data: #128 NE (size: 4) read-only data: #37, jump to next block Linking match... Linking block... linked jump of instruction #4 to #9 linked jump of instruction #5 to #9 linked jump of instruction #7 to #9 linked jump of instruction #8 to #9 Parsing block 2 Parsing match 0: unshare Found description for unshare 9: OP_NR: if syscall number is not 164, jump to next block Parsing match argument flags Linking match... Linking block... linked jump of instruction #9 to #10 Parsing block 3 Parsing match 0: unshare Found description for unshare 10: OP_NR: if syscall number is not 164, jump to next block Parsing match argument flags Linking match... Linking block... linked jump of instruction #10 to #11 Parsing block 4 Parsing match 0: mknod Found description for mknod 11: OP_NR: if syscall number is not 164, jump to next block Parsing match argument path allocating 1 at offset 168 12: OP_LOAD: #168 < args[0] (size: 1) setting tag reference 'path' tag 'path' now refers to temporary data at 168 Parsing match argument mode Parsing match argument major Parsing match argument minor setting tag reference 'minor' tag 'minor' now refers to seccomp data at 2 Linking match... Linking block... linked jump of instruction #11 to #13 Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
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);
+ }
+}