/**
 * BPF Disassembler
 *
 * 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 <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
#include <stdbool.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 _OP_FMT			"%-3s"

/**
 * 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 -a <arch> [-d] [-h]\n", program);
	exit(EINVAL);
}

/**
 * Decode the BPF operand
 * @param bpf the BPF instruction
 *
 * Decode the BPF operand and print it to stdout.
 *
 */
static const char *bpf_decode_op(const bpf_instr_raw *bpf)
{
	switch (bpf->code) {
	case BPF_LD+BPF_W+BPF_IMM:
	case BPF_LD+BPF_W+BPF_ABS:
	case BPF_LD+BPF_W+BPF_IND:
	case BPF_LD+BPF_W+BPF_MEM:
	case BPF_LD+BPF_W+BPF_LEN:
	case BPF_LD+BPF_W+BPF_MSH:
		return "ld";
	case BPF_LD+BPF_H+BPF_IMM:
	case BPF_LD+BPF_H+BPF_ABS:
	case BPF_LD+BPF_H+BPF_IND:
	case BPF_LD+BPF_H+BPF_MEM:
	case BPF_LD+BPF_H+BPF_LEN:
	case BPF_LD+BPF_H+BPF_MSH:
		return "ldh";
	case BPF_LD+BPF_B+BPF_IMM:
	case BPF_LD+BPF_B+BPF_ABS:
	case BPF_LD+BPF_B+BPF_IND:
	case BPF_LD+BPF_B+BPF_MEM:
	case BPF_LD+BPF_B+BPF_LEN:
	case BPF_LD+BPF_B+BPF_MSH:
		return "ldb";
	case BPF_LDX+BPF_W+BPF_IMM:
	case BPF_LDX+BPF_W+BPF_ABS:
	case BPF_LDX+BPF_W+BPF_IND:
	case BPF_LDX+BPF_W+BPF_MEM:
	case BPF_LDX+BPF_W+BPF_LEN:
	case BPF_LDX+BPF_W+BPF_MSH:
	case BPF_LDX+BPF_H+BPF_IMM:
	case BPF_LDX+BPF_H+BPF_ABS:
	case BPF_LDX+BPF_H+BPF_IND:
	case BPF_LDX+BPF_H+BPF_MEM:
	case BPF_LDX+BPF_H+BPF_LEN:
	case BPF_LDX+BPF_H+BPF_MSH:
	case BPF_LDX+BPF_B+BPF_IMM:
	case BPF_LDX+BPF_B+BPF_ABS:
	case BPF_LDX+BPF_B+BPF_IND:
	case BPF_LDX+BPF_B+BPF_MEM:
	case BPF_LDX+BPF_B+BPF_LEN:
	case BPF_LDX+BPF_B+BPF_MSH:
		return "ldx";
	case BPF_ST:
		return "st";
	case BPF_STX:
		return "stx";
	case BPF_ALU+BPF_ADD+BPF_K:
	case BPF_ALU+BPF_ADD+BPF_X:
		return "add";
	case BPF_ALU+BPF_SUB+BPF_K:
	case BPF_ALU+BPF_SUB+BPF_X:
		return "sub";
	case BPF_ALU+BPF_MUL+BPF_K:
	case BPF_ALU+BPF_MUL+BPF_X:
		return "mul";
	case BPF_ALU+BPF_DIV+BPF_K:
	case BPF_ALU+BPF_DIV+BPF_X:
		return "div";
	case BPF_ALU+BPF_OR+BPF_K:
	case BPF_ALU+BPF_OR+BPF_X:
		return "or";
	case BPF_ALU+BPF_AND+BPF_K:
	case BPF_ALU+BPF_AND+BPF_X:
		return "and";
	case BPF_ALU+BPF_LSH+BPF_K:
	case BPF_ALU+BPF_LSH+BPF_X:
		return "lsh";
	case BPF_ALU+BPF_RSH+BPF_K:
	case BPF_ALU+BPF_RSH+BPF_X:
		return "rsh";
	case BPF_ALU+BPF_NEG+BPF_K:
	case BPF_ALU+BPF_NEG+BPF_X:
		return "neg";
	case BPF_ALU+BPF_MOD+BPF_K:
	case BPF_ALU+BPF_MOD+BPF_X:
		return "mod";
	case BPF_ALU+BPF_XOR+BPF_K:
	case BPF_ALU+BPF_XOR+BPF_X:
		return "xor";
	case BPF_JMP+BPF_JA+BPF_K:
	case BPF_JMP+BPF_JA+BPF_X:
		return "jmp";
	case BPF_JMP+BPF_JEQ+BPF_K:
	case BPF_JMP+BPF_JEQ+BPF_X:
		return "jeq";
	case BPF_JMP+BPF_JGT+BPF_K:
	case BPF_JMP+BPF_JGT+BPF_X:
		return "jgt";
	case BPF_JMP+BPF_JGE+BPF_K:
	case BPF_JMP+BPF_JGE+BPF_X:
		return "jge";
	case BPF_JMP+BPF_JSET+BPF_K:
	case BPF_JMP+BPF_JSET+BPF_X:
		return "jset";
	case BPF_RET+BPF_K:
	case BPF_RET+BPF_X:
	case BPF_RET+BPF_A:
		return "ret";
	case BPF_MISC+BPF_TAX:
		return "tax";
	case BPF_MISC+BPF_TXA:
		return "txa";
	}
	return "???";
}

/**
 * Decode a RET action
 * @param k the return action
 *
 * Decode the action and print it to stdout.
 *
 */
static void bpf_decode_action(uint32_t k)
{
	uint32_t act = k & SECCOMP_RET_ACTION_FULL;
	uint32_t data = k & SECCOMP_RET_DATA;

	switch (act) {
	case SECCOMP_RET_KILL_PROCESS:
		printf("KILL_PROCESS");
		break;
	case SECCOMP_RET_KILL_THREAD:
		printf("KILL");
		break;
	case SECCOMP_RET_TRAP:
		printf("TRAP");
		break;
	case SECCOMP_RET_ERRNO:
		printf("ERRNO(%u)", data);
		break;
	case SECCOMP_RET_TRACE:
		printf("TRACE(%u)", data);
		break;
	case SECCOMP_RET_LOG:
		printf("LOG");
		break;
	case SECCOMP_RET_ALLOW:
		printf("ALLOW");
		break;
	default:
		printf("0x%.8x", k);
	}
}

/**
 * Decode the BPF arguments (JT, JF, and K)
 * @param bpf the BPF instruction
 * @param line the current line number
 *
 * Decode the BPF arguments (JT, JF, and K) and print the relevant information
 * to stdout based on the operand.
 *
 */
static void bpf_decode_args(const bpf_instr_raw *bpf, unsigned int line)
{
	switch (BPF_CLASS(bpf->code)) {
	case BPF_LD:
	case BPF_LDX:
		switch (BPF_MODE(bpf->code)) {
		case BPF_ABS:
			printf("$data[%u]", bpf->k);
			break;
		case BPF_MEM:
			printf("$temp[%u]", bpf->k);
			break;
		case BPF_IMM:
			printf("%u", bpf->k);
			break;
		case BPF_IND:
			printf("$data[X + %u]", bpf->k);
			break;
		case BPF_LEN:
			printf("len($data)");
			break;
		case BPF_MSH:
			printf("4 * $data[%u] & 0x0f", bpf->k);
			break;
		}
		break;
	case BPF_ST:
	case BPF_STX:
		printf("$temp[%u]", bpf->k);
		break;
	case BPF_ALU:
		if (BPF_SRC(bpf->code) == BPF_K) {
			switch (BPF_OP(bpf->code)) {
			case BPF_OR:
			case BPF_AND:
				printf("0x%.8x", bpf->k);
				break;
			default:
				printf("%u", bpf->k);
			}
		} else
			printf("%u", bpf->k);
		break;
	case BPF_JMP:
		if (BPF_OP(bpf->code) == BPF_JA) {
			printf("%.4u", (line + 1) + bpf->k);
		} else {
			printf("%-4u true:%.4u false:%.4u",
			       bpf->k,
			       (line + 1) + bpf->jt,
			       (line + 1) + bpf->jf);
		}
		break;
	case BPF_RET:
		if (BPF_RVAL(bpf->code) == BPF_A) {
			/* XXX - accumulator? */
			printf("$acc");
		} else if (BPF_SRC(bpf->code) == BPF_K) {
			bpf_decode_action(bpf->k);
		} else if (BPF_SRC(bpf->code) == BPF_X) {
			/* XXX - any idea? */
			printf("???");
		}
		break;
	case BPF_MISC:
		break;
	default:
		printf("???");
	}
}

/**
 * Perform a simple decoding of the BPF program
 * @param file the BPF program
 *
 * Read the BPF program and display the instructions.  Returns zero on success,
 * non-zero values on failure.
 *
 */
static int bpf_decode(FILE *file)
{
	unsigned int line = 0;
	bpf_instr_raw bpf;

	/* header */
	printf(" line  OP   JT   JF   K\n");
	printf("=================================\n");

	while (fread(&bpf, sizeof(bpf), 1, file)) {
		/* convert the bpf statement */
		bpf.code = ttoh16(arch, bpf.code);
		bpf.k = ttoh32(arch, bpf.k);

		/* display a hex dump */
		printf(" %.4u: 0x%.2x 0x%.2x 0x%.2x 0x%.8x",
		       line, bpf.code, bpf.jt, bpf.jf, bpf.k);

		/* display the assembler statements */
		printf("   ");
		printf(_OP_FMT, bpf_decode_op(&bpf));
		printf(" ");
		bpf_decode_args(&bpf, line);
		printf("\n");

		line++;
	}

	if (ferror(file))
		return errno;
	return 0;
}

/**
 * Decode the BPF arguments (JT, JF, and K)
 * @param bpf the BPF instruction
 * @param line the current line number
 *
 * Decode the BPF arguments (JT, JF, and K) and print the relevant information
 * to stdout based on the operand.
 *
 */
static void bpf_dot_decode_args(const bpf_instr_raw *bpf, unsigned int line)
{
	const char *op = bpf_decode_op(bpf);

	printf("\tline%d[label=\"%s", line, op);
	switch (BPF_CLASS(bpf->code)) {
	case BPF_LD:
	case BPF_LDX:
		switch (BPF_MODE(bpf->code)) {
		case BPF_ABS:
			printf(" $data[%u]\",shape=parallelogram]\n", bpf->k);
			break;
		case BPF_MEM:
			printf(" $temp[%u]\",shape=parallelogram]\n", bpf->k);
			break;
		case BPF_IMM:
			printf(" %u\",shape=parallelogram]\n", bpf->k);
			break;
		case BPF_IND:
			printf(" $data[X + %u]\",shape=parallelogram]\n", bpf->k);
			break;
		case BPF_LEN:
			printf(" len($data)\",shape=parallelogram]\n");
			break;
		case BPF_MSH:
			printf(" 4 * $data[%u] & 0x0f\",shape=parallelogram]\n", bpf->k);
			break;
		}
		break;
	case BPF_ST:
	case BPF_STX:
		printf(" $temp[%u]\",shape=parallelogram]\n",
		       bpf->k);
		break;
	case BPF_ALU:
		if (BPF_SRC(bpf->code) == BPF_K) {
			switch (BPF_OP(bpf->code)) {
			case BPF_OR:
			case BPF_AND:
				printf(" 0x%.8x\",shape=rectangle]\n", bpf->k);
				break;
			default:
				printf(" %u\",shape=rectangle]\n", bpf->k);
			}
		} else
			printf(" %u\",shape=rectangle]\n", bpf->k);
		break;
	case BPF_JMP:
		if (BPF_OP(bpf->code) == BPF_JA) {
			printf("\",shape=hexagon]\n");
			printf("\tline%d -> line%d\n",
			       line, (line + 1) + bpf->k);
		} else {
			printf(" %-4u", bpf->k);
			/* Heuristic: if k > 256, also emit hex version */
			if (bpf->k > 256)
				printf("\\n(0x%.8x)", bpf->k);
			printf("\",shape=diamond]\n");
			printf("\tline%d -> line%d [label=\"true\"]\n",
			       line, (line + 1) + bpf->jt);
			printf("\tline%d -> line%d [label=\"false\"]\n",
			       line, (line + 1) + bpf->jf);
		}
		break;
	case BPF_RET:
		if (BPF_RVAL(bpf->code) == BPF_A) {
			/* XXX - accumulator? */
			printf(" $acc\", shape=\"box\", style=rounded]\n");
		} else if (BPF_SRC(bpf->code) == BPF_K) {
			printf(" ");
			bpf_decode_action(bpf->k);
			printf("\", shape=\"box\", style=rounded]\n");
		} else if (BPF_SRC(bpf->code) == BPF_X) {
			/* XXX - any idea? */
			printf(" ???\", shape=\"box\", style=rounded]\n");
		}
		break;
	case BPF_MISC:
		printf("\"]\n");
		break;
	default:
		printf(" ???\"]\n");
	}
}

/**
 * Perform a simple decoding of the BPF program to a dot graph
 * @param file the BPF program
 *
 * Read the BPF program and display the instructions.  Returns zero on success,
 * non-zero values on failure.
 *
 */
static int bpf_dot_decode(FILE *file)
{
	unsigned int line = 0;
	bpf_instr_raw bpf;
	int prev_class = 0;

	/* header */
	printf("digraph {\n");
	printf("\tstart[shape=\"box\", style=rounded];\n");

	while (fread(&bpf, sizeof(bpf), 1, file)) {
		/* convert the bpf statement */
		bpf.code = ttoh16(arch, bpf.code);
		bpf.k = ttoh32(arch, bpf.k);

		/* display the statement */
		bpf_dot_decode_args(&bpf, line);

		/* if previous line wasn't RET/JMP, link it to this line */
		if (line == 0)
			printf("\tstart -> line%d\n", line);
		else if ((prev_class != BPF_JMP) && (prev_class != BPF_RET))
			printf("\tline%d -> line%d\n", line - 1, line);
		prev_class = BPF_CLASS(bpf.code);

		line++;
	}
	printf("}\n");

	if (ferror(file))
		return errno;
	return 0;
}

/**
 * main
 */
int main(int argc, char *argv[])
{
	int rc;
	int opt;
	bool dot_out = false;
	FILE *file;

	/* parse the command line */
	while ((opt = getopt(argc, argv, "a:dh")) > 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, "ppc64") == 0)
				arch = AUDIT_ARCH_PPC64;
			else if (strcmp(optarg, "ppc64le") == 0)
				arch = AUDIT_ARCH_PPC64LE;
			else if (strcmp(optarg, "ppc") == 0)
				arch = AUDIT_ARCH_PPC;
			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_usage(argv[0]);
			break;
		case 'd':
			dot_out = true;
			break;
		default:
			/* usage information */
			exit_usage(argv[0]);
		}
	}

	if ((optind > 1) && (optind < argc)) {
		int opt_file = optind - 1 ;
		file = fopen(argv[opt_file], "r");
		if (file == NULL) {
			fprintf(stderr, "error: unable to open \"%s\" (%s)\n",
				argv[opt_file], strerror(errno));
			return errno;
		}
	} else
		file = stdin;

	if (dot_out)
		rc = bpf_dot_decode(file);
	else
		rc = bpf_decode(file);
	fclose(file);

	return rc;
}
