/**
* 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,
* negative values on failure.
*
*/
static int bpf_decode(FILE *file)
{
unsigned int line = 0;
size_t len;
bpf_instr_raw bpf;
/* header */
printf(" line OP JT JF K\n");
printf("=================================\n");
while ((len = 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,
* negative values on failure.
*
*/
static int bpf_dot_decode(FILE *file)
{
unsigned int line = 0;
size_t len;
bpf_instr_raw bpf;
int prev_class = 0;
/* header */
printf("digraph {\n");
printf("\tstart[shape=\"box\", style=rounded];\n");
while ((len = 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
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;
}