Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpftool: Add CET-aware symbol matching for x86_64 architectures

Adjust symbol matching logic to account for Control-flow Enforcement
Technology (CET) on x86_64 systems. CET prefixes functions with
a 4-byte 'endbr' instruction, shifting the actual hook entry point to
symbol + 4.

Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Quentin Monnet <qmo@kernel.org>
Acked-by: Yonghong Song <yonghong.song@linux.dev>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Link: https://lore.kernel.org/bpf/20250829061107.23905-3-chenyuan_fl@163.com

authored by

Yuan Chen and committed by
Daniel Borkmann
6417ca85 70f32a10

+50 -4
+50 -4
tools/bpf/bpftool/link.c
··· 282 282 return data; 283 283 } 284 284 285 + static bool is_x86_ibt_enabled(void) 286 + { 287 + #if defined(__x86_64__) 288 + struct kernel_config_option options[] = { 289 + { "CONFIG_X86_KERNEL_IBT", }, 290 + }; 291 + char *values[ARRAY_SIZE(options)] = { }; 292 + bool ret; 293 + 294 + if (read_kernel_config(options, ARRAY_SIZE(options), values, NULL)) 295 + return false; 296 + 297 + ret = !!values[0]; 298 + free(values[0]); 299 + return ret; 300 + #else 301 + return false; 302 + #endif 303 + } 304 + 305 + static bool 306 + symbol_matches_target(__u64 sym_addr, __u64 target_addr, bool is_ibt_enabled) 307 + { 308 + if (sym_addr == target_addr) 309 + return true; 310 + 311 + /* 312 + * On x86_64 architectures with CET (Control-flow Enforcement Technology), 313 + * function entry points have a 4-byte 'endbr' instruction prefix. 314 + * This causes kprobe hooks to target the address *after* 'endbr' 315 + * (symbol address + 4), preserving the CET instruction. 316 + * Here we check if the symbol address matches the hook target address 317 + * minus 4, indicating a CET-enabled function entry point. 318 + */ 319 + if (is_ibt_enabled && sym_addr == target_addr - 4) 320 + return true; 321 + 322 + return false; 323 + } 324 + 285 325 static void 286 326 show_kprobe_multi_json(struct bpf_link_info *info, json_writer_t *wtr) 287 327 { 288 328 struct addr_cookie *data; 289 329 __u32 i, j = 0; 330 + bool is_ibt_enabled; 290 331 291 332 jsonw_bool_field(json_wtr, "retprobe", 292 333 info->kprobe_multi.flags & BPF_F_KPROBE_MULTI_RETURN); ··· 347 306 if (!dd.sym_count) 348 307 goto error; 349 308 309 + is_ibt_enabled = is_x86_ibt_enabled(); 350 310 for (i = 0; i < dd.sym_count; i++) { 351 - if (dd.sym_mapping[i].address != data[j].addr) 311 + if (!symbol_matches_target(dd.sym_mapping[i].address, 312 + data[j].addr, is_ibt_enabled)) 352 313 continue; 353 314 jsonw_start_object(json_wtr); 354 - jsonw_uint_field(json_wtr, "addr", dd.sym_mapping[i].address); 315 + jsonw_uint_field(json_wtr, "addr", (unsigned long)data[j].addr); 355 316 jsonw_string_field(json_wtr, "func", dd.sym_mapping[i].name); 356 317 /* Print null if it is vmlinux */ 357 318 if (dd.sym_mapping[i].module[0] == '\0') { ··· 762 719 { 763 720 struct addr_cookie *data; 764 721 __u32 i, j = 0; 722 + bool is_ibt_enabled; 765 723 766 724 if (!info->kprobe_multi.count) 767 725 return; ··· 786 742 if (!dd.sym_count) 787 743 goto error; 788 744 745 + is_ibt_enabled = is_x86_ibt_enabled(); 789 746 printf("\n\t%-16s %-16s %s", "addr", "cookie", "func [module]"); 790 747 for (i = 0; i < dd.sym_count; i++) { 791 - if (dd.sym_mapping[i].address != data[j].addr) 748 + if (!symbol_matches_target(dd.sym_mapping[i].address, 749 + data[j].addr, is_ibt_enabled)) 792 750 continue; 793 751 printf("\n\t%016lx %-16llx %s", 794 - dd.sym_mapping[i].address, data[j].cookie, dd.sym_mapping[i].name); 752 + (unsigned long)data[j].addr, data[j].cookie, dd.sym_mapping[i].name); 795 753 if (dd.sym_mapping[i].module[0] != '\0') 796 754 printf(" [%s] ", dd.sym_mapping[i].module); 797 755 else