From 240eb94b44f9dc613a85911d4190df129372e9cc Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Tue, 18 Jul 2023 11:15:53 +0200 Subject: cooker: generate OCI seccomp profile Generate the OCI seccomp profile instead of directly the BPF filter. The seccomp profile will be used consquently by the container runtime as input in order to generate the BPF filter. Example with mknod: $ seitan-cooker -g /tmp/gluten -p /tmp/scmp_prof.json -s seccomp.json -i demo/mknod.hjson $ seitan -s /tmp/seitan.sock -i /tmp/gluten $ podman run --cap-drop ALL --security-opt=seccomp=/tmp/scmp_prof.json \ --annotation run.oci.seccomp.receiver=/tmp/seitan.sock \ -ti fedora \ sh -c 'mknod /dev/lol c 1 7 && ls /dev/lol' /dev/lol Signed-off-by: Alice Frosi --- cooker/seccomp_profile.c | 305 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 cooker/seccomp_profile.c (limited to 'cooker/seccomp_profile.c') 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 + */ + +#define _GNU_SOURCE +#include +#include +#include + +#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; +} -- cgit v1.2.3