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