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