2359 lines · 63.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only// Copyright (c) 2026 Meta Platforms, Inc. and affiliates. #include <linux/bpf.h>#include <linux/bpf_verifier.h>#include <linux/btf.h>#include <linux/ctype.h>#include <linux/kernel.h>#include <linux/list.h>#include <linux/seq_buf.h>#include <linux/overflow.h>#include <linux/slab.h>#include <linux/stdarg.h>#include <linux/string.h> #include "disasm.h"#include "diagnostics.h" #define REGISTER_TYPE_SAFETY "Register Type Safety"#define MEMORY_SAFETY "Memory Safety"#define RESOURCE_LIFETIME_SAFETY "Resource Lifetime Safety"#define CALL_TYPE_SAFETY "Call Type Safety"#define EXECUTION_CONTEXT_SAFETY "Execution Context Safety"#define PROGRAM_STRUCTURE "Program Structure"#define POLICY "Policy" #define BPF_DIAG_TEXT_WIDTH 100#define BPF_DIAG_TEXT_INDENT "  "#define BPF_DIAG_CONTEXT 2#define BPF_DIAG_CONTEXT_CNT (1 + BPF_DIAG_CONTEXT * 2)#define BPF_DIAG_HISTORY_RENDER_MAX 64#define BPF_DIAG_SOURCE_LANE_WIDTH 88#define BPF_DIAG_TAB_WIDTH 8#define BPF_DIAG_FMT_CHUNK_SIZE (PAGE_SIZE - sizeof(struct diag_fmt_chunk))#define BPF_DIAG_FMT_BUF_SIZE 256#define BPF_DIAG_EVENT_LOG_MAX_SIZE (64U << 20)#define DISASM_LINE_LEN 160 enum bpf_diag_mod_target_kind {	BPF_DIAG_MOD_TARGET_NONE,	BPF_DIAG_MOD_TARGET_REG,	BPF_DIAG_MOD_TARGET_STACK_ARG,	BPF_DIAG_MOD_TARGET_STACK_SLOT,	BPF_DIAG_MOD_TARGET_STACK_RANGE,}; struct bpf_diag_mod_target {	u32 frame_id;	union {		struct {			s16 min_off;			s16 max_off;		} range;		u16 spi;		u8 regno;		u8 stack_arg;	};	u8 frameno;	u8 kind;}; static struct bpf_diag_mod_target diag_reg_target(u32 frame_id, u8 frameno, u8 regno){	return (struct bpf_diag_mod_target){		.frame_id = frame_id,		.frameno = frameno,		.kind = BPF_DIAG_MOD_TARGET_REG,		.regno = regno,	};} static struct bpf_diag_mod_target diag_stack_arg_target(u32 frame_id, u8 frameno, u8 slot){	return (struct bpf_diag_mod_target){		.frame_id = frame_id,		.frameno = frameno,		.kind = BPF_DIAG_MOD_TARGET_STACK_ARG,		.stack_arg = slot,	};} static struct bpf_diag_mod_target diag_stack_slot_target(u32 frame_id, u8 frameno, u16 spi){	return (struct bpf_diag_mod_target){		.frame_id = frame_id,		.frameno = frameno,		.kind = BPF_DIAG_MOD_TARGET_STACK_SLOT,		.spi = spi,	};} static struct bpf_diag_mod_target diag_stack_range_target(u32 frame_id, u8 frameno,							  s16 min_off, s16 max_off){	return (struct bpf_diag_mod_target){		.frame_id = frame_id,		.frameno = frameno,		.kind = BPF_DIAG_MOD_TARGET_STACK_RANGE,		.range.min_off = min_off,		.range.max_off = max_off,	};} struct bpf_diag_reg_snapshot {	u32 type;	u32 btf_id;	const struct bpf_map *map_ptr;	const struct btf *btf;	struct tnum var_off;	struct cnum64 r64;}; enum bpf_diag_history_kind {	BPF_DIAG_HISTORY_BRANCH,	BPF_DIAG_HISTORY_MOD,	BPF_DIAG_HISTORY_REF_ACQUIRE,	BPF_DIAG_HISTORY_REF_RELEASE,	BPF_DIAG_HISTORY_CONTEXT,}; struct bpf_diag_history_event {	u32 insn_idx : 24;	u32 kind : 8;	u8 in_lineage : 1;	union {		struct {			bool cond_true;		} branch;		struct {			struct bpf_diag_mod_target target;			struct bpf_diag_mod_target origin;			struct bpf_diag_reg_snapshot old, new;			u8 reason;			bool origin_valid;		} mod;		struct {			u32 ref_id;		} ref;		struct {			u32 depth;			u8 kind;			bool enter;		} ctx;	};}; enum bpf_diag_history_scope {	BPF_DIAG_HISTORY_SCOPE_REG,	BPF_DIAG_HISTORY_SCOPE_STACK_ARG,	BPF_DIAG_HISTORY_SCOPE_REF,	BPF_DIAG_HISTORY_SCOPE_CONTEXT,}; struct bpf_diag_history_opts {	enum bpf_diag_history_scope scope;	u32 frame_id;	u32 frameno;	int regno;	int stack_arg_slot;	u32 ref_id;	enum bpf_diag_context_kind ctx_kind;	u32 ctx_depth;}; static void diag_print_history(struct bpf_verifier_env *env,			       const struct bpf_diag_history_opts *opts);static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,				const struct bpf_diag_mod_target *target);static const char *diag_context_name(enum bpf_diag_context_kind kind);struct disasm_line {	char text[DISASM_LINE_LEN];	int idx;	bool valid;}; struct disasm_ctx {	struct bpf_verifier_env *env;	struct seq_buf seq;}; struct diag_fmt_chunk {	struct list_head node;	struct seq_buf seq;	char data[];}; struct diag_fmt_mark {	struct diag_fmt_chunk *chunk;	size_t len;}; struct bpf_diag_log {	struct bpf_diag_history_event *events;	/* Sequence number of the oldest retained event on the active path. */	u64 first_seq;	u32 cnt;	u32 cap;	u32 head;	bool growth_failed;}; struct bpf_diag_scratch {	struct bpf_linfo_source source_lines[BPF_DIAG_CONTEXT_CNT];	struct disasm_line disasm_lines[BPF_DIAG_CONTEXT_CNT];}; struct bpf_diag_mod_scope {	struct bpf_reg_state target_reg_snapshot;	struct bpf_diag_mod_target target;	struct bpf_diag_mod_target origin;	enum bpf_diag_mod_reason reason;	u32 insn_idx;	bool active;	bool origin_valid;}; struct bpf_diag {	struct bpf_diag_log log;	struct bpf_diag_scratch scratch;	struct list_head fmt_chunks;	struct bpf_diag_mod_scope mod;	u32 frame_id_gen;}; bool bpf_diag_enabled(const struct bpf_verifier_env *env){	return env->log.level & BPF_LOG_LEVEL;} static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3); int bpf_diag_init(struct bpf_verifier_env *env){	if (!bpf_diag_enabled(env))		return 0; 	env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);	if (!env->diag)		return -ENOMEM; 	INIT_LIST_HEAD(&env->diag->fmt_chunks);	return 0;} void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state){	if (env->diag)		state->diag_frame_id = ++env->diag->frame_id_gen;} static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size){	struct bpf_diag *diag = env->diag;	struct diag_fmt_chunk *chunk;	size_t capacity, available;	char *buf; 	if (!diag || !size || size > INT_MAX)		return NULL; 	if (!list_empty(&diag->fmt_chunks)) {		chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);		available = seq_buf_get_buf(&chunk->seq, &buf);		if (available >= size)			goto commit;	} 	capacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size);	chunk = kmalloc_flex(*chunk, data, capacity, GFP_KERNEL_ACCOUNT);	if (!chunk)		return NULL; 	seq_buf_init(&chunk->seq, chunk->data, capacity);	list_add_tail(&chunk->node, &diag->fmt_chunks);	available = seq_buf_get_buf(&chunk->seq, &buf);	if (WARN_ON_ONCE(available < size))		return NULL; commit:	seq_buf_commit(&chunk->seq, size);	return buf;} char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size){	char *buf; 	buf = diag_fmt_alloc(env, size);	if (buf)		buf[0] = '\0';	return buf;} const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args){	va_list copy;	char *buf;	int len; 	va_copy(copy, args);	len = vsnprintf(NULL, 0, fmt, copy);	va_end(copy);	if (len < 0 || len == INT_MAX)		return ""; 	buf = diag_fmt_alloc(env, len + 1);	if (buf)		vsnprintf(buf, len + 1, fmt, args);	return buf ?: "";} const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...){	const char *buf;	va_list args; 	va_start(args, fmt);	buf = bpf_diag_vfmt(env, fmt, args);	va_end(args);	return buf;} static struct diag_fmt_mark diag_fmt_save(struct bpf_verifier_env *env){	struct bpf_diag *diag = env->diag;	struct diag_fmt_mark mark = {}; 	if (!diag || list_empty(&diag->fmt_chunks))		return mark; 	mark.chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);	mark.len = mark.chunk->seq.len;	return mark;} static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark mark){	struct bpf_diag *diag = env->diag;	struct diag_fmt_chunk *chunk; 	if (!diag)		return; 	while (!list_empty(&diag->fmt_chunks)) {		chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);		if (chunk == mark.chunk)			break;		list_del(&chunk->node);		kfree(chunk);	} 	if (mark.chunk) {		mark.chunk->seq.len = mark.len;		seq_buf_str(&mark.chunk->seq);	}} void bpf_diag_free(struct bpf_verifier_env *env){	struct bpf_diag *diag = env->diag; 	if (!diag)		return; 	diag_fmt_restore(env, (struct diag_fmt_mark){});	kvfree(diag->log.events);	kfree(diag);	env->diag = NULL;} static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...){	va_list args; 	if (!bpf_diag_enabled(env))		return; 	va_start(args, fmt);	bpf_verifier_vlog(&env->log, fmt, args);	va_end(args);} static u64 log_end(const struct bpf_diag_log *log){	return log->first_seq + log->cnt;} static u32 log_pos(const struct bpf_diag_log *log, u32 idx){	u32 pos = log->head + idx; 	return pos < log->cap ? pos : pos - log->cap;} u64 bpf_diag_event_log_save(struct bpf_verifier_env *env){	struct bpf_diag *diag = env->diag; 	return diag ? log_end(&diag->log) : 0;} void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos){	struct bpf_diag *diag = env->diag;	struct bpf_diag_log *log;	u64 end_seq; 	if (!diag)		return; 	log = &diag->log;	end_seq = log_end(log);	if (WARN_ON_ONCE(log_pos > end_seq))		log_pos = end_seq; 	/*	 * A deep abandoned path may have rotated away the shared prefix. In	 * that case, restart with an empty retained suffix and remember that	 * every event before the restored mark is unavailable.	 */	if (log_pos <= log->first_seq) {		log->first_seq = log_pos;		log->head = 0;		log->cnt = 0;		return;	} 	log->cnt = log_pos - log->first_seq;} u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state){	u32 depth = 0;	int i; 	for (i = 0; i < state->acquired_refs; i++) {		if (state->refs[i].type == REF_TYPE_IRQ)			depth++;	} 	return depth;} static void diag_append_history(struct bpf_verifier_env *env,				const struct bpf_diag_history_event *event){	struct bpf_diag_history_event *events;	struct bpf_diag *diag = env->diag;	struct bpf_diag_log *log;	u32 cap, max_events; 	if (!diag)		return;	log = &diag->log; 	if (log->cnt < log->cap) {		log->events[log_pos(log, log->cnt++)] = *event;		return;	} 	max_events = BPF_DIAG_EVENT_LOG_MAX_SIZE / sizeof(*events);	if (log->growth_failed || log->cap == max_events)		goto rotate; 	cap = min(log->cap ? log->cap * 2 : 64, max_events);	events = kvrealloc(log->events, array_size(cap, sizeof(*events)), GFP_KERNEL_ACCOUNT);	if (!events) {		log->growth_failed = true;		goto rotate;	}	log->events = events;	log->cap = cap;	log->events[log->cnt++] = *event;	return; rotate:	if (log->cap) {		log->events[log->head++] = *event;		if (log->head == log->cap)			log->head = 0;	}	log->first_seq++;} static void diag_print_wrapped_prefixed(struct bpf_verifier_env *env, const char *first_prefix,					const char *next_prefix, const char *text){	const char *prefix = first_prefix; 	while (*text) {		const char *line = text;		int prefix_len = strlen(prefix);		int text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;		int len = 0, last_space = -1; 		if (text_width < 1)			text_width = 1; 		while (line[len] && line[len] != '\n' && len < text_width) {			if (line[len] == ' ')

Lines 1–500 of 2359.