xref: /linux/kernel/bpf/diagnostics.c (revision daf8248701b621d8df7ea134793dbb750d4887c0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 // Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
3 
4 #include <linux/bpf.h>
5 #include <linux/bpf_verifier.h>
6 #include <linux/btf.h>
7 #include <linux/ctype.h>
8 #include <linux/kernel.h>
9 #include <linux/list.h>
10 #include <linux/seq_buf.h>
11 #include <linux/slab.h>
12 #include <linux/stdarg.h>
13 #include <linux/string.h>
14 
15 #include "disasm.h"
16 #include "diagnostics.h"
17 
18 #define BPF_DIAG_TEXT_WIDTH 100
19 #define BPF_DIAG_CONTEXT 2
20 #define BPF_DIAG_CONTEXT_CNT (1 + BPF_DIAG_CONTEXT * 2)
21 #define BPF_DIAG_SOURCE_LANE_WIDTH 88
22 #define BPF_DIAG_TAB_WIDTH 8
23 #define BPF_DIAG_FMT_CHUNK_SIZE (PAGE_SIZE - sizeof(struct diag_fmt_chunk))
24 #define BPF_DIAG_FMT_BUF_SIZE 256
25 #define BPF_DIAG_EVENT_LOG_MAX_SIZE (64U << 20)
26 #define DISASM_LINE_LEN 160
27 
28 enum bpf_diag_history_kind {
29 	BPF_DIAG_HISTORY_BRANCH,
30 };
31 
32 struct bpf_diag_history_event {
33 	u32 insn_idx : 24;
34 	u32 kind : 8;
35 	u8 in_lineage : 1;
36 	union {
37 		struct {
38 			bool cond_true;
39 		} branch;
40 	};
41 };
42 
43 struct disasm_line {
44 	char text[DISASM_LINE_LEN];
45 	int idx;
46 	bool valid;
47 };
48 
49 struct disasm_ctx {
50 	struct bpf_verifier_env *env;
51 	struct seq_buf seq;
52 };
53 
54 struct diag_fmt_chunk {
55 	struct list_head node;
56 	struct seq_buf seq;
57 	char data[];
58 };
59 
60 struct diag_fmt_mark {
61 	struct diag_fmt_chunk *chunk;
62 	size_t len;
63 };
64 
65 struct bpf_diag_log {
66 	struct bpf_diag_history_event *events;
67 	/* Sequence number of the oldest retained event on the active path. */
68 	u64 first_seq;
69 	u32 cnt;
70 	u32 cap;
71 	u32 head;
72 	bool growth_failed;
73 };
74 
75 struct bpf_diag_scratch {
76 	struct bpf_linfo_source source_lines[BPF_DIAG_CONTEXT_CNT];
77 	struct disasm_line disasm_lines[BPF_DIAG_CONTEXT_CNT];
78 };
79 
80 struct bpf_diag {
81 	struct bpf_diag_log log;
82 	struct bpf_diag_scratch scratch;
83 	struct list_head fmt_chunks;
84 };
85 
86 bool bpf_diag_enabled(const struct bpf_verifier_env *env)
87 {
88 	return env->log.level & BPF_LOG_LEVEL;
89 }
90 
91 static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
92 
93 int bpf_diag_init(struct bpf_verifier_env *env)
94 {
95 	if (!bpf_diag_enabled(env))
96 		return 0;
97 
98 	env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);
99 	if (!env->diag)
100 		return -ENOMEM;
101 
102 	INIT_LIST_HEAD(&env->diag->fmt_chunks);
103 	return 0;
104 }
105 
106 static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size)
107 {
108 	struct bpf_diag *diag = env->diag;
109 	struct diag_fmt_chunk *chunk;
110 	size_t capacity, available;
111 	char *buf;
112 
113 	if (!diag || !size || size > INT_MAX)
114 		return NULL;
115 
116 	if (!list_empty(&diag->fmt_chunks)) {
117 		chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
118 		available = seq_buf_get_buf(&chunk->seq, &buf);
119 		if (available >= size)
120 			goto commit;
121 	}
122 
123 	capacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size);
124 	chunk = kmalloc(struct_size(chunk, data, capacity), GFP_KERNEL_ACCOUNT);
125 	if (!chunk)
126 		return NULL;
127 
128 	seq_buf_init(&chunk->seq, chunk->data, capacity);
129 	list_add_tail(&chunk->node, &diag->fmt_chunks);
130 	available = seq_buf_get_buf(&chunk->seq, &buf);
131 	if (WARN_ON_ONCE(available < size))
132 		return NULL;
133 
134 commit:
135 	seq_buf_commit(&chunk->seq, size);
136 	return buf;
137 }
138 
139 char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size)
140 {
141 	char *buf;
142 
143 	buf = diag_fmt_alloc(env, size);
144 	if (buf)
145 		buf[0] = '\0';
146 	return buf;
147 }
148 
149 const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
150 {
151 	va_list copy;
152 	char *buf;
153 	int len;
154 
155 	va_copy(copy, args);
156 	len = vsnprintf(NULL, 0, fmt, copy);
157 	va_end(copy);
158 	if (len < 0 || len == INT_MAX)
159 		return "";
160 
161 	buf = diag_fmt_alloc(env, len + 1);
162 	if (buf)
163 		vsnprintf(buf, len + 1, fmt, args);
164 	return buf ?: "";
165 }
166 
167 const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...)
168 {
169 	const char *buf;
170 	va_list args;
171 
172 	va_start(args, fmt);
173 	buf = bpf_diag_vfmt(env, fmt, args);
174 	va_end(args);
175 	return buf;
176 }
177 
178 static struct diag_fmt_mark diag_fmt_save(struct bpf_verifier_env *env)
179 {
180 	struct bpf_diag *diag = env->diag;
181 	struct diag_fmt_mark mark = {};
182 
183 	if (!diag || list_empty(&diag->fmt_chunks))
184 		return mark;
185 
186 	mark.chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
187 	mark.len = mark.chunk->seq.len;
188 	return mark;
189 }
190 
191 static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark mark)
192 {
193 	struct bpf_diag *diag = env->diag;
194 	struct diag_fmt_chunk *chunk;
195 
196 	if (!diag)
197 		return;
198 
199 	while (!list_empty(&diag->fmt_chunks)) {
200 		chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
201 		if (chunk == mark.chunk)
202 			break;
203 		list_del(&chunk->node);
204 		kfree(chunk);
205 	}
206 
207 	if (mark.chunk) {
208 		mark.chunk->seq.len = mark.len;
209 		seq_buf_str(&mark.chunk->seq);
210 	}
211 }
212 
213 void bpf_diag_free(struct bpf_verifier_env *env)
214 {
215 	struct bpf_diag *diag = env->diag;
216 
217 	if (!diag)
218 		return;
219 
220 	diag_fmt_restore(env, (struct diag_fmt_mark){});
221 	kvfree(diag->log.events);
222 	kfree(diag);
223 	env->diag = NULL;
224 }
225 
226 static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...)
227 {
228 	va_list args;
229 
230 	if (!bpf_diag_enabled(env))
231 		return;
232 
233 	va_start(args, fmt);
234 	bpf_verifier_vlog(&env->log, fmt, args);
235 	va_end(args);
236 }
237 
238 static u64 log_end(const struct bpf_diag_log *log)
239 {
240 	return log->first_seq + log->cnt;
241 }
242 
243 static u32 log_pos(const struct bpf_diag_log *log, u32 idx)
244 {
245 	u32 pos = log->head + idx;
246 
247 	return pos < log->cap ? pos : pos - log->cap;
248 }
249 
250 u64 bpf_diag_event_log_save(struct bpf_verifier_env *env)
251 {
252 	struct bpf_diag *diag = env->diag;
253 
254 	return diag ? log_end(&diag->log) : 0;
255 }
256 
257 void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos)
258 {
259 	struct bpf_diag *diag = env->diag;
260 	struct bpf_diag_log *log;
261 	u64 end_seq;
262 
263 	if (!diag)
264 		return;
265 
266 	log = &diag->log;
267 	end_seq = log_end(log);
268 	if (WARN_ON_ONCE(log_pos > end_seq))
269 		log_pos = end_seq;
270 
271 	/*
272 	 * A deep abandoned path may have rotated away the shared prefix. In
273 	 * that case, restart with an empty retained suffix and remember that
274 	 * every event before the restored mark is unavailable.
275 	 */
276 	if (log_pos <= log->first_seq) {
277 		log->first_seq = log_pos;
278 		log->head = 0;
279 		log->cnt = 0;
280 		return;
281 	}
282 
283 	log->cnt = log_pos - log->first_seq;
284 }
285 
286 static void diag_append_history(struct bpf_verifier_env *env,
287 				const struct bpf_diag_history_event *event)
288 {
289 	struct bpf_diag_history_event *events;
290 	struct bpf_diag *diag = env->diag;
291 	struct bpf_diag_log *log;
292 	u32 cap, max_events;
293 
294 	if (!diag)
295 		return;
296 	log = &diag->log;
297 
298 	if (log->cnt < log->cap) {
299 		log->events[log_pos(log, log->cnt++)] = *event;
300 		return;
301 	}
302 
303 	max_events = BPF_DIAG_EVENT_LOG_MAX_SIZE / sizeof(*events);
304 	if (log->growth_failed || log->cap == max_events)
305 		goto rotate;
306 
307 	cap = min(log->cap ? log->cap * 2 : 64, max_events);
308 	events = kvrealloc(log->events, array_size(cap, sizeof(*events)), GFP_KERNEL_ACCOUNT);
309 	if (!events) {
310 		log->growth_failed = true;
311 		goto rotate;
312 	}
313 	log->events = events;
314 	log->cap = cap;
315 	log->events[log->cnt++] = *event;
316 	return;
317 
318 rotate:
319 	if (log->cap) {
320 		log->events[log->head++] = *event;
321 		if (log->head == log->cap)
322 			log->head = 0;
323 	}
324 	log->first_seq++;
325 }
326 
327 static void diag_print_wrapped_prefixed(struct bpf_verifier_env *env, const char *first_prefix,
328 					const char *next_prefix, const char *text)
329 {
330 	const char *prefix = first_prefix;
331 
332 	while (*text) {
333 		const char *line = text;
334 		int prefix_len = strlen(prefix);
335 		int text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;
336 		int len = 0, last_space = -1;
337 
338 		if (text_width < 1)
339 			text_width = 1;
340 
341 		while (line[len] && line[len] != '\n' && len < text_width) {
342 			if (line[len] == ' ')
343 				last_space = len;
344 			len++;
345 		}
346 
347 		if (line[len] && line[len] != '\n' && line[len] != ' ' && last_space > 0)
348 			len = last_space;
349 
350 		diag_write(env, "%s%.*s\n", prefix, len, line);
351 
352 		text = line + len;
353 		while (*text == ' ')
354 			text++;
355 		if (*text == '\n')
356 			text++;
357 
358 		prefix = next_prefix;
359 	}
360 }
361 
362 static int diag_line_width(unsigned int line)
363 {
364 	int width = 1;
365 
366 	while (line >= 10) {
367 		line /= 10;
368 		width++;
369 	}
370 
371 	return width;
372 }
373 
374 static int diag_line_indent(const char *line)
375 {
376 	int indent = 0;
377 
378 	while (*line == ' ' || *line == '\t') {
379 		if (*line == '\t')
380 			indent = round_up(indent + 1, BPF_DIAG_TAB_WIDTH);
381 		else
382 			indent++;
383 		line++;
384 	}
385 
386 	return indent;
387 }
388 
389 static void disasm_print(void *private_data, const char *fmt, ...) __printf(2, 3);
390 
391 static void disasm_print(void *private_data, const char *fmt, ...)
392 {
393 	struct disasm_ctx *ctx = private_data;
394 	va_list args;
395 
396 	va_start(args, fmt);
397 	seq_buf_vprintf(&ctx->seq, fmt, args);
398 	va_end(args);
399 }
400 
401 static const char *disasm_kfunc_name(void *private_data, const struct bpf_insn *insn)
402 {
403 	struct disasm_ctx *ctx = private_data;
404 
405 	return bpf_disasm_kfunc_name(ctx->env, insn);
406 }
407 
408 static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,
409 			       struct disasm_line *line)
410 {
411 	struct disasm_ctx ctx = { .env = env };
412 	struct bpf_insn *insn;
413 	const struct bpf_insn_cbs cbs = {
414 		.cb_call = disasm_kfunc_name,
415 		.cb_print = disasm_print,
416 		.private_data = &ctx,
417 	};
418 
419 	line->idx = insn_idx;
420 	line->valid = false;
421 	seq_buf_init(&ctx.seq, line->text, sizeof(line->text));
422 
423 	if (insn_idx < 0 || insn_idx >= env->prog->len)
424 		return;
425 
426 	if (insn_idx > 0 && bpf_is_ldimm64(&env->prog->insnsi[insn_idx - 1]))
427 		return;
428 
429 	insn = &env->prog->insnsi[insn_idx];
430 	if (bpf_is_ldimm64(insn) && insn_idx + 1 >= env->prog->len)
431 		return;
432 
433 	print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
434 	seq_buf_str(&ctx.seq);
435 	ctx.seq.len = strnlen(line->text, sizeof(line->text));
436 	while (ctx.seq.len && line->text[ctx.seq.len - 1] == '\n')
437 		seq_buf_pop(&ctx.seq);
438 	seq_buf_str(&ctx.seq);
439 
440 	line->valid = true;
441 }
442 
443 static void diag_format_source_text(char *buf, size_t size, const char *line, int width)
444 {
445 	int col = 0, len = 0;
446 
447 	if (!size)
448 		return;
449 	if (width <= 0) {
450 		buf[0] = '\0';
451 		return;
452 	}
453 
454 	line = line ?: "...";
455 	while (*line && col < width && len + 1 < size) {
456 		if (*line == '\t') {
457 			int next = round_up(col + 1, BPF_DIAG_TAB_WIDTH);
458 
459 			while (col < next && col < width && len + 1 < size) {
460 				buf[len++] = ' ';
461 				col++;
462 			}
463 			line++;
464 			continue;
465 		}
466 
467 		buf[len++] = *line++;
468 		col++;
469 	}
470 
471 	if (*line) {
472 		int ellipsis_len = min(3, width);
473 
474 		while (len > 0 && col > width - ellipsis_len) {
475 			len--;
476 			col--;
477 		}
478 		while (ellipsis_len-- && len + 1 < size)
479 			buf[len++] = '.';
480 	}
481 
482 	buf[len] = '\0';
483 }
484 
485 static void diag_format_source_lane(char *buf, size_t size, const char *source_prefix,
486 				    int source_line_width, int line_num, const char *line)
487 {
488 	int len, text_width;
489 
490 	if (line_num <= 0) {
491 		buf[0] = '\0';
492 		return;
493 	}
494 
495 	len = scnprintf(buf, size, "%s%*d | ", source_prefix, source_line_width, line_num);
496 	text_width = BPF_DIAG_SOURCE_LANE_WIDTH - len;
497 	diag_format_source_text(buf + len, size - len, line, text_width);
498 }
499 
500 static void bpf_diag_header(struct bpf_verifier_env *env, const char *category,
501 			    const char *problem)
502 {
503 	char first;
504 
505 	if (!bpf_diag_enabled(env))
506 		return;
507 
508 	category = category ?: "Verifier Error";
509 	problem = problem ?: "";
510 
511 	if (!problem[0]) {
512 		diag_write(env, "\nVerification failed: %s\n", category);
513 		return;
514 	}
515 
516 	first = toupper(problem[0]);
517 	diag_write(env, "\nVerification failed: %s: %c%s\n", category, first, problem + 1);
518 }
519 
520 static void diag_print_source_annotation(struct bpf_verifier_env *env, int line_width, int indent,
521 					 const char *label, const char *msg)
522 {
523 	const char *first_prefix, *next_prefix, *text;
524 
525 	indent = min_t(int, indent, max_t(int, 0, BPF_DIAG_SOURCE_LANE_WIDTH - line_width - 8));
526 	text = bpf_diag_fmt(env, "%s: %s", label, msg);
527 	first_prefix = bpf_diag_fmt(env, "  %*s | %*s^-- ", line_width + 4, "", indent, "");
528 	next_prefix = bpf_diag_fmt(env, "  %*s | %*s    ", line_width + 4, "", indent, "");
529 
530 	diag_print_wrapped_prefixed(env, first_prefix, next_prefix, text);
531 }
532 
533 static void diag_print_insn_context(struct bpf_verifier_env *env, u32 insn_idx,
534 				    struct disasm_line *disasm_lines)
535 {
536 	int insn_width = diag_line_width(env->prog->len ? env->prog->len - 1 : 0);
537 	int i;
538 
539 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
540 		int row = i - BPF_DIAG_CONTEXT;
541 
542 		format_disasm_line(env, insn_idx + row, &disasm_lines[i]);
543 	}
544 
545 	diag_write(env, "  Instruction context:\n");
546 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
547 		struct disasm_line *line = &disasm_lines[i];
548 
549 		if (line->valid)
550 			diag_write(env, "  %s%*d | %s\n",
551 				   line->idx == insn_idx ? ">>> " : "    ",
552 				   insn_width, line->idx, line->text);
553 	}
554 }
555 
556 static void bpf_diag_source(struct bpf_verifier_env *env, u32 insn_idx, const char *label,
557 			    const char *fmt, ...)
558 {
559 	struct bpf_diag_scratch *scratch;
560 	struct bpf_linfo_source *source_lines;
561 	struct disasm_line *disasm_lines;
562 	struct bpf_linfo_source src = {};
563 	struct diag_fmt_mark mark;
564 	const struct bpf_line_info *linfo;
565 	const struct bpf_subprog_info *subprog;
566 	struct btf *btf = env->prog->aux->btf;
567 	char *source_lane;
568 	const char *msg;
569 	const char *func;
570 	int start_line, end_line, width, indent, subprogno, linfo_start, linfo_end, i;
571 	va_list args;
572 
573 	if (!bpf_diag_enabled(env))
574 		return;
575 	if (!env->diag)
576 		return;
577 
578 	mark = diag_fmt_save(env);
579 	label = label ?: "note";
580 	scratch = &env->diag->scratch;
581 	source_lines = scratch->source_lines;
582 	disasm_lines = scratch->disasm_lines;
583 	memset(source_lines, 0, sizeof(scratch->source_lines));
584 	memset(disasm_lines, 0, sizeof(scratch->disasm_lines));
585 
586 	va_start(args, fmt);
587 	msg = bpf_diag_vfmt(env, fmt, args);
588 	va_end(args);
589 	if (!*msg)
590 		msg = "<failed to allocate diagnostic text>";
591 
592 	linfo = bpf_find_linfo(env->prog, insn_idx);
593 	if (btf && linfo)
594 		bpf_get_linfo_source(btf, linfo, &src);
595 	if (!src.file || !*src.file || !src.line || !*src.line) {
596 		diag_write(env, "  insn %u\n", insn_idx);
597 		diag_print_source_annotation(env, 0, 0, label, msg);
598 		diag_print_insn_context(env, insn_idx, disasm_lines);
599 		goto out_restore;
600 	}
601 
602 	subprog = bpf_find_containing_subprog(env, insn_idx);
603 	subprogno = subprog ? subprog - env->subprog_info : -ENOENT;
604 	func = subprogno >= 0 ? bpf_subprog_name(env, subprogno) : NULL;
605 	if (func && *func)
606 		diag_write(env, "  %s @ %s:%d:%d\n", func, src.file, src.line_num, src.line_col);
607 	else
608 		diag_write(env, "  %s:%d:%d\n", src.file, src.line_num, src.line_col);
609 
610 	start_line = src.line_num - BPF_DIAG_CONTEXT;
611 	end_line = src.line_num + BPF_DIAG_CONTEXT;
612 	width = diag_line_width(end_line);
613 	indent = diag_line_indent(src.line);
614 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++)
615 		source_lines[i].line_num = start_line + i;
616 
617 	linfo = env->prog->aux->linfo;
618 	linfo_start = subprog ? subprog->linfo_idx : 0;
619 	linfo_end = subprogno >= 0 && subprogno + 1 < env->subprog_cnt ?
620 		    env->subprog_info[subprogno + 1].linfo_idx : env->prog->aux->nr_linfo;
621 	for (i = linfo_start; i < linfo_end; i++) {
622 		struct bpf_linfo_source line_src;
623 		int idx;
624 
625 		bpf_get_linfo_source(btf, &linfo[i], &line_src);
626 		if (line_src.file_name_off != src.file_name_off ||
627 		    line_src.line_num < start_line || line_src.line_num > end_line ||
628 		    !line_src.line || !*line_src.line)
629 			continue;
630 
631 		idx = line_src.line_num - start_line;
632 		if (!source_lines[idx].line)
633 			source_lines[idx] = line_src;
634 	}
635 
636 	diag_write(env, "  Source context:\n");
637 	source_lane = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
638 	if (!source_lane)
639 		goto out_restore;
640 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
641 		const char *source_prefix;
642 
643 		source_prefix = source_lines[i].line_num == src.line_num ? ">>> " : "    ";
644 		diag_format_source_lane(source_lane, BPF_DIAG_FMT_BUF_SIZE, source_prefix, width,
645 					source_lines[i].line_num, source_lines[i].line);
646 		diag_write(env, "  %s\n", source_lane);
647 		if (source_lines[i].line_num == src.line_num)
648 			diag_print_source_annotation(env, width, indent, label, msg);
649 	}
650 	diag_print_insn_context(env, insn_idx, disasm_lines);
651 
652 out_restore:
653 	diag_fmt_restore(env, mark);
654 }
655 
656 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true)
657 {
658 	struct bpf_diag_history_event event = {
659 		.insn_idx = insn_idx,
660 		.kind = BPF_DIAG_HISTORY_BRANCH,
661 		.branch = {
662 			.cond_true = cond_true,
663 		},
664 	};
665 
666 	diag_append_history(env, &event);
667 }
668