/** * BPF Simulator * * Copyright (c) 2012 Red Hat * Author: Paul Moore */ /* * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #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 [-v] [-h]" " -a -s [-0 ] ... [-5 ]\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, "loongarch64") == 0) arch = AUDIT_ARCH_LOONGARCH64; else if (strcmp(optarg, "m68k") == 0) arch = AUDIT_ARCH_M68K; 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 if (strcmp(optarg, "sheb") == 0) arch = AUDIT_ARCH_SH; else if (strcmp(optarg, "sh") == 0) arch = AUDIT_ARCH_SHEL; 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 endianness 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; }