// SPDX-License-Identifier: GPL-3.0-or-later /* seitan - Syscall Expressive Interpreter, Transformer and Notifier * * cooker/emit.c - Generate gluten (bytecode) instructions and read-only data * * Copyright 2023 Red Hat GmbH * Author: Stefano Brivio * Alice Frosi */ #include "cooker.h" #include "gluten.h" #include "util.h" #include "emit.h" static const char *type_str[] = { "UNDEF", "NONE", "INT", "INTMASK", "INTFLAGS", "U32", "U32MASK", "U32FLAGS", "U64", "U64MASK", "U64FLAGS", "LONG", "LONGMASK", "LONGFLAGS", "STRING", "STRUCT", "SELECT", "PID", "PORT", "IPV4", "IPV6", "FDPATH", NULL }; static const char *cmp_type_str[] = { "EQ", "NE", "GT", "GE", "LT", "LE", NULL }; /** * emit_nr() - Emit OP_NR instruction: jump on syscall mismatch * @g: gluten context * @number: Pointer to system call number */ void emit_nr(struct gluten_ctx *g, struct gluten_offset number) { struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); struct op_nr *nr = &op->op.nr; op->type = OP_NR; nr->nr = number; nr->no_match.type = OFFSET_INSTRUCTION; nr->no_match.offset = JUMP_NEXT_BLOCK; debug(" %i: OP_NR: if syscall number is not %li, jump to %s", g->ip.offset, number, jump_name[nr->no_match.offset]); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } /** * emit_load() - Emit OP_LOAD instruction: dereference and copy syscall argument * @g: gluten context * @dst: gluten destination to copy dereferenced data * @index: Index of system call argument * @len: Length of data item pointed by reference */ void emit_load(struct gluten_ctx *g, struct gluten_offset dst, int index, size_t len) { struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); struct op_load *load = &op->op.load; op->type = OP_LOAD; load->src.type = OFFSET_SECCOMP_DATA; load->src.offset = index; load->dst = dst; debug(" %i: OP_LOAD: #%i < args[%i] (size: %lu)", g->ip.offset, dst.offset, index, len); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } /** * emit_cmp(): Emit OP_CMP instruction: compare data from two offsets * @g: gluten context * @cmp_type: Type of comparison * @x: gluten pointer to first operand of comparison * @y: gluten pointer to second operand of comparison * @size: Size of comparison * @jmp: Jump direction if comparison is true */ void emit_cmp(struct gluten_ctx *g, enum op_cmp_type cmp_type, struct gluten_offset x, struct gluten_offset y, size_t size, enum jump_type jmp) { struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); struct op_cmp *cmp = &op->op.cmp; op->type = OP_CMP; cmp->x = x; cmp->y = y; cmp->size = size; cmp->cmp = cmp_type; cmp->jmp.type = OFFSET_INSTRUCTION; cmp->jmp.offset = jmp; debug(" %i: OP_CMP: if %s: #%lu %s (size: %lu) %s: #%lu, jump to %s", g->ip.offset, gluten_offset_name[x.type], x.offset, cmp_type_str[cmp_type], size, gluten_offset_name[y.type], y.offset, jump_name[jmp]); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } /** * emit_cmp_field() - Emit OP_CMP for a given field type * @g: gluten context * @cmp: Type of comparison * @field: Description of field from system call model * @x: gluten pointer to first operand of comparison * @y: gluten pointer to second operand of comparison * @jmp: Jump direction if comparison is true */ void emit_cmp_field(struct gluten_ctx *g, enum op_cmp_type cmp, struct field *field, struct gluten_offset x, struct gluten_offset y, enum jump_type jmp) { emit_cmp(g, cmp, x, y, field->strlen ? field->strlen : gluten_size[field->type], jmp); } /** * emit_return() - Emit OP_RETURN instruction: return value * @g: gluten context * @v: Pointer to return value */ void emit_return(struct gluten_ctx *g, struct gluten_offset v) { struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); struct op_return *ret = &op->op.ret; op->type = OP_RETURN; ret->val = v; debug(" %i: OP_RETURN:", g->ip.offset); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } /** * emit_block() - Emit OP_BLOCK instruction: return error value * @g: gluten context * @error: Error value */ void emit_block(struct gluten_ctx *g, int32_t error) { struct op *op = (struct op *)gluten_ptr(&g->g, g->ip); struct op_block *block = &op->op.block; op->type = OP_BLOCK; block->error = error; debug(" %i: OP_BLOCK: %d", g->ip.offset, error); if (++g->ip.offset > INST_MAX) die("Too many instructions"); } struct gluten_offset emit_data(struct gluten_ctx *g, enum type type, size_t str_len, union value *value) { void *p = gluten_ptr(&g->g, g->cp); struct gluten_offset ret = g->cp; switch (type) { case INT: if (g->cp.offset + sizeof(int) > RO_DATA_SIZE) die(" Read-only data section exceeded"); *(int *)p = value->v_int; debug(" C#%i: (%s) %i", g->cp.offset, type_str[type], value->v_int); g->cp.offset += sizeof(int); break; case U64: if (g->cp.offset + sizeof(uint64_t) > RO_DATA_SIZE) die(" Read-only data section exceeded"); *(uint64_t *)p = value->v_u64; debug(" C#%i: (%s) %i", g->cp.offset, type_str[type], value->v_u64); g->cp.offset += sizeof(uint64_t); break; case STRING: if (g->cp.offset + str_len > RO_DATA_SIZE) die(" Read-only data section exceeded"); strncpy(p, value->v_str, str_len); debug(" C#%i: (%s:%i) %s", g->cp.offset, type_str[type], str_len, value->v_str); g->cp.offset += str_len; break; default: ; } return ret; } static void gluten_link(struct gluten_ctx *g, enum jump_type type, struct op *start) { struct gluten_offset *jmp; struct op *op; for (op = (struct op *)start; op->type; op++) { switch (op->type) { case OP_NR: jmp = &op->op.nr.no_match; break; case OP_CMP: jmp = &op->op.cmp.jmp; break; default: continue; } if (jmp->offset == type) { jmp->offset = g->ip.offset; debug(" linked jump of instruction #%i to #%i", op - (struct op *)g->g.inst, g->ip.offset); } } } void link_block(struct gluten_ctx *g) { debug(" Linking block..."); gluten_link(g, JUMP_NEXT_BLOCK, (struct op *)gluten_ptr(&g->g, g->lr)); } void link_match(struct gluten_ctx *g) { debug(" Linking match..."); gluten_link(g, JUMP_NEXT_MATCH, (struct op *)gluten_ptr(&g->g, g->mr)); }