From b516a262658d86278c16b593aa7dcd061e339f5e Mon Sep 17 00:00:00 2001 From: ErenAri Date: Mon, 22 Jun 2026 14:12:42 +0300 Subject: [PATCH] feat(validator): auto-type programs libbpf can't classify by section name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a program is left BPF_PROG_TYPE_UNSPEC after open because its ELF section name isn't one libbpf recognizes, set the type the artifact's own loader assigns, before load. Today this covers socket-filter programs in "socket"-prefixed sections (e.g. Inspektor Gadget's socket1), which otherwise fail with "missing BPF prog type". Reported per program in the run notes. Verified against IG trace_dns: the socket1 program-type error is gone (it is auto-typed to SOCKET_FILTER). trace_dns then surfaces a separate, deeper issue — a CO-RE relocation against IG's socket-enricher API type gadget_socket_value, which the IG loader supplies at runtime; that is a framework dependency, not a kernel-compat gap, and out of scope here. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 10 +++++ internal/runner/runner.go | 13 +++++++ internal/runner/validator_result.go | 5 +++ validator/c-libbpf/src/main.c | 57 +++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40bbc4b..cb4249e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once a ## [Unreleased] ### Added +- Auto-type programs libbpf can't classify by section name: when a program is + left `BPF_PROG_TYPE_UNSPEC` after open because its ELF section name isn't one + libbpf recognizes, the validator sets the type the artifact's own loader + assigns. Today this covers socket-filter programs in `socket`-prefixed + sections (e.g. Inspektor Gadget's `socket1`), which otherwise fail to load + with "missing BPF prog type". Reported per program in the run notes. (This + clears the program-type blocker; a framework-coupled gadget can still fail + later for its own reasons — e.g. `trace_dns` then hits a CO-RE relocation + against Inspektor Gadget's socket-enricher API type `gadget_socket_value`, + which the IG loader supplies at runtime and a standalone load does not.) - Generic inner-map prototype map fixup: a manifest `maps[].inner_map` (`type`/`key_size`/`value_size`/`max_entries`) installs an inner-map template on a `HASH_OF_MAPS`/`ARRAY_OF_MAPS` before load, so objects whose own loader diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 3a89eb4..9928f7c 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -577,6 +577,7 @@ func executeTarget( target.Notes = append(target.Notes, mapTypeHintNotes(vr.Logs.Libbpf)...) target.Notes = append(target.Notes, mapFixupNotes(vr)...) target.Notes = append(target.Notes, autoSizedMapNotes(vr)...) + target.Notes = append(target.Notes, autoTypedProgramNotes(vr)...) target.Notes = append(target.Notes, progVariantNotes(vr)...) target.Notes = append(target.Notes, perProgramLoadNotes(vr)...) target.BTF = &schema.TargetBTF{ @@ -994,6 +995,18 @@ func autoSizedMapNotes(vr validatorResult) []string { return notes } +// autoTypedProgramNotes reports programs whose BPF type the validator set +// because libbpf could not infer it from the ELF section name (e.g. a +// socket-filter program in a "socket1" section) — transparent so a reader +// knows the load relied on setting the type, as the artifact's loader does. +func autoTypedProgramNotes(vr validatorResult) []string { + notes := make([]string, 0, len(vr.AutoTypedPrograms)) + for _, p := range vr.AutoTypedPrograms { + notes = append(notes, fmt.Sprintf("auto-typed program %q (section %q) — set BPF program type the loader assigns, since libbpf could not infer it from the section name", p.Name, p.Section)) + } + return notes +} + // validatorTuning carries manifest-declared loader-contract settings (map // fixups, program variant groups) from manifest load to VM execution. type validatorTuning struct { diff --git a/internal/runner/validator_result.go b/internal/runner/validator_result.go index 5792b34..4fda089 100644 --- a/internal/runner/validator_result.go +++ b/internal/runner/validator_result.go @@ -93,6 +93,11 @@ type validatorResult struct { MapType uint32 `json:"map_type"` MaxEntries uint32 `json:"max_entries"` } `json:"auto_sized_maps"` + AutoTypedPrograms []struct { + Name string `json:"name"` + Section string `json:"section"` + ProgType uint32 `json:"prog_type"` + } `json:"auto_typed_programs"` Discovery struct { Programs []struct { Name string `json:"name"` diff --git a/validator/c-libbpf/src/main.c b/validator/c-libbpf/src/main.c index acc8c33..2c0c622 100644 --- a/validator/c-libbpf/src/main.c +++ b/validator/c-libbpf/src/main.c @@ -1649,6 +1649,53 @@ static void auto_size_maps(struct bpf_object *obj) { } } +/* Programs whose ELF section name libbpf can't map to a program type get + * auto-typed the way the real loader would. The motivating case: Inspektor + * Gadget's socket-filter programs live in sections like "socket1", which + * libbpf doesn't recognize (it matches "socket" exactly or "socket/..."), so + * the load fails with "missing BPF prog type". Only programs libbpf left as + * UNSPEC are touched, and only for section families that unambiguously imply + * a type — today socket-filter. Other families (kprobe/, tracepoint/, ...) + * already use libbpf's "/..." convention and resolve on their own. */ +#define MAX_AUTOTYPED_PROGS 64 + +struct autotyped_prog { + char name[128]; + char section[128]; + unsigned int prog_type; +}; +static struct autotyped_prog g_autotyped[MAX_AUTOTYPED_PROGS]; +static int g_autotyped_count; + +static void auto_type_programs(struct bpf_object *obj) { + struct bpf_program *prog; + bpf_object__for_each_program(prog, obj) { + if (bpf_program__type(prog) != BPF_PROG_TYPE_UNSPEC) { + continue; + } + const char *section = bpf_program__section_name(prog); + if (!section) { + continue; + } + unsigned int t = 0; + if (strncmp(section, "socket", 6) == 0) { + t = BPF_PROG_TYPE_SOCKET_FILTER; + } + if (t == 0) { + continue; + } + if (bpf_program__set_type(prog, (enum bpf_prog_type)t) != 0) { + continue; + } + if (g_autotyped_count < MAX_AUTOTYPED_PROGS) { + struct autotyped_prog *a = &g_autotyped[g_autotyped_count++]; + snprintf(a->name, sizeof(a->name), "%s", bpf_program__name(prog)); + snprintf(a->section, sizeof(a->section), "%s", section); + a->prog_type = t; + } + } +} + /* Select one variant per group the way the artifact's loader does: walk in * priority order, probe required helpers against this kernel, autoload the * first satisfying variant and disable the rest. Probing uses @@ -1835,6 +1882,7 @@ static void run_libbpf_load(struct validator_result *res) { apply_prog_variants(&res->opts, obj); apply_map_fixups(&res->opts, obj, true); auto_size_maps(obj); + auto_type_programs(obj); int err = bpf_object__load(obj); if (err) { @@ -1946,6 +1994,15 @@ static int write_result_json(const struct validator_result *res) { fprintf(f, "\",\"map_type\":%u,\"max_entries\":%u}", g_autosized[i].map_type, g_autosized[i].max_entries); } fprintf(f, "],\n"); + fprintf(f, " \"auto_typed_programs\": ["); + for (int i = 0; i < g_autotyped_count; i++) { + fprintf(f, "%s{\"name\":\"", i == 0 ? "" : ","); + escape_json_string(f, g_autotyped[i].name); + fprintf(f, "\",\"section\":\""); + escape_json_string(f, g_autotyped[i].section); + fprintf(f, "\",\"prog_type\":%u}", g_autotyped[i].prog_type); + } + fprintf(f, "],\n"); fprintf(f, " \"program_variants\": ["); for (int g = 0; g < res->opts.variant_group_count; g++) { const struct prog_variant_group *grp = &res->opts.variant_groups[g];