/**
 * BPF Simulator
 *
 * Copyright (c) 2012 Red Hat <pmoore@redhat.com>
 * Author: Paul Moore <paul@paul-moore.com>
 */

/*
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of version 2.1 of the GNU Lesser General Public License as
 * published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses>.
 */

#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <linux/audit.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "bpf.h"
#include "util.h"

#define BPF_PRG_MAX_LEN		4096

/**
 * BPF simulator machine state
 */
struct sim_state {
	uint32_t acc;
	uint32_t temp[BPF_SCRATCH_SIZE];
};

struct bpf_program {
	size_t i_cnt;
	bpf_instr_raw *i;
};

static unsigned int opt_verbose = 0;

/**
 * Print the usage information to stderr and exit
 * @param program the name of the current program being invoked
 *
 * Print the usage information and exit with EINVAL.
 *
 */
static void exit_usage(const char *program)
{
	fprintf(stderr,
		"usage: %s -f <bpf_file> [-v] [-h]"
		" -a <arch> -s <syscall_num> [-0 <a0>] ... [-5 <a5>]\n",
		program);
	exit(EINVAL);
}

/**
 * Handle a simulator fault
 * @param rc the error or return code
 *
 * Print a "FAULT" to stderr to indicate a simulator fault, and an errno value
 * if the simulator is running in verbose mode, then exit with EFAULT.
 *
 */
static void exit_fault(unsigned int rc)
{
	if (opt_verbose)
		fprintf(stderr, "FAULT: errno = %d\n", rc);
	else
		fprintf(stderr, "FAULT\n");
	exit(EFAULT);
}

/**
 * Handle a BPF program error
 * @param rc the error or return code
 * @param line the line number
 *
 * Print an "ERROR" to stderr to indicate a program error, and an errno value
 * if the simulator is running in verbose mode, then exit with ENOEXEC.
 *
 */
static void exit_error(unsigned int rc, unsigned int line)
{
	if (opt_verbose)
		fprintf(stderr, "ERROR: errno = %d, line = %d\n", rc, line);
	else
		fprintf(stderr, "ERROR\n");
	exit(ENOEXEC);
}

/**
 * Handle a simulator return/action
 * @param action the return value
 * @param line the line number
 *
 * Display the action to stdout and exit with 0.
 *
 */
static void end_action(uint32_t action, unsigned int line)
{
	uint32_t act = action & SECCOMP_RET_ACTION_FULL;
	uint32_t data = action & SECCOMP_RET_DATA;

	switch (act) {
	case SECCOMP_RET_KILL_PROCESS:
		fprintf(stdout, "KILL_PROCESS\n");
		break;
	case SECCOMP_RET_KILL_THREAD:
		fprintf(stdout, "KILL\n");
		break;
	case SECCOMP_RET_TRAP:
		fprintf(stdout, "TRAP\n");
		break;
	case SECCOMP_RET_ERRNO:
		fprintf(stdout, "ERRNO(%u)\n", data);
		break;
	case SECCOMP_RET_TRACE:
		fprintf(stdout, "TRACE(%u)\n", data);
		break;
	case SECCOMP_RET_LOG:
		fprintf(stdout, "LOG\n");
		break;
	case SECCOMP_RET_ALLOW:
		fprintf(stdout, "ALLOW\n");
		break;
	default:
		exit_error(EDOM, line);
	}

	exit(0);
}

/**
 * Execute a BPF program
 * @param prg the loaded BPF program
 * @param sys_data the syscall record being tested
 *
 * Simulate the BPF program with the given syscall record.
 *
 */
static void bpf_execute(const struct bpf_program *prg,
			const struct seccomp_data *sys_data)
{
	unsigned int ip, ip_c;
	struct sim_state state;
	bpf_instr_raw *bpf;
	unsigned char *sys_data_b = (unsigned char *)sys_data;
	uint16_t code;
	uint8_t jt;
	uint8_t jf;
	uint32_t k;

	/* initialize the machine state */
	ip_c = 0;
	ip = 0;
	memset(&state, 0, sizeof(state));

	while (ip < prg->i_cnt) {
		/* get the instruction and bump the ip */
		ip_c = ip;
		bpf = &prg->i[ip++];

		code = ttoh16(arch, bpf->code);
		jt = bpf->jt;
		jf = bpf->jf;
		k = ttoh32(arch, bpf->k);

		switch (code) {
		case BPF_LD+BPF_W+BPF_ABS:
			if (k < BPF_SYSCALL_MAX) {
				uint32_t val = *((uint32_t *)&sys_data_b[k]);
				state.acc = ttoh32(arch, val);
			} else
				exit_error(ERANGE, ip_c);
			break;
		case BPF_ALU+BPF_OR+BPF_K:
			state.acc |= k;
			break;
		case BPF_ALU+BPF_AND+BPF_K:
			state.acc &= k;
			break;
		case BPF_JMP+BPF_JA:
			ip += k;
			break;
		case BPF_JMP+BPF_JEQ+BPF_K:
			if (state.acc == k)
				ip += jt;
			else
				ip += jf;
			break;
		case BPF_JMP+BPF_JGT+BPF_K:
			if (state.acc > k)
				ip += jt;
			else
				ip += jf;
			break;
		case BPF_JMP+BPF_JGE+BPF_K:
			if (state.acc >= k)
				ip += jt;
			else
				ip += jf;
			break;
		case BPF_RET+BPF_K:
			end_action(k, ip_c);
			break;
		default:
			/* since we don't support the full bpf language just
			 * yet, this could be either a fault or an error, we'll
			 * treat it as a fault until we provide full support */
			exit_fault(EOPNOTSUPP);
		}
	}

	/* if we've reached here there is a problem with the program */
	exit_error(ERANGE, ip_c);
}

/**
 * main
 */
int main(int argc, char *argv[])
{
	int opt;
	int iter;
	char *opt_file = NULL;
	FILE *file;
	size_t file_read_len;
	struct seccomp_data sys_data;
	struct bpf_program bpf_prg;

	/* initialize the syscall record */
	memset(&sys_data, 0, sizeof(sys_data));

	/* parse the command line */
	while ((opt = getopt(argc, argv, "a:f:hs:v0:1:2:3:4:5:")) > 0) {
		switch (opt) {
		case 'a':
			if (strcmp(optarg, "x86") == 0)
				arch = AUDIT_ARCH_I386;
			else if (strcmp(optarg, "x86_64") == 0)
				arch = AUDIT_ARCH_X86_64;
			else if (strcmp(optarg, "x32") == 0)
				arch = AUDIT_ARCH_X86_64;
			else if (strcmp(optarg, "arm") == 0)
				arch = AUDIT_ARCH_ARM;
			else if (strcmp(optarg, "aarch64") == 0)
				arch = AUDIT_ARCH_AARCH64;
			else if (strcmp(optarg, "mips") == 0)
				arch = AUDIT_ARCH_MIPS;
			else if (strcmp(optarg, "mipsel") == 0)
				arch = AUDIT_ARCH_MIPSEL;
			else if (strcmp(optarg, "mips64") == 0)
				arch = AUDIT_ARCH_MIPS64;
			else if (strcmp(optarg, "mipsel64") == 0)
				arch = AUDIT_ARCH_MIPSEL64;
			else if (strcmp(optarg, "mips64n32") == 0)
				arch = AUDIT_ARCH_MIPS64N32;
			else if (strcmp(optarg, "mipsel64n32") == 0)
				arch = AUDIT_ARCH_MIPSEL64N32;
			else if (strcmp(optarg, "parisc") == 0)
				arch = AUDIT_ARCH_PARISC;
			else if (strcmp(optarg, "parisc64") == 0)
				arch = AUDIT_ARCH_PARISC64;
			else if (strcmp(optarg, "ppc") == 0)
				arch = AUDIT_ARCH_PPC;
			else if (strcmp(optarg, "ppc64") == 0)
				arch = AUDIT_ARCH_PPC64;
			else if (strcmp(optarg, "ppc64le") == 0)
				arch = AUDIT_ARCH_PPC64LE;
			else if (strcmp(optarg, "s390") == 0)
				arch = AUDIT_ARCH_S390;
			else if (strcmp(optarg, "s390x") == 0)
				arch = AUDIT_ARCH_S390X;
			else if (strcmp(optarg, "riscv64") == 0)
				arch = AUDIT_ARCH_RISCV64;
			else
				exit_fault(EINVAL);
			break;
		case 'f':
			if (opt_file)
				exit_fault(EINVAL);
			opt_file = strdup(optarg);
			if (opt_file == NULL)
				exit_fault(ENOMEM);
			break;
		case 's':
			sys_data.nr = strtol(optarg, NULL, 0);
			break;
		case 'v':
			opt_verbose = 1;
			break;
		case '0':
			sys_data.args[0] = strtoull(optarg, NULL, 0);
			break;
		case '1':
			sys_data.args[1] = strtoull(optarg, NULL, 0);
			break;
		case '2':
			sys_data.args[2] = strtoull(optarg, NULL, 0);
			break;
		case '3':
			sys_data.args[3] = strtoull(optarg, NULL, 0);
			break;
		case '4':
			sys_data.args[4] = strtoull(optarg, NULL, 0);
			break;
		case '5':
			sys_data.args[5] = strtoull(optarg, NULL, 0);
			break;
		case 'h':
		default:
			/* usage information */
			exit_usage(argv[0]);
		}
	}

	/* adjust the endianess of sys_data to match the target */
	sys_data.nr = htot32(arch, sys_data.nr);
	sys_data.arch = htot32(arch, arch);
	sys_data.instruction_pointer = htot64(arch,
					      sys_data.instruction_pointer);
	for (iter = 0; iter < BPF_SYS_ARG_MAX; iter++)
		sys_data.args[iter] = htot64(arch, sys_data.args[iter]);

	/* allocate space for the bpf program */
	/* XXX - we should make this dynamic */
	bpf_prg.i_cnt = 0;
	bpf_prg.i = calloc(BPF_PRG_MAX_LEN, sizeof(*bpf_prg.i));
	if (bpf_prg.i == NULL)
		exit_fault(ENOMEM);

	/* load the bpf program */
	if (opt_file == NULL)
		exit_usage(argv[0]);
	file = fopen(opt_file, "r");
	if (file == NULL)
		exit_fault(errno);
	do {
		file_read_len = fread(&(bpf_prg.i[bpf_prg.i_cnt]),
				      sizeof(*bpf_prg.i), 1, file);
		if (file_read_len == 1)
			bpf_prg.i_cnt++;

		/* check the size */
		if (bpf_prg.i_cnt == BPF_PRG_MAX_LEN)
			exit_fault(E2BIG);
	} while (file_read_len > 0);
	fclose(file);

	/* execute the bpf program */
	bpf_execute(&bpf_prg, &sys_data);

	/* we should never reach here */
	exit_fault(EFAULT);
	return 0;
}
