aboutgitcodelistschat:MatrixIRC
path: root/cooker/seccomp_profile.c
blob: e75fda2e1dc6e5cce5b7b5980c98cce437fb4001 (plain) (tree)
















































































































































































































































































































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