selftests/bpf: Add BTF fixup for __naked subprog parameter names

When __naked subprogs are used in verifier tests, clang drops
parameter names from their BTF FUNC_PROTO entries. This prevents
the verifier from resolving stack argument slots by name.

Add a __btf_func_path(path) annotation that points to a separate
BTF file containing properly-named FUNC entries. The test_loader
matches FUNC entries by name, detects anonymous parameters, and
replaces the FUNC_PROTO with a new one that carries parameter
names from the custom file while preserving the original type IDs.

The custom BTF file also serves as btf_custom_path for kfunc
resolution when no separate btf_custom_path is specified.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
Link: https://lore.kernel.org/r/20260513045138.2398886-1-yonghong.song@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Yonghong Song 2026-05-12 21:51:38 -07:00 committed by Alexei Starovoitov
parent 9f42204c62
commit 5b31de8892
2 changed files with 136 additions and 1 deletions

View File

@ -152,6 +152,7 @@
#define __auxiliary __test_tag("test_auxiliary")
#define __auxiliary_unpriv __test_tag("test_auxiliary_unpriv")
#define __btf_path(path) __test_tag("test_btf_path=" path)
#define __btf_func_path(path) __test_tag("test_btf_func_path=" path)
#define __arch(arch) __test_tag("test_arch=" arch)
#define __arch_x86_64 __arch("X86_64")
#define __arch_arm64 __arch("ARM64")

View File

@ -63,6 +63,7 @@ struct test_spec {
struct test_subspec priv;
struct test_subspec unpriv;
const char *btf_custom_path;
const char *btf_custom_func_path;
int log_level;
int prog_flags;
int mode_mask;
@ -590,6 +591,8 @@ static int parse_test_spec(struct test_loader *tester,
jit_on_next_line = true;
} else if ((val = str_has_pfx(s, "test_btf_path="))) {
spec->btf_custom_path = val;
} else if ((val = str_has_pfx(s, "test_btf_func_path="))) {
spec->btf_custom_func_path = val;
} else if ((val = str_has_pfx(s, "test_caps_unpriv="))) {
err = parse_caps(val, &spec->unpriv.caps, "test caps");
if (err)
@ -1175,6 +1178,123 @@ static int get_stream(int stream_id, int prog_fd, char *text, size_t text_sz)
return ret;
}
/*
* Fix up the program's BTF using BTF from a separate file.
*
* For __naked subprogs, clang drops parameter names from BTF. Find FUNC
* entries with anonymous parameters and replace their FUNC_PROTO with the
* properly-named version from the custom file.
*/
static int fixup_btf_from_path(struct bpf_object *obj, const char *path)
{
struct btf *prog_btf, *custom_btf;
__u32 i, j, cnt, custom_cnt;
int err = 0;
prog_btf = bpf_object__btf(obj);
if (!prog_btf)
return 0;
custom_btf = btf__parse(path, NULL);
if (!ASSERT_OK_PTR(custom_btf, "parse_custom_btf"))
return -EINVAL;
cnt = btf__type_cnt(prog_btf);
custom_cnt = btf__type_cnt(custom_btf);
/* Fix up FUNC entries with anonymous params.
* Save all data from prog_btf BEFORE calling btf__add_*,
* since those calls may reallocate the BTF data buffer
* and invalidate any pointers obtained from btf__type_by_id.
*/
for (i = 1; i < cnt; i++) {
const struct btf_type *t = btf__type_by_id(prog_btf, i);
const struct btf_type *fp, *custom_t, *custom_fp;
const struct btf_param *params, *custom_params;
__u32 ret_type_id, vlen;
__u32 *prog_param_types = NULL;
const char *name;
int new_proto_id;
if (!btf_is_func(t))
continue;
fp = btf__type_by_id(prog_btf, t->type);
if (!fp || !btf_is_func_proto(fp) || btf_vlen(fp) == 0)
continue;
/* Check if any param is anonymous */
params = btf_params(fp);
if (params[0].name_off != 0)
continue;
/* Find matching FUNC by name in custom BTF */
name = btf__name_by_offset(prog_btf, t->name_off);
if (!name)
continue;
for (j = 1; j < custom_cnt; j++) {
const char *cname;
custom_t = btf__type_by_id(custom_btf, j);
if (!btf_is_func(custom_t))
continue;
cname = btf__name_by_offset(custom_btf, custom_t->name_off);
if (cname && strcmp(name, cname) == 0)
break;
}
if (j >= custom_cnt)
continue;
custom_fp = btf__type_by_id(custom_btf, custom_t->type);
if (!custom_fp || !btf_is_func_proto(custom_fp))
continue;
vlen = btf_vlen(fp);
if (vlen != btf_vlen(custom_fp))
continue;
/* Save data before btf__add_* calls invalidate pointers */
ret_type_id = fp->type;
prog_param_types = malloc(vlen * sizeof(*prog_param_types));
if (!prog_param_types) {
err = -ENOMEM;
break;
}
for (j = 0; j < vlen; j++)
prog_param_types[j] = params[j].type;
/* Add a new FUNC_PROTO: param names from custom, types from prog */
new_proto_id = btf__add_func_proto(prog_btf, ret_type_id);
if (new_proto_id < 0) {
err = new_proto_id;
free(prog_param_types);
break;
}
custom_params = btf_params(custom_fp);
for (j = 0; j < vlen; j++) {
const char *pname;
pname = btf__name_by_offset(custom_btf, custom_params[j].name_off);
err = btf__add_func_param(prog_btf, pname ?: "", prog_param_types[j]);
if (err)
break;
}
free(prog_param_types);
if (err)
break;
/* Update the FUNC to point to the new FUNC_PROTO (re-fetch
* since btf__add_* may have reallocated the data buffer).
*/
((struct btf_type *)btf__type_by_id(prog_btf, i))->type = new_proto_id;
}
btf__free(custom_btf);
return err;
}
/* this function is forced noinline and has short generic name to look better
* in test_progs output (in case of a failure)
*/
@ -1231,13 +1351,27 @@ void run_subtest(struct test_loader *tester,
}
}
/* Implicitly reset to NULL if next test case doesn't specify */
/* Implicitly reset to NULL if next test case doesn't specify.
* btf_custom_func_path also serves as btf_custom_path for kfunc resolution.
*/
open_opts->btf_custom_path = spec->btf_custom_path;
if (!open_opts->btf_custom_path)
open_opts->btf_custom_path = spec->btf_custom_func_path;
tobj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, open_opts);
if (!ASSERT_OK_PTR(tobj, "obj_open_mem")) /* shouldn't happen */
goto subtest_cleanup;
/* Fix up __naked subprog BTF using a separate file with named params */
if (spec->btf_custom_func_path) {
err = fixup_btf_from_path(tobj, spec->btf_custom_func_path);
if (err) {
PRINT_FAIL("failed to fixup BTF from %s: %d\n",
spec->btf_custom_func_path, err);
goto tobj_cleanup;
}
}
i = 0;
bpf_object__for_each_program(tprog_iter, tobj) {
spec_iter = &specs[i++];