xref: /linux/kernel/bpf/diagnostics.c (revision b9c5d822f677e065971481c4bafe5d84b5451082)
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 DISASM_LINE_LEN 160
26 
27 struct disasm_line {
28 	char text[DISASM_LINE_LEN];
29 	int idx;
30 	bool valid;
31 };
32 
33 struct disasm_ctx {
34 	struct bpf_verifier_env *env;
35 	struct seq_buf seq;
36 };
37 
38 struct diag_fmt_chunk {
39 	struct list_head node;
40 	struct seq_buf seq;
41 	char data[];
42 };
43 
44 struct diag_fmt_mark {
45 	struct diag_fmt_chunk *chunk;
46 	size_t len;
47 };
48 
49 struct bpf_diag_scratch {
50 	struct bpf_linfo_source source_lines[BPF_DIAG_CONTEXT_CNT];
51 	struct disasm_line disasm_lines[BPF_DIAG_CONTEXT_CNT];
52 };
53 
54 struct bpf_diag {
55 	struct bpf_diag_scratch scratch;
56 	struct list_head fmt_chunks;
57 };
58 
59 bool bpf_diag_enabled(const struct bpf_verifier_env *env)
60 {
61 	return env->log.level & BPF_LOG_LEVEL;
62 }
63 
64 static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
65 
66 int bpf_diag_init(struct bpf_verifier_env *env)
67 {
68 	if (!bpf_diag_enabled(env))
69 		return 0;
70 
71 	env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);
72 	if (!env->diag)
73 		return -ENOMEM;
74 
75 	INIT_LIST_HEAD(&env->diag->fmt_chunks);
76 	return 0;
77 }
78 
79 static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size)
80 {
81 	struct bpf_diag *diag = env->diag;
82 	struct diag_fmt_chunk *chunk;
83 	size_t capacity, available;
84 	char *buf;
85 
86 	if (!diag || !size || size > INT_MAX)
87 		return NULL;
88 
89 	if (!list_empty(&diag->fmt_chunks)) {
90 		chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
91 		available = seq_buf_get_buf(&chunk->seq, &buf);
92 		if (available >= size)
93 			goto commit;
94 	}
95 
96 	capacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size);
97 	chunk = kmalloc(struct_size(chunk, data, capacity), GFP_KERNEL_ACCOUNT);
98 	if (!chunk)
99 		return NULL;
100 
101 	seq_buf_init(&chunk->seq, chunk->data, capacity);
102 	list_add_tail(&chunk->node, &diag->fmt_chunks);
103 	available = seq_buf_get_buf(&chunk->seq, &buf);
104 	if (WARN_ON_ONCE(available < size))
105 		return NULL;
106 
107 commit:
108 	seq_buf_commit(&chunk->seq, size);
109 	return buf;
110 }
111 
112 char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size)
113 {
114 	char *buf;
115 
116 	buf = diag_fmt_alloc(env, size);
117 	if (buf)
118 		buf[0] = '\0';
119 	return buf;
120 }
121 
122 const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
123 {
124 	va_list copy;
125 	char *buf;
126 	int len;
127 
128 	va_copy(copy, args);
129 	len = vsnprintf(NULL, 0, fmt, copy);
130 	va_end(copy);
131 	if (len < 0 || len == INT_MAX)
132 		return "";
133 
134 	buf = diag_fmt_alloc(env, len + 1);
135 	if (buf)
136 		vsnprintf(buf, len + 1, fmt, args);
137 	return buf ?: "";
138 }
139 
140 const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...)
141 {
142 	const char *buf;
143 	va_list args;
144 
145 	va_start(args, fmt);
146 	buf = bpf_diag_vfmt(env, fmt, args);
147 	va_end(args);
148 	return buf;
149 }
150 
151 static struct diag_fmt_mark diag_fmt_save(struct bpf_verifier_env *env)
152 {
153 	struct bpf_diag *diag = env->diag;
154 	struct diag_fmt_mark mark = {};
155 
156 	if (!diag || list_empty(&diag->fmt_chunks))
157 		return mark;
158 
159 	mark.chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
160 	mark.len = mark.chunk->seq.len;
161 	return mark;
162 }
163 
164 static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark mark)
165 {
166 	struct bpf_diag *diag = env->diag;
167 	struct diag_fmt_chunk *chunk;
168 
169 	if (!diag)
170 		return;
171 
172 	while (!list_empty(&diag->fmt_chunks)) {
173 		chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
174 		if (chunk == mark.chunk)
175 			break;
176 		list_del(&chunk->node);
177 		kfree(chunk);
178 	}
179 
180 	if (mark.chunk) {
181 		mark.chunk->seq.len = mark.len;
182 		seq_buf_str(&mark.chunk->seq);
183 	}
184 }
185 
186 void bpf_diag_free(struct bpf_verifier_env *env)
187 {
188 	struct bpf_diag *diag = env->diag;
189 
190 	if (!diag)
191 		return;
192 
193 	diag_fmt_restore(env, (struct diag_fmt_mark){});
194 	kfree(diag);
195 	env->diag = NULL;
196 }
197 
198 static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...)
199 {
200 	va_list args;
201 
202 	if (!bpf_diag_enabled(env))
203 		return;
204 
205 	va_start(args, fmt);
206 	bpf_verifier_vlog(&env->log, fmt, args);
207 	va_end(args);
208 }
209 
210 static void diag_print_wrapped_prefixed(struct bpf_verifier_env *env, const char *first_prefix,
211 					const char *next_prefix, const char *text)
212 {
213 	const char *prefix = first_prefix;
214 
215 	while (*text) {
216 		const char *line = text;
217 		int prefix_len = strlen(prefix);
218 		int text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;
219 		int len = 0, last_space = -1;
220 
221 		if (text_width < 1)
222 			text_width = 1;
223 
224 		while (line[len] && line[len] != '\n' && len < text_width) {
225 			if (line[len] == ' ')
226 				last_space = len;
227 			len++;
228 		}
229 
230 		if (line[len] && line[len] != '\n' && line[len] != ' ' && last_space > 0)
231 			len = last_space;
232 
233 		diag_write(env, "%s%.*s\n", prefix, len, line);
234 
235 		text = line + len;
236 		while (*text == ' ')
237 			text++;
238 		if (*text == '\n')
239 			text++;
240 
241 		prefix = next_prefix;
242 	}
243 }
244 
245 static int diag_line_width(unsigned int line)
246 {
247 	int width = 1;
248 
249 	while (line >= 10) {
250 		line /= 10;
251 		width++;
252 	}
253 
254 	return width;
255 }
256 
257 static int diag_line_indent(const char *line)
258 {
259 	int indent = 0;
260 
261 	while (*line == ' ' || *line == '\t') {
262 		if (*line == '\t')
263 			indent = round_up(indent + 1, BPF_DIAG_TAB_WIDTH);
264 		else
265 			indent++;
266 		line++;
267 	}
268 
269 	return indent;
270 }
271 
272 static void disasm_print(void *private_data, const char *fmt, ...) __printf(2, 3);
273 
274 static void disasm_print(void *private_data, const char *fmt, ...)
275 {
276 	struct disasm_ctx *ctx = private_data;
277 	va_list args;
278 
279 	va_start(args, fmt);
280 	seq_buf_vprintf(&ctx->seq, fmt, args);
281 	va_end(args);
282 }
283 
284 static const char *disasm_kfunc_name(void *private_data, const struct bpf_insn *insn)
285 {
286 	struct disasm_ctx *ctx = private_data;
287 
288 	return bpf_disasm_kfunc_name(ctx->env, insn);
289 }
290 
291 static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,
292 			       struct disasm_line *line)
293 {
294 	struct disasm_ctx ctx = { .env = env };
295 	struct bpf_insn *insn;
296 	const struct bpf_insn_cbs cbs = {
297 		.cb_call = disasm_kfunc_name,
298 		.cb_print = disasm_print,
299 		.private_data = &ctx,
300 	};
301 
302 	line->idx = insn_idx;
303 	line->valid = false;
304 	seq_buf_init(&ctx.seq, line->text, sizeof(line->text));
305 
306 	if (insn_idx < 0 || insn_idx >= env->prog->len)
307 		return;
308 
309 	if (insn_idx > 0 && bpf_is_ldimm64(&env->prog->insnsi[insn_idx - 1]))
310 		return;
311 
312 	insn = &env->prog->insnsi[insn_idx];
313 	if (bpf_is_ldimm64(insn) && insn_idx + 1 >= env->prog->len)
314 		return;
315 
316 	print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
317 	seq_buf_str(&ctx.seq);
318 	ctx.seq.len = strnlen(line->text, sizeof(line->text));
319 	while (ctx.seq.len && line->text[ctx.seq.len - 1] == '\n')
320 		seq_buf_pop(&ctx.seq);
321 	seq_buf_str(&ctx.seq);
322 
323 	line->valid = true;
324 }
325 
326 static void diag_format_source_text(char *buf, size_t size, const char *line, int width)
327 {
328 	int col = 0, len = 0;
329 
330 	if (!size)
331 		return;
332 	if (width <= 0) {
333 		buf[0] = '\0';
334 		return;
335 	}
336 
337 	line = line ?: "...";
338 	while (*line && col < width && len + 1 < size) {
339 		if (*line == '\t') {
340 			int next = round_up(col + 1, BPF_DIAG_TAB_WIDTH);
341 
342 			while (col < next && col < width && len + 1 < size) {
343 				buf[len++] = ' ';
344 				col++;
345 			}
346 			line++;
347 			continue;
348 		}
349 
350 		buf[len++] = *line++;
351 		col++;
352 	}
353 
354 	if (*line) {
355 		int ellipsis_len = min(3, width);
356 
357 		while (len > 0 && col > width - ellipsis_len) {
358 			len--;
359 			col--;
360 		}
361 		while (ellipsis_len-- && len + 1 < size)
362 			buf[len++] = '.';
363 	}
364 
365 	buf[len] = '\0';
366 }
367 
368 static void diag_format_source_lane(char *buf, size_t size, const char *source_prefix,
369 				    int source_line_width, int line_num, const char *line)
370 {
371 	int len, text_width;
372 
373 	if (line_num <= 0) {
374 		buf[0] = '\0';
375 		return;
376 	}
377 
378 	len = scnprintf(buf, size, "%s%*d | ", source_prefix, source_line_width, line_num);
379 	text_width = BPF_DIAG_SOURCE_LANE_WIDTH - len;
380 	diag_format_source_text(buf + len, size - len, line, text_width);
381 }
382 
383 static void bpf_diag_header(struct bpf_verifier_env *env, const char *category,
384 			    const char *problem)
385 {
386 	char first;
387 
388 	if (!bpf_diag_enabled(env))
389 		return;
390 
391 	category = category ?: "Verifier Error";
392 	problem = problem ?: "";
393 
394 	if (!problem[0]) {
395 		diag_write(env, "\nVerification failed: %s\n", category);
396 		return;
397 	}
398 
399 	first = toupper(problem[0]);
400 	diag_write(env, "\nVerification failed: %s: %c%s\n", category, first, problem + 1);
401 }
402 
403 static void diag_print_source_annotation(struct bpf_verifier_env *env, int line_width, int indent,
404 					 const char *label, const char *msg)
405 {
406 	const char *first_prefix, *next_prefix, *text;
407 
408 	indent = min_t(int, indent, max_t(int, 0, BPF_DIAG_SOURCE_LANE_WIDTH - line_width - 8));
409 	text = bpf_diag_fmt(env, "%s: %s", label, msg);
410 	first_prefix = bpf_diag_fmt(env, "  %*s | %*s^-- ", line_width + 4, "", indent, "");
411 	next_prefix = bpf_diag_fmt(env, "  %*s | %*s    ", line_width + 4, "", indent, "");
412 
413 	diag_print_wrapped_prefixed(env, first_prefix, next_prefix, text);
414 }
415 
416 static void diag_print_insn_context(struct bpf_verifier_env *env, u32 insn_idx,
417 				    struct disasm_line *disasm_lines)
418 {
419 	int insn_width = diag_line_width(env->prog->len ? env->prog->len - 1 : 0);
420 	int i;
421 
422 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
423 		int row = i - BPF_DIAG_CONTEXT;
424 
425 		format_disasm_line(env, insn_idx + row, &disasm_lines[i]);
426 	}
427 
428 	diag_write(env, "  Instruction context:\n");
429 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
430 		struct disasm_line *line = &disasm_lines[i];
431 
432 		if (line->valid)
433 			diag_write(env, "  %s%*d | %s\n",
434 				   line->idx == insn_idx ? ">>> " : "    ",
435 				   insn_width, line->idx, line->text);
436 	}
437 }
438 
439 static void bpf_diag_source(struct bpf_verifier_env *env, u32 insn_idx, const char *label,
440 			    const char *fmt, ...)
441 {
442 	struct bpf_diag_scratch *scratch;
443 	struct bpf_linfo_source *source_lines;
444 	struct disasm_line *disasm_lines;
445 	struct bpf_linfo_source src = {};
446 	struct diag_fmt_mark mark;
447 	const struct bpf_line_info *linfo;
448 	const struct bpf_subprog_info *subprog;
449 	struct btf *btf = env->prog->aux->btf;
450 	char *source_lane;
451 	const char *msg;
452 	const char *func;
453 	int start_line, end_line, width, indent, subprogno, linfo_start, linfo_end, i;
454 	va_list args;
455 
456 	if (!bpf_diag_enabled(env))
457 		return;
458 	if (!env->diag)
459 		return;
460 
461 	mark = diag_fmt_save(env);
462 	label = label ?: "note";
463 	scratch = &env->diag->scratch;
464 	source_lines = scratch->source_lines;
465 	disasm_lines = scratch->disasm_lines;
466 	memset(source_lines, 0, sizeof(scratch->source_lines));
467 	memset(disasm_lines, 0, sizeof(scratch->disasm_lines));
468 
469 	va_start(args, fmt);
470 	msg = bpf_diag_vfmt(env, fmt, args);
471 	va_end(args);
472 	if (!*msg)
473 		msg = "<failed to allocate diagnostic text>";
474 
475 	linfo = bpf_find_linfo(env->prog, insn_idx);
476 	if (btf && linfo)
477 		bpf_get_linfo_source(btf, linfo, &src);
478 	if (!src.file || !*src.file || !src.line || !*src.line) {
479 		diag_write(env, "  insn %u\n", insn_idx);
480 		diag_print_source_annotation(env, 0, 0, label, msg);
481 		diag_print_insn_context(env, insn_idx, disasm_lines);
482 		goto out_restore;
483 	}
484 
485 	subprog = bpf_find_containing_subprog(env, insn_idx);
486 	subprogno = subprog ? subprog - env->subprog_info : -ENOENT;
487 	func = subprogno >= 0 ? bpf_subprog_name(env, subprogno) : NULL;
488 	if (func && *func)
489 		diag_write(env, "  %s @ %s:%d:%d\n", func, src.file, src.line_num, src.line_col);
490 	else
491 		diag_write(env, "  %s:%d:%d\n", src.file, src.line_num, src.line_col);
492 
493 	start_line = src.line_num - BPF_DIAG_CONTEXT;
494 	end_line = src.line_num + BPF_DIAG_CONTEXT;
495 	width = diag_line_width(end_line);
496 	indent = diag_line_indent(src.line);
497 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++)
498 		source_lines[i].line_num = start_line + i;
499 
500 	linfo = env->prog->aux->linfo;
501 	linfo_start = subprog ? subprog->linfo_idx : 0;
502 	linfo_end = subprogno >= 0 && subprogno + 1 < env->subprog_cnt ?
503 		    env->subprog_info[subprogno + 1].linfo_idx : env->prog->aux->nr_linfo;
504 	for (i = linfo_start; i < linfo_end; i++) {
505 		struct bpf_linfo_source line_src;
506 		int idx;
507 
508 		bpf_get_linfo_source(btf, &linfo[i], &line_src);
509 		if (line_src.file_name_off != src.file_name_off ||
510 		    line_src.line_num < start_line || line_src.line_num > end_line ||
511 		    !line_src.line || !*line_src.line)
512 			continue;
513 
514 		idx = line_src.line_num - start_line;
515 		if (!source_lines[idx].line)
516 			source_lines[idx] = line_src;
517 	}
518 
519 	diag_write(env, "  Source context:\n");
520 	source_lane = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
521 	if (!source_lane)
522 		goto out_restore;
523 	for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
524 		const char *source_prefix;
525 
526 		source_prefix = source_lines[i].line_num == src.line_num ? ">>> " : "    ";
527 		diag_format_source_lane(source_lane, BPF_DIAG_FMT_BUF_SIZE, source_prefix, width,
528 					source_lines[i].line_num, source_lines[i].line);
529 		diag_write(env, "  %s\n", source_lane);
530 		if (source_lines[i].line_num == src.line_num)
531 			diag_print_source_annotation(env, width, indent, label, msg);
532 	}
533 	diag_print_insn_context(env, insn_idx, disasm_lines);
534 
535 out_restore:
536 	diag_fmt_restore(env, mark);
537 }
538