Skip to content

bpftool: Refactor config parsing and add CET symbol matching #5753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: bpf-next_base
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions tools/bpf/bpftool/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <sys/utsname.h>

#include <linux/filter.h>
#include <linux/limits.h>
Expand All @@ -31,6 +32,7 @@
#include <bpf/hashmap.h>
#include <bpf/libbpf.h> /* libbpf_num_possible_cpus */
#include <bpf/btf.h>
#include <zlib.h>

#include "main.h"

Expand Down Expand Up @@ -1208,3 +1210,94 @@ int pathname_concat(char *buf, int buf_sz, const char *path,

return 0;
}

static bool read_next_kernel_config_option(gzFile file, char *buf, size_t n,
char **value)
{
char *sep;

while (gzgets(file, buf, n)) {
if (strncmp(buf, "CONFIG_", 7))
continue;

sep = strchr(buf, '=');
if (!sep)
continue;

/* Trim ending '\n' */
buf[strlen(buf) - 1] = '\0';

/* Split on '=' and ensure that a value is present. */
*sep = '\0';
if (!sep[1])
continue;

*value = sep + 1;
return true;
}

return false;
}

int read_kernel_config(const struct kernel_config_option *requested_options,
size_t num_options, char **out_values,
const char *define_prefix)
{
struct utsname utsn;
char path[PATH_MAX];
gzFile file = NULL;
char buf[4096];
char *value;
size_t i;
int ret = 0;

if (!requested_options || !out_values || num_options == 0)
return -1;

if (!uname(&utsn)) {
snprintf(path, sizeof(path), "/boot/config-%s", utsn.release);

/* gzopen also accepts uncompressed files. */
file = gzopen(path, "r");
}

if (!file) {
/* Some distributions build with CONFIG_IKCONFIG=y and put the
* config file at /proc/config.gz.
*/
file = gzopen("/proc/config.gz", "r");
}

if (!file) {
p_info("skipping kernel config, can't open file: %s",
strerror(errno));
return -1;
}

if (!gzgets(file, buf, sizeof(buf)) || !gzgets(file, buf, sizeof(buf))) {
p_info("skipping kernel config, can't read from file: %s",
strerror(errno));
ret = -1;
goto end_parse;
}

if (strcmp(buf, "# Automatically generated file; DO NOT EDIT.\n")) {
p_info("skipping kernel config, can't find correct file");
ret = -1;
goto end_parse;
}

while (read_next_kernel_config_option(file, buf, sizeof(buf), &value)) {
for (i = 0; i < num_options; i++) {
if ((define_prefix && !requested_options[i].macro_dump) ||
out_values[i] || strcmp(buf, requested_options[i].name))
continue;

out_values[i] = strdup(value);
}
}

end_parse:
gzclose(file);
return ret;
}
86 changes: 4 additions & 82 deletions tools/bpf/bpftool/feature.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@
#ifdef USE_LIBCAP
#include <sys/capability.h>
#endif
#include <sys/utsname.h>
#include <sys/vfs.h>

#include <linux/filter.h>
#include <linux/limits.h>

#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <zlib.h>

#include "main.h"

Expand Down Expand Up @@ -327,40 +325,9 @@ static void probe_jit_limit(void)
}
}

static bool read_next_kernel_config_option(gzFile file, char *buf, size_t n,
char **value)
{
char *sep;

while (gzgets(file, buf, n)) {
if (strncmp(buf, "CONFIG_", 7))
continue;

sep = strchr(buf, '=');
if (!sep)
continue;

/* Trim ending '\n' */
buf[strlen(buf) - 1] = '\0';

/* Split on '=' and ensure that a value is present. */
*sep = '\0';
if (!sep[1])
continue;

*value = sep + 1;
return true;
}

return false;
}

static void probe_kernel_image_config(const char *define_prefix)
{
static const struct {
const char * const name;
bool macro_dump;
} options[] = {
struct kernel_config_option options[] = {
/* Enable BPF */
{ "CONFIG_BPF", },
/* Enable bpf() syscall */
Expand Down Expand Up @@ -435,63 +402,18 @@ static void probe_kernel_image_config(const char *define_prefix)
{ "CONFIG_HZ", true, }
};
char *values[ARRAY_SIZE(options)] = { };
struct utsname utsn;
char path[PATH_MAX];
gzFile file = NULL;
char buf[4096];
char *value;
size_t i;

if (!uname(&utsn)) {
snprintf(path, sizeof(path), "/boot/config-%s", utsn.release);

/* gzopen also accepts uncompressed files. */
file = gzopen(path, "r");
}

if (!file) {
/* Some distributions build with CONFIG_IKCONFIG=y and put the
* config file at /proc/config.gz.
*/
file = gzopen("/proc/config.gz", "r");
}
if (!file) {
p_info("skipping kernel config, can't open file: %s",
strerror(errno));
goto end_parse;
}
/* Sanity checks */
if (!gzgets(file, buf, sizeof(buf)) ||
!gzgets(file, buf, sizeof(buf))) {
p_info("skipping kernel config, can't read from file: %s",
strerror(errno));
goto end_parse;
}
if (strcmp(buf, "# Automatically generated file; DO NOT EDIT.\n")) {
p_info("skipping kernel config, can't find correct file");
goto end_parse;
}

while (read_next_kernel_config_option(file, buf, sizeof(buf), &value)) {
for (i = 0; i < ARRAY_SIZE(options); i++) {
if ((define_prefix && !options[i].macro_dump) ||
values[i] || strcmp(buf, options[i].name))
continue;

values[i] = strdup(value);
}
}
if (read_kernel_config(options, ARRAY_SIZE(options), values,
define_prefix))
return;

for (i = 0; i < ARRAY_SIZE(options); i++) {
if (define_prefix && !options[i].macro_dump)
continue;
print_kernel_option(options[i].name, values[i], define_prefix);
free(values[i]);
}

end_parse:
if (file)
gzclose(file);
}

static bool probe_bpf_syscall(const char *define_prefix)
Expand Down
50 changes: 48 additions & 2 deletions tools/bpf/bpftool/link.c
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,52 @@ get_addr_cookie_array(__u64 *addrs, __u64 *cookies, __u32 count)
return data;
}

static bool is_x86_ibt_enabled(void)
{
#if defined(__x86_64__)
struct kernel_config_option options[] = {
{ "CONFIG_X86_KERNEL_IBT", },
};
char *values[ARRAY_SIZE(options)] = { };
bool ret;

if (read_kernel_config(options, ARRAY_SIZE(options), values, NULL))
return false;

ret = !!values[0];
free(values[0]);
return ret;
#else
return false;
#endif
}

static bool
symbol_matches_target(__u64 sym_addr, __u64 target_addr, bool is_ibt_enabled)
{
if (sym_addr == target_addr)
return true;

/*
* On x86_64 architectures with CET (Control-flow Enforcement Technology),
* function entry points have a 4-byte 'endbr' instruction prefix.
* This causes kprobe hooks to target the address *after* 'endbr'
* (symbol address + 4), preserving the CET instruction.
* Here we check if the symbol address matches the hook target address
* minus 4, indicating a CET-enabled function entry point.
*/
if (is_ibt_enabled && sym_addr == target_addr - 4)
return true;

return false;
}

static void
show_kprobe_multi_json(struct bpf_link_info *info, json_writer_t *wtr)
{
struct addr_cookie *data;
__u32 i, j = 0;
bool is_ibt_enabled;

jsonw_bool_field(json_wtr, "retprobe",
info->kprobe_multi.flags & BPF_F_KPROBE_MULTI_RETURN);
Expand All @@ -306,8 +347,10 @@ show_kprobe_multi_json(struct bpf_link_info *info, json_writer_t *wtr)
if (!dd.sym_count)
goto error;

is_ibt_enabled = is_x86_ibt_enabled();
for (i = 0; i < dd.sym_count; i++) {
if (dd.sym_mapping[i].address != data[j].addr)
if (!symbol_matches_target(dd.sym_mapping[i].address,
data[j].addr, is_ibt_enabled))
continue;
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "addr", dd.sym_mapping[i].address);
Expand Down Expand Up @@ -719,6 +762,7 @@ static void show_kprobe_multi_plain(struct bpf_link_info *info)
{
struct addr_cookie *data;
__u32 i, j = 0;
bool is_ibt_enabled;

if (!info->kprobe_multi.count)
return;
Expand All @@ -742,9 +786,11 @@ static void show_kprobe_multi_plain(struct bpf_link_info *info)
if (!dd.sym_count)
goto error;

is_ibt_enabled = is_x86_ibt_enabled();
printf("\n\t%-16s %-16s %s", "addr", "cookie", "func [module]");
for (i = 0; i < dd.sym_count; i++) {
if (dd.sym_mapping[i].address != data[j].addr)
if (!symbol_matches_target(dd.sym_mapping[i].address,
data[j].addr, is_ibt_enabled))
continue;
printf("\n\t%016lx %-16llx %s",
dd.sym_mapping[i].address, data[j].cookie, dd.sym_mapping[i].name);
Expand Down
9 changes: 9 additions & 0 deletions tools/bpf/bpftool/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,13 @@ int pathname_concat(char *buf, int buf_sz, const char *path,
/* print netfilter bpf_link info */
void netfilter_dump_plain(const struct bpf_link_info *info);
void netfilter_dump_json(const struct bpf_link_info *info, json_writer_t *wtr);

struct kernel_config_option {
const char *name;
bool macro_dump;
};

int read_kernel_config(const struct kernel_config_option *requested_options,
size_t num_options, char **out_values,
const char *define_prefix);
#endif
Loading