aboutgitcodelistschat:MatrixIRC
path: root/cooker/match.c
blob: bd96a035a66047d458e70396a05cb977ae403811 (plain) (tree)








































































































































































































































































































































































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