cregit-Linux how code gets into the kernel

Release 4.14 tools/net/bpf_dbg.c

Directory: tools/net
/*
 * Minimal BPF debugger
 *
 * Minimal BPF debugger that mimics the kernel's engine (w/o extensions)
 * and allows for single stepping through selected packets from a pcap
 * with a provided user filter in order to facilitate verification of a
 * BPF program. Besides others, this is useful to verify BPF programs
 * before attaching to a live system, and can be used in socket filters,
 * cls_bpf, xt_bpf, team driver and e.g. PTP code; in particular when a
 * single more complex BPF program is being used. Reasons for a more
 * complex BPF program are likely primarily to optimize execution time
 * for making a verdict when multiple simple BPF programs are combined
 * into one in order to prevent parsing same headers multiple times.
 *
 * More on how to debug BPF opcodes see Documentation/networking/filter.txt
 * which is the main document on BPF. Mini howto for getting started:
 *
 *  1) `./bpf_dbg` to enter the shell (shell cmds denoted with '>'):
 *  2) > load bpf 6,40 0 0 12,21 0 3 20... (output from `bpf_asm` or
 *     `tcpdump -iem1 -ddd port 22 | tr '\n' ','` to load as filter)
 *  3) > load pcap foo.pcap
 *  4) > run <n>/disassemble/dump/quit (self-explanatory)
 *  5) > breakpoint 2 (sets bp at loaded BPF insns 2, do `run` then;
 *       multiple bps can be set, of course, a call to `breakpoint`
 *       w/o args shows currently loaded bps, `breakpoint reset` for
 *       resetting all breakpoints)
 *  6) > select 3 (`run` etc will start from the 3rd packet in the pcap)
 *  7) > step [-<n>, +<n>] (performs single stepping through the BPF)
 *
 * Copyright 2013 Daniel Borkmann <borkmann@redhat.com>
 * Licensed under the GNU General Public License, version 2.0 (GPLv2)
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdarg.h>
#include <setjmp.h>
#include <linux/filter.h>
#include <linux/if_packet.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#include <net/ethernet.h>


#define TCPDUMP_MAGIC	0xa1b2c3d4


#define BPF_LDX_B	(BPF_LDX | BPF_B)

#define BPF_LDX_W	(BPF_LDX | BPF_W)

#define BPF_JMP_JA	(BPF_JMP | BPF_JA)

#define BPF_JMP_JEQ	(BPF_JMP | BPF_JEQ)

#define BPF_JMP_JGT	(BPF_JMP | BPF_JGT)

#define BPF_JMP_JGE	(BPF_JMP | BPF_JGE)

#define BPF_JMP_JSET	(BPF_JMP | BPF_JSET)

#define BPF_ALU_ADD	(BPF_ALU | BPF_ADD)

#define BPF_ALU_SUB	(BPF_ALU | BPF_SUB)

#define BPF_ALU_MUL	(BPF_ALU | BPF_MUL)

#define BPF_ALU_DIV	(BPF_ALU | BPF_DIV)

#define BPF_ALU_MOD	(BPF_ALU | BPF_MOD)

#define BPF_ALU_NEG	(BPF_ALU | BPF_NEG)

#define BPF_ALU_AND	(BPF_ALU | BPF_AND)

#define BPF_ALU_OR	(BPF_ALU | BPF_OR)

#define BPF_ALU_XOR	(BPF_ALU | BPF_XOR)

#define BPF_ALU_LSH	(BPF_ALU | BPF_LSH)

#define BPF_ALU_RSH	(BPF_ALU | BPF_RSH)

#define BPF_MISC_TAX	(BPF_MISC | BPF_TAX)

#define BPF_MISC_TXA	(BPF_MISC | BPF_TXA)

#define BPF_LD_B	(BPF_LD | BPF_B)

#define BPF_LD_H	(BPF_LD | BPF_H)

#define BPF_LD_W	(BPF_LD | BPF_W)

#ifndef array_size

# define array_size(x)	(sizeof(x) / sizeof((x)[0]))
#endif

#ifndef __check_format_printf

# define __check_format_printf(pos_fmtstr, pos_fmtargs) \
	__attribute__ ((format (printf, (pos_fmtstr), (pos_fmtargs))))
#endif

enum {
	
CMD_OK,
	
CMD_ERR,
	
CMD_EX,
};


struct shell_cmd {
	
const char *name;
	
int (*func)(char *args);
};


struct pcap_filehdr {
	
uint32_t magic;
	
uint16_t version_major;
	
uint16_t version_minor;
	
int32_t  thiszone;
	
uint32_t sigfigs;
	
uint32_t snaplen;
	
uint32_t linktype;
};


struct pcap_timeval {
	
int32_t tv_sec;
	
int32_t tv_usec;
};


struct pcap_pkthdr {
	
struct pcap_timeval ts;
	
uint32_t caplen;
	
uint32_t len;
};


struct bpf_regs {
	
uint32_t A;
	
uint32_t X;
	
uint32_t M[BPF_MEMWORDS];
	
uint32_t R;
	
bool     Rs;
	
uint16_t Pc;
};


static struct sock_filter bpf_image[BPF_MAXINSNS + 1];

static unsigned int bpf_prog_len;


static int bpf_breakpoints[64];

static struct bpf_regs bpf_regs[BPF_MAXINSNS + 1];

static struct bpf_regs bpf_curr;

static unsigned int bpf_regs_len;


static int pcap_fd = -1;

static unsigned int pcap_packet;

static size_t pcap_map_size;


static char *pcap_ptr_va_start, *pcap_ptr_va_curr;


static const char * const op_table[] = {
	[BPF_ST]	= "st",
	[BPF_STX]	= "stx",
	[BPF_LD_B]	= "ldb",
	[BPF_LD_H]	= "ldh",
	[BPF_LD_W]	= "ld",
	[BPF_LDX]	= "ldx",
	[BPF_LDX_B]	= "ldxb",
	[BPF_JMP_JA]	= "ja",
	[BPF_JMP_JEQ]	= "jeq",
	[BPF_JMP_JGT]	= "jgt",
	[BPF_JMP_JGE]	= "jge",
	[BPF_JMP_JSET]	= "jset",
	[BPF_ALU_ADD]	= "add",
	[BPF_ALU_SUB]	= "sub",
	[BPF_ALU_MUL]	= "mul",
	[BPF_ALU_DIV]	= "div",
	[BPF_ALU_MOD]	= "mod",
	[BPF_ALU_NEG]	= "neg",
	[BPF_ALU_AND]	= "and",
	[BPF_ALU_OR]	= "or",
	[BPF_ALU_XOR]	= "xor",
	[BPF_ALU_LSH]	= "lsh",
	[BPF_ALU_RSH]	= "rsh",
	[BPF_MISC_TAX]	= "tax",
	[BPF_MISC_TXA]	= "txa",
	[BPF_RET]	= "ret",
};


static __check_format_printf(1, 2) int rl_printf(const char *fmt, ...)
{
	int ret;
	va_list vl;

	va_start(vl, fmt);
	ret = vfprintf(rl_outstream, fmt, vl);
	va_end(vl);

	return ret;
}


static int matches(const char *cmd, const char *pattern) { int len = strlen(cmd); if (len > strlen(pattern)) return -1; return memcmp(pattern, cmd, len); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann47100.00%1100.00%
Total47100.00%1100.00%


static void hex_dump(const uint8_t *buf, size_t len) { int i; rl_printf("%3u: ", 0); for (i = 0; i < len; i++) { if (i && !(i % 16)) rl_printf("\n%3u: ", i); rl_printf("%02x ", buf[i]); } rl_printf("\n"); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann72100.00%1100.00%
Total72100.00%1100.00%


static bool bpf_prog_loaded(void) { if (bpf_prog_len == 0) rl_printf("no bpf program loaded!\n"); return bpf_prog_len > 0; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann24100.00%1100.00%
Total24100.00%1100.00%


static void bpf_disasm(const struct sock_filter f, unsigned int i) { const char *op, *fmt; int val = f.k; char buf[256]; switch (f.code) { case BPF_RET | BPF_K: op = op_table[BPF_RET]; fmt = "#%#x"; break; case BPF_RET | BPF_A: op = op_table[BPF_RET]; fmt = "a"; break; case BPF_RET | BPF_X: op = op_table[BPF_RET]; fmt = "x"; break; case BPF_MISC_TAX: op = op_table[BPF_MISC_TAX]; fmt = ""; break; case BPF_MISC_TXA: op = op_table[BPF_MISC_TXA]; fmt = ""; break; case BPF_ST: op = op_table[BPF_ST]; fmt = "M[%d]"; break; case BPF_STX: op = op_table[BPF_STX]; fmt = "M[%d]"; break; case BPF_LD_W | BPF_ABS: op = op_table[BPF_LD_W]; fmt = "[%d]"; break; case BPF_LD_H | BPF_ABS: op = op_table[BPF_LD_H]; fmt = "[%d]"; break; case BPF_LD_B | BPF_ABS: op = op_table[BPF_LD_B]; fmt = "[%d]"; break; case BPF_LD_W | BPF_LEN: op = op_table[BPF_LD_W]; fmt = "#len"; break; case BPF_LD_W | BPF_IND: op = op_table[BPF_LD_W]; fmt = "[x+%d]"; break; case BPF_LD_H | BPF_IND: op = op_table[BPF_LD_H]; fmt = "[x+%d]"; break; case BPF_LD_B | BPF_IND: op = op_table[BPF_LD_B]; fmt = "[x+%d]"; break; case BPF_LD | BPF_IMM: op = op_table[BPF_LD_W]; fmt = "#%#x"; break; case BPF_LDX | BPF_IMM: op = op_table[BPF_LDX]; fmt = "#%#x"; break; case BPF_LDX_B | BPF_MSH: op = op_table[BPF_LDX_B]; fmt = "4*([%d]&0xf)"; break; case BPF_LD | BPF_MEM: op = op_table[BPF_LD_W]; fmt = "M[%d]"; break; case BPF_LDX | BPF_MEM: op = op_table[BPF_LDX]; fmt = "M[%d]"; break; case BPF_JMP_JA: op = op_table[BPF_JMP_JA]; fmt = "%d"; val = i + 1 + f.k; break; case BPF_JMP_JGT | BPF_X: op = op_table[BPF_JMP_JGT]; fmt = "x"; break; case BPF_JMP_JGT | BPF_K: op = op_table[BPF_JMP_JGT]; fmt = "#%#x"; break; case BPF_JMP_JGE | BPF_X: op = op_table[BPF_JMP_JGE]; fmt = "x"; break; case BPF_JMP_JGE | BPF_K: op = op_table[BPF_JMP_JGE]; fmt = "#%#x"; break; case BPF_JMP_JEQ | BPF_X: op = op_table[BPF_JMP_JEQ]; fmt = "x"; break; case BPF_JMP_JEQ | BPF_K: op = op_table[BPF_JMP_JEQ]; fmt = "#%#x"; break; case BPF_JMP_JSET | BPF_X: op = op_table[BPF_JMP_JSET]; fmt = "x"; break; case BPF_JMP_JSET | BPF_K: op = op_table[BPF_JMP_JSET]; fmt = "#%#x"; break; case BPF_ALU_NEG: op = op_table[BPF_ALU_NEG]; fmt = ""; break; case BPF_ALU_LSH | BPF_X: op = op_table[BPF_ALU_LSH]; fmt = "x"; break; case BPF_ALU_LSH | BPF_K: op = op_table[BPF_ALU_LSH]; fmt = "#%d"; break; case BPF_ALU_RSH | BPF_X: op = op_table[BPF_ALU_RSH]; fmt = "x"; break; case BPF_ALU_RSH | BPF_K: op = op_table[BPF_ALU_RSH]; fmt = "#%d"; break; case BPF_ALU_ADD | BPF_X: op = op_table[BPF_ALU_ADD]; fmt = "x"; break; case BPF_ALU_ADD | BPF_K: op = op_table[BPF_ALU_ADD]; fmt = "#%d"; break; case BPF_ALU_SUB | BPF_X: op = op_table[BPF_ALU_SUB]; fmt = "x"; break; case BPF_ALU_SUB | BPF_K: op = op_table[BPF_ALU_SUB]; fmt = "#%d"; break; case BPF_ALU_MUL | BPF_X: op = op_table[BPF_ALU_MUL]; fmt = "x"; break; case BPF_ALU_MUL | BPF_K: op = op_table[BPF_ALU_MUL]; fmt = "#%d"; break; case BPF_ALU_DIV | BPF_X: op = op_table[BPF_ALU_DIV]; fmt = "x"; break; case BPF_ALU_DIV | BPF_K: op = op_table[BPF_ALU_DIV]; fmt = "#%d"; break; case BPF_ALU_MOD | BPF_X: op = op_table[BPF_ALU_MOD]; fmt = "x"; break; case BPF_ALU_MOD | BPF_K: op = op_table[BPF_ALU_MOD]; fmt = "#%d"; break; case BPF_ALU_AND | BPF_X: op = op_table[BPF_ALU_AND]; fmt = "x"; break; case BPF_ALU_AND | BPF_K: op = op_table[BPF_ALU_AND]; fmt = "#%#x"; break; case BPF_ALU_OR | BPF_X: op = op_table[BPF_ALU_OR]; fmt = "x"; break; case BPF_ALU_OR | BPF_K: op = op_table[BPF_ALU_OR]; fmt = "#%#x"; break; case BPF_ALU_XOR | BPF_X: op = op_table[BPF_ALU_XOR]; fmt = "x"; break; case BPF_ALU_XOR | BPF_K: op = op_table[BPF_ALU_XOR]; fmt = "#%#x"; break; default: op = "nosup"; fmt = "%#x"; val = f.code; break; } memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), fmt, val); buf[sizeof(buf) - 1] = 0; if ((BPF_CLASS(f.code) == BPF_JMP && BPF_OP(f.code) != BPF_JA)) rl_printf("l%d:\t%s %s, l%d, l%d\n", i, op, buf, i + 1 + f.jt, i + 1 + f.jf); else rl_printf("l%d:\t%s %s\n", i, op, buf); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann990100.00%1100.00%
Total990100.00%1100.00%


static void bpf_dump_curr(struct bpf_regs *r, struct sock_filter *f) { int i, m = 0; rl_printf("pc: [%u]\n", r->Pc); rl_printf("code: [%u] jt[%u] jf[%u] k[%u]\n", f->code, f->jt, f->jf, f->k); rl_printf("curr: "); bpf_disasm(*f, r->Pc); if (f->jt || f->jf) { rl_printf("jt: "); bpf_disasm(*(f + f->jt + 1), r->Pc + f->jt + 1); rl_printf("jf: "); bpf_disasm(*(f + f->jf + 1), r->Pc + f->jf + 1); } rl_printf("A: [%#08x][%u]\n", r->A, r->A); rl_printf("X: [%#08x][%u]\n", r->X, r->X); if (r->Rs) rl_printf("ret: [%#08x][%u]!\n", r->R, r->R); for (i = 0; i < BPF_MEMWORDS; i++) { if (r->M[i]) { m++; rl_printf("M[%d]: [%#08x][%u]\n", i, r->M[i], r->M[i]); } } if (m == 0) rl_printf("M[0,%d]: [%#08x][%u]\n", BPF_MEMWORDS - 1, 0, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann252100.00%1100.00%
Total252100.00%1100.00%


static void bpf_dump_pkt(uint8_t *pkt, uint32_t pkt_caplen, uint32_t pkt_len) { if (pkt_caplen != pkt_len) rl_printf("cap: %u, len: %u\n", pkt_caplen, pkt_len); else rl_printf("len: %u\n", pkt_len); hex_dump(pkt, pkt_caplen); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann46100.00%1100.00%
Total46100.00%1100.00%


static void bpf_disasm_all(const struct sock_filter *f, unsigned int len) { unsigned int i; for (i = 0; i < len; i++) bpf_disasm(f[i], i); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann43100.00%1100.00%
Total43100.00%1100.00%


static void bpf_dump_all(const struct sock_filter *f, unsigned int len) { unsigned int i; rl_printf("/* { op, jt, jf, k }, */\n"); for (i = 0; i < len; i++) rl_printf("{ %#04x, %2u, %2u, %#010x },\n", f[i].code, f[i].jt, f[i].jf, f[i].k); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann71100.00%1100.00%
Total71100.00%1100.00%


static bool bpf_runnable(struct sock_filter *f, unsigned int len) { int sock, ret, i; struct sock_fprog bpf = { .filter = f, .len = len, }; sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { rl_printf("cannot open socket!\n"); return false; } ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); close(sock); if (ret < 0) { rl_printf("program not allowed to run by kernel!\n"); return false; } for (i = 0; i < len; i++) { if (BPF_CLASS(f[i].code) == BPF_LD && f[i].k > SKF_AD_OFF) { rl_printf("extensions currently not supported!\n"); return false; } } return true; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann156100.00%2100.00%
Total156100.00%2100.00%


static void bpf_reset_breakpoints(void) { int i; for (i = 0; i < array_size(bpf_breakpoints); i++) bpf_breakpoints[i] = -1; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann35100.00%1100.00%
Total35100.00%1100.00%


static void bpf_set_breakpoints(unsigned int where) { int i; bool set = false; for (i = 0; i < array_size(bpf_breakpoints); i++) { if (bpf_breakpoints[i] == (int) where) { rl_printf("breakpoint already set!\n"); set = true; break; } if (bpf_breakpoints[i] == -1 && set == false) { bpf_breakpoints[i] = where; set = true; } } if (!set) rl_printf("too many breakpoints set, reset first!\n"); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann97100.00%1100.00%
Total97100.00%1100.00%


static void bpf_dump_breakpoints(void) { int i; rl_printf("breakpoints: "); for (i = 0; i < array_size(bpf_breakpoints); i++) { if (bpf_breakpoints[i] < 0) continue; rl_printf("%d ", bpf_breakpoints[i]); } rl_printf("\n"); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann59100.00%1100.00%
Total59100.00%1100.00%


static void bpf_reset(void) { bpf_regs_len = 0; memset(bpf_regs, 0, sizeof(bpf_regs)); memset(&bpf_curr, 0, sizeof(bpf_curr)); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann37100.00%1100.00%
Total37100.00%1100.00%


static void bpf_safe_regs(void) { memcpy(&bpf_regs[bpf_regs_len++], &bpf_curr, sizeof(bpf_curr)); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann26100.00%1100.00%
Total26100.00%1100.00%


static bool bpf_restore_regs(int off) { unsigned int index = bpf_regs_len - 1 + off; if (index == 0) { bpf_reset(); return true; } else if (index < bpf_regs_len) { memcpy(&bpf_curr, &bpf_regs[index], sizeof(bpf_curr)); bpf_regs_len = index; return true; } else { rl_printf("reached bottom of register history stack!\n"); return false; } }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann77100.00%1100.00%
Total77100.00%1100.00%


static uint32_t extract_u32(uint8_t *pkt, uint32_t off) { uint32_t r; memcpy(&r, &pkt[off], sizeof(r)); return ntohl(r); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann39100.00%1100.00%
Total39100.00%1100.00%


static uint16_t extract_u16(uint8_t *pkt, uint32_t off) { uint16_t r; memcpy(&r, &pkt[off], sizeof(r)); return ntohs(r); }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann39100.00%1100.00%
Total39100.00%1100.00%


static uint8_t extract_u8(uint8_t *pkt, uint32_t off) { return pkt[off]; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann19100.00%1100.00%
Total19100.00%1100.00%


static void set_return(struct bpf_regs *r) { r->R = 0; r->Rs = true; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Borkmann23100.00%1100.00%
Total23100.00%1100.00%


static void bpf_single_step(struct bpf_regs *r, struct sock_filter *f, uint8_t *pkt, uint32_t pkt_caplen, uint32_t pkt_len) { uint32_t K = f->k; int d; switch (f->code) { case BPF_RET | BPF_K: r->R = K; r->Rs = true; break; case BPF_RET | BPF_A: r->R = r->A; r->Rs = true; break; case BPF_RET | BPF_X: r->R = r->X; r->Rs = true; break; case BPF_MISC_TAX: r->X = r->A; break; case BPF_MISC_TXA: r->A = r->X; break; case BPF_ST: r->M[K] = r->A; break; case BPF_STX: r->M[K] = r->X; break; case BPF_LD_W | BPF_ABS: d = pkt_caplen - K; if (d >= sizeof(uint32_t)) r->A = extract_u32(pkt, K); else set_return(r); break; case BPF_LD_H | BPF_ABS: d = pkt_caplen - K; if (d >= sizeof(uint16_t)) r->A = extract_u16(pkt, K); else set_return(r); break; case BPF_LD_B | BPF_ABS: d = pkt_caplen - K; if (d >= sizeof(uint8_t)) r->A = extract_u8(pkt, K); else set_return(r); break; case BPF_LD_W | BPF_IND: d = pkt_caplen - (r->X + K); if (d >= sizeof(uint32_t)) r->A = extract_u32(pkt, r->X + K)