diff options
Diffstat (limited to 'cooker/seccomp_profile.c')
-rw-r--r-- | cooker/seccomp_profile.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/cooker/seccomp_profile.c b/cooker/seccomp_profile.c new file mode 100644 index 0000000..e75fda2 --- /dev/null +++ b/cooker/seccomp_profile.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.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: Alice Frosi <afrosi@redhat.com> + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdbool.h> +#include <string.h> + +#include "seccomp_profile.h" + +static struct seccomp scmp_profile; +static unsigned int counter; +static unsigned int count_args; +static JSON_Object *root_obj; +static JSON_Value *root; +static bool ignore_syscall; + +const char *scmp_act_str[] = { + "SCMP_ACT_KILL_THREAD", "SCMP_ACT_TRAP", "SCMP_ACT_ERRNO", + "SCMP_ACT_TRACE", "SCMP_ACT_ALLOW", "SCMP_ACT_LOG", + "SCMP_ACT_NOTIFY", +}; + +const char *scmp_op_str[] = { + "", + "SCMP_CMP_NE", + "SCMP_CMP_LT", + "SCMP_CMP_LE", + "SCMP_CMP_EQ", + "SCMP_CMP_GE", + "SCMP_CMP_GT", + "SCMP_CMP_MASKED_EQ", +}; + +const char *arch_str[] = { + "SCMP_ARCH_NATIVE", "SCMP_ARCH_X86", "SCMP_ARCH_X86_64", + "SCMP_ARCH_X32", "SCMP_ARCH_ARM", "SCMP_ARCH_AARCH64", + "SCMP_ARCH_MIPS", "SCMP_ARCH_MIPS64", "SCMP_ARCH_MIPS64N32", + "SCMP_ARCH_MIPSEL", "SCMP_ARCH_MIPSEL64", "SCMP_ARCH_MIPSEL64N32", + "SCMP_ARCH_PPC", "SCMP_ARCH_PPC64", "SCMP_ARCH_PPC64LE", + "SCMP_ARCH_S390", "SCMP_ARCH_S390X", "SCMP_ARCH_PARISC", + "SCMP_ARCH_PARISC64", "SCMP_ARCH_RISCV64", +}; + +const char *scmp_filter_str[] = { + "SECCOMP_FILTER_FLAG_TSYNC", + "SECCOMP_FILTER_FLAG_LOG", + "SECCOMP_FILTER_FLAG_SPEC_ALLOW", + "SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV", +}; + +// TODO: decide defaults for when the original profile isn't definied +static void set_defaults_scmp_profile() +{ + scmp_profile.default_action = ACT_ERRNO; + die("Not implemented yet"); +} + +static void parse_orig_scmp_profile(char *path) +{ + debug("Use %s as base for the generated seccomp profile", path); + root = json_parse_file(path); + if (root == NULL) + die(" failed parsing JSON seccomp profile: %s", path); +} + +/** + * is_scmp_notify_set - Verify if one of the syscall entries has the SCMP_NOTIFY + * action enabled + * + */ +static bool is_scmp_notify_set(JSON_Array *syscalls) +{ + const char *action; + JSON_Object *obj; + unsigned int i; + + for (i = 0; i < json_array_get_count(syscalls); i++) { + if (((obj = json_array_get_object(syscalls, i)) == NULL) || + ((action = json_object_get_string(obj, "action")) == NULL)) + continue; + if (strcmp(action, scmp_act_str[ACT_NOTIFY]) == 0) + return true; + } + return false; +} + +void scmp_profile_init(char *path) +{ + JSON_Array *syscalls; + counter = 0; + count_args = 0; + + if (path == NULL) { + root = json_value_init_object(); + debug("Set defaults for the seccomp profile"); + set_defaults_scmp_profile(); + } else { + parse_orig_scmp_profile(path); + } + if ((root_obj = json_value_get_object(root)) == NULL) + die(" failed serialize JSON"); + + if ((syscalls = json_object_get_array(root_obj, "syscalls")) == NULL) + return; + if (is_scmp_notify_set(syscalls)) + die(" the use of multiple seccomp notifiers isn't supported"); +} + +static bool is_syscall_present(const char *name) +{ + if (name == NULL) + return false; + for (unsigned int i = 0; i < counter; i++) + if (strcmp(scmp_profile.syscalls[i].names, name) == 0) + return true; + + return false; +} + +void scmp_profile_notify(const char *name) +{ + ignore_syscall = false; + if (is_syscall_present(name)) { + ignore_syscall = true; + return; + } + debug(" #%u add syscall %s to seccomp profile", counter, name); + strcpy(scmp_profile.syscalls[counter].names, name); + scmp_profile.syscalls[counter].action = ACT_NOTIFY; +} + +void scmp_profile_add_check(int index, union value v, union value mask, + enum op_cmp_type cmp) +{ + char *name = scmp_profile.syscalls[counter].names; + struct scmp_arg *arg; + + if (count_args > 5) + die(" too many arguments for the syscall entry %d:%s", counter, + name); + debug(" #%u add arg to syscall %s to seccomp profile", count_args); + arg = &scmp_profile.syscalls[counter].args[count_args]; + arg->index = index; + arg->value = v.v_num; + arg->set = true; + if (mask.v_num) { + arg->valueTwo = mask.v_num; + arg->op = OP_MASKEDEQUAL; + return; + } + + // TODO: check if also the other cmp operations are inverted in cooker + switch (cmp) { + case CMP_NE: + arg->op = OP_EQUALTO; + break; + case CMP_EQ: + arg->op = OP_NOTEQUAL; + break; + case CMP_LE: + arg->op = OP_LESSEQUAL; + break; + case CMP_LT: + arg->op = OP_LESSTHAN; + break; + case CMP_GE: + arg->op = OP_GREATEREQUAL; + break; + case CMP_GT: + arg->op = OP_GREATERTHAN; + break; + default: + die(" operation not recognized"); + break; + } +} + +void scmp_profile_flush_args() +{ + if (ignore_syscall) + return; + debug(" flush args for syscall %s", + scmp_profile.syscalls[counter].names); + counter++; + count_args = 0; +} + +static void json_append_syscall(JSON_Array *syscalls, struct syscall *syscall) +{ + JSON_Value *val = json_value_init_object(); + JSON_Object *obj = json_value_get_object(val); + JSON_Value *arg = json_value_init_object(); + JSON_Object *arg_obj = json_value_get_object(arg); + JSON_Array *names_array; + JSON_Array *args_array; + + json_object_set_value(obj, "names", json_value_init_array()); + json_object_set_value(obj, "args", json_value_init_array()); + names_array = json_object_get_array(obj, "names"); + ; + args_array = json_object_get_array(obj, "args"); + ; + check_JSON_status(json_object_set_string(obj, "action", + scmp_act_str[ACT_NOTIFY])); + check_JSON_status( + json_array_append_string(names_array, &syscall->names[0])); + for (unsigned int i = 0; i < 6; i++) { + if (!syscall->args[i].set) + continue; + check_JSON_status(json_object_set_number( + arg_obj, "index", syscall->args[i].index)); + check_JSON_status(json_object_set_number( + arg_obj, "value", syscall->args[i].value)); + check_JSON_status(json_object_set_number( + arg_obj, "valueTwo", syscall->args[i].valueTwo)); + check_JSON_status(json_object_set_string( + arg_obj, "op", scmp_op_str[syscall->args[i].op])); + check_JSON_status(json_array_append_value(args_array, arg)); + debug(" added args for syscall %s %d: ", syscall->names, i, + syscall->args[i].value, syscall->args[i].valueTwo, + scmp_op_str[syscall->args[i].op]); + } + json_array_append_value(syscalls, val); +} + +/** + * remove_existing_syscall() - Remove the syscall entry name from the list where + * the syscall is listed as allowed and without parameters. + * Eg. if in the original seccomp profile with the 'connect' syscall: + * "syscalls": [ + * { + * "names": [ .. + * "connect", + * .. + * "action": "SCMP_ACT_ALLOW", + * "args": [], + * } + * + */ +static void remove_existing_syscall(JSON_Array *syscalls, char *name) +{ + const char *curr_syscall, *action; + JSON_Array *names, *args; + unsigned int i, k; + JSON_Object *obj; + + for (i = 0; i < json_array_get_count(syscalls); i++) { + if (((obj = json_array_get_object(syscalls, i)) == NULL) || + ((names = json_object_get_array(obj, "names")) == NULL) || + ((args = json_object_get_array(obj, "args")) == NULL) || + ((action = json_object_get_string(obj, "action")) == NULL)) + continue; + if ((strcmp(action, scmp_act_str[ACT_ALLOW]) != 0) || + (json_array_get_count(args) > 0)) + continue; + for (k = 0; k < json_array_get_count(names); k++) { + curr_syscall = json_array_get_string(names, k); + if (curr_syscall == NULL) + die(" empty syscall name"); + if (strcmp(curr_syscall, name) == 0) { + debug(" remove %s as duplicate at %d", + curr_syscall, k); + json_array_remove(names, k); + } + } + } +} + +static void json_serialize_syscalls() +{ + JSON_Array *syscalls; + unsigned int i; + + if ((syscalls = json_object_get_array(root_obj, "syscalls")) == NULL) { + json_object_set_value(root_obj, "syscalls", + json_value_init_array()); + syscalls = json_object_get_array(root_obj, "syscalls"); + } + + for (i = 0; i < counter; i++) { + remove_existing_syscall(syscalls, + &scmp_profile.syscalls[i].names[0]); + // Create syscall entry for the notify + json_append_syscall(syscalls, &scmp_profile.syscalls[i]); + } +} + +void scmp_profile_write(const char *file) +{ + debug("Write seccomp profile to %s", file); + json_serialize_syscalls(); + json_serialize_to_file_pretty(root, file); + + json_value_free(root); + + return; +} |