// SPDX-License-Identifier: GPL-2.0-or-later /* seitan - Syscall Expressive Interpreter, Transformer and Notifier * * cooker/parse.c - JSON recipe parsing * * Copyright 2023 Red Hat GmbH * Author: Stefano Brivio * Alice Frosi */ #include "parson.h" #include "calls.h" #include "cooker.h" #include "gluten.h" #include "call.h" #include "match.h" #include "emit.h" #include "util.h" static bool get_valid_tag_field(const JSON_Object *obj, const char *field, char *tag) { JSON_Value *jvalue = json_object_get_value(obj, field); JSON_Object *tmp; if (json_value_get_type(jvalue) != JSONObject) return false; if (!(tmp = json_object_get_object(obj, field))) return false; if (json_object_get_string(tmp, "set")) { strcpy(tag, json_object_get_string(tmp, "set")); return true; } if (json_object_get_string(tmp, "get")) { strcpy(tag, json_object_get_string(tmp, "get")); return true; } return NULL; } static void handle_fd(struct gluten_ctx *g, JSON_Value *value) { JSON_Object *obj = json_value_get_object(value), *tmp = NULL; struct fd_desc desc = { .cloexec = 1 }; JSON_Value *jvalue; char tag[PATH_MAX]; debug(" Parsing \"fd\""); jvalue = json_object_get_value(obj, "src"); if (json_value_get_type(jvalue) == JSONObject) { if (!get_valid_tag_field(obj, "src", tag)) die("invalid tag specification: %s", json_serialize_to_string( json_object_get_wrapping_value(tmp))); desc.srcfd = gluten_get_tag(g, tag); } else if (json_value_get_type(jvalue) == JSONNumber) { union value v = { .v_num = json_value_get_number(jvalue) }; desc.srcfd = emit_data(g, U32, 0, &v); } else { die("no valid \"src\" in \"fd\""); } jvalue = json_object_get_value(obj, "new"); if (!jvalue) { ; } else if (json_value_get_type(jvalue) == JSONObject) { tmp = json_object_get_object(obj, "new"); if (!get_valid_tag_field(obj, "new", tag)) die("invalid tag specification: %s", json_serialize_to_string( json_object_get_wrapping_value(tmp))); desc.newfd = gluten_get_tag(g, tag); desc.setfd = 1; } else if (json_value_get_type(jvalue) == JSONNumber) { union value v = { .v_num = json_value_get_number(jvalue) }; desc.newfd = emit_data(g, U32, 0, &v); desc.setfd = 1; } else { die("invalid \"new\" in \"fd\""); } if (json_object_get_value(obj, "return")) desc.do_return = json_object_get_boolean(obj, "return"); if (json_object_get_value(obj, "close_on_exec")) desc.cloexec = json_object_get_boolean(obj, "close_on_exec"); emit_fd(g, &desc); } static void handle_limit(struct gluten_ctx *g, JSON_Value *value) { (void)g; (void)value; } static void handle_write(struct gluten_ctx *g, JSON_Value *value) { JSON_Object *obj = json_value_get_object(value); struct gluten_offset src, dst, count; const char *tag; if (!(tag = json_object_get_string(obj, "src"))) die("invalid tag specification"); src = gluten_get_tag(g, tag); if (!(tag = json_object_get_string(obj, "dst"))) die("invalid tag specification"); dst = gluten_get_tag(g, tag); if (!(tag = json_object_get_string(obj, "count"))) die("invalid tag specification"); count = gluten_get_tag(g, tag); emit_store(g, dst, src, count); } static void handle_return(struct gluten_ctx *g, JSON_Value *value) { struct gluten_offset data = NULL_OFFSET, error = NULL_OFFSET; JSON_Object *obj = json_value_get_object(value); union value vv = NO_VALUE, ve = NO_VALUE; const char *tag_error = NULL, *tag_value = NULL; JSON_Value *jvalue; bool cont = false; char buf[BUFSIZ]; size_t n; debug(" Parsing \"return\""); jvalue = json_object_get_value(obj, "error"); if (json_value_get_type(jvalue) == JSONNumber) { ve.v_int = json_value_get_number(jvalue); error = emit_data(g, INT, sizeof(ve.v_int), &ve); } else if ((tag_error = json_object_get_string(obj, "error"))) { error = gluten_get_tag(g, tag_error); } jvalue = json_object_get_value(obj, "value"); if (json_value_get_type(jvalue) == JSONNumber) { vv.v_u64 = json_value_get_number(jvalue); data = emit_data(g, U64, sizeof(vv.v_u64), &vv); } else if ((tag_value = json_object_get_string(obj, "value"))) { data = gluten_get_tag(g, tag_value); } jvalue = json_object_get_value(obj, "continue"); if (json_value_get_type(jvalue) == JSONBoolean) cont = json_value_get_boolean(jvalue); if (cont && (error.offset != 0 || data.offset != OFFSET_NULL)) die(" \"continue\" with non-zero value or error code"); if (!cont) { n = snprintf(buf, BUFSIZ, " emit return: value "); if (tag_value) n += snprintf(buf + n, BUFSIZ - n, "tag %s", tag_value); else n += snprintf(buf + n, BUFSIZ - n, "%ld", vv.v_u64); n = snprintf(buf, BUFSIZ, " , error "); if (tag_error) n += snprintf(buf + n, BUFSIZ - n, "tag %s", tag_error); else n += snprintf(buf + n, BUFSIZ - n, "%d", ve.v_int); } else { snprintf(buf, BUFSIZ, " emit return: continue"); } debug(buf); emit_return(g, data, error, cont); } static void handle_context(struct gluten_ctx *g, JSON_Value *value) { (void)g; (void)value; } /** * struct rule_parser - Parsing handler for JSON rule type * @type: JSON key name * @fn: Parsing function */ struct rule_parser { const char *type; void (*fn)(struct gluten_ctx *g, JSON_Value *value); } parsers[] = { { "match", handle_matches }, { "call", handle_calls }, { "write", handle_write }, { "fd", handle_fd }, { "limit", handle_limit }, { "return", handle_return }, { "context", handle_context }, { NULL, NULL }, }; static union value value_get_set(struct num *desc, JSON_Array *set) { union value n = { 0 }; struct num *tmp; unsigned i; for (i = 0; i < json_array_get_count(set); i++) { for (tmp = desc; tmp->name; tmp++) { if (!strcmp(tmp->name, json_array_get_string(set, i))) { n.v_num |= tmp->value; break; } } if (!tmp->name) die("invalid flag'%s'", json_array_get_string(set, i)); } return n; } void value_get_flags(struct num *desc, JSON_Object *obj, union value *bitset, enum op_cmp_type *cmp, union value *cmpterm) { JSON_Array *set; if ((set = json_object_get_array(obj, "some"))) { *bitset = value_get_set(desc, set); *cmp = CMP_EQ; cmpterm->v_num = 0; } else if ((set = json_object_get_array(obj, "all"))) { *bitset = value_get_set(desc, set); *cmp = CMP_NE; *cmpterm = *bitset; } else if ((set = json_object_get_array(obj, "not"))) { *bitset = value_get_set(desc, set); *cmp = CMP_NE; cmpterm->v_num = 0; } else { die("unsupported flag quantifier"); } } /** * value_get_mask() - Get union of all possible numeric values from description * @desc: Description of possible values from model * * Return: numeric value */ long long value_get_mask(struct num *desc) { long long n = 0; while ((desc++)->name) n |= desc->value; return n; } /** * value_get_num() - Get numeric value from description matching JSON input * @desc: Description of possible values from model * @value: JSON value * * Return: numeric value */ 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; } ssize_t value_get_size(struct gluten_ctx *g, intptr_t id) { if (!g) return -1; return gluten_get_attr(g, ATTR_SIZE, id).v_num; } /** * 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 */ 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_field() - Select field based on value and description * @g: gluten context * @pos: Index of syscall argument being parsed * @s: Possible selection choices * @v: Selector value * * Return: pointer to field, NULL if selector refers to a different argument */ struct field *select_field(struct gluten_ctx *g, int pos, struct select *s, union value v) { if (TYPE_IS_NUM(s->field->type)) { struct select_num *d_num; for (d_num = s->desc.d_num; d_num->target.f.type; d_num++) { if (d_num->value == v.v_num) { if (g && d_num->sel_size != -1) { v.v_num = d_num->sel_size; gluten_add_attr(g, ATTR_SIZE, (intptr_t)s, v); } if (d_num->target.pos == pos) return &d_num->target.f; if (g) { pos = d_num->target.pos; g->selected_arg[pos] = &d_num->target; } return NULL; } } if (!d_num->target.f.type) die(" No match for numeric selector %i", v.v_num); } die(" not supported yet"); } /** * parse_block() - Parse a transformation block with rules * @g: gluten context * @block: Array of rules in block */ static void parse_block(struct gluten_ctx *g, JSON_Object *block) { unsigned i; memset(g->selected_arg, 0, sizeof(g->selected_arg)); memset(g->match_dst, 0, sizeof(g->match_dst)); memset(g->call_src, 0, sizeof(g->call_src)); memset(g->tags, 0, sizeof(g->tags)); g->lr = g->ip; for (i = 0; i < json_object_get_count(block); i++) { struct rule_parser *parser; JSON_Value *rule; const char *type; type = json_object_get_name(block, i); rule = json_object_get_value(block, type); for (parser = parsers; parser->type; parser++) { if (!strcmp(type, parser->type)) { parser->fn(g, rule); break; } } if (!parser->type) die(" Invalid rule type: \"%s\"", type); } emit_end(g); link_block(g); } /** * parse_file() - Entry point for parsing of a JSON input file * @g: gluten context * @path: Input file path */ void parse_file(struct gluten_ctx *g, const char *path) { JSON_Array *blocks; JSON_Value *root; JSON_Object *obj; unsigned i; root = json_parse_file_with_comments(path); if (json_value_get_type(root) != JSONArray) die("Invalid input file %s", path); blocks = json_value_get_array(root); for (i = 0; i < json_array_get_count(blocks); i++) { obj = json_array_get_object(blocks, i); debug("Parsing block %i", i); parse_block(g, obj); } }