Can my eBPF tool actually run here, and if not, exactly what needs to change?
kfeatures is a pure-Go library that answers this question.
It probes kernel capabilities at runtime and returns actionable diagnostics: not just unsupported, but why and how to fix it.
if err := kfeatures.Check(kfeatures.FeatureBPFLSM, kfeatures.FeatureBTF); err != nil {
var fe *kfeatures.FeatureError
if errors.As(err, &fe) {
log.Fatalf("%s — %s", fe.Feature, fe.Reason)
// Output: BPF LSM — CONFIG_BPF_LSM=y but 'bpf' not in active LSM list; add lsm=...,bpf to kernel boot params
}
}cilium/ebpf/features answers: "Does this kernel support program type X?"
bpftool feature probe answers: "What BPF features does this kernel have?" (CLI only, not embeddable in Go)
Neither tells you whether your tool can actually run. For example, BPF LSM requires three things simultaneously: CONFIG_BPF_LSM=y in the kernel config, bpf in the active LSM boot parameter list, and the LSM program type supported by the running kernel. cilium/ebpf/features can only check the last one. bpftool can check the first and last, but not the second. Neither provides remediation guidance.
| Capability | cilium/ebpf/features |
bpftool feature probe |
kfeatures |
|---|---|---|---|
| BPF program type probes | ✓ | ✓ | ✓ |
| BPF map type / helper probes | ✓ | ✓ | ✓ † |
BTF availability (/sys/kernel/btf/vmlinux) |
✗ | ✗ * | ✓ |
Kernel config parsing (any CONFIG_*, =y/=m) |
✗ | ✓ | ✓ |
Active LSM list (/sys/kernel/security/lsm) |
✗ | ✗ | ✓ |
| BPF LSM enabled (config + boot params + program type) | ✗ | ✗ | ✓ |
| IMA detection (LSM list + securityfs directory) | ✗ | ✗ | ✓ |
| IMA active measurement (runtime policy detection) | ✗ | ✗ | ✓ |
| Process capabilities (CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON) | ✗ | ✗ | ✓ |
| Unprivileged BPF status | ✗ | ✓ | ✓ |
| Mount-state gates (bpffs/tracefs/custom paths via superblock magic) | ✗ | ✗ | ✓ |
ELF requirement extraction (parse .o, derive requirements) |
✗ | ✗ | ✓ |
| Composite feature validation | ✗ | ✗ | ✓ |
| Actionable diagnostics (remediation steps) | ✗ | ✗ | ✓ |
| Selective probing (minimize overhead) | ✓ ‡ | ✗ § | ✓ |
| Pure Go, no CGO | ✓ | ✗ | ✓ |
| Usable as a Go library | ✓ | ✗ | ✓ |
* bpftool checks CONFIG_DEBUG_INFO_BTF in the kernel config but does not verify /sys/kernel/btf/vmlinux exists.
† Exposed in kfeatures as parameterized requirements (RequireMapType, RequireProgramHelper) consumed by Check(...).
‡ cilium/ebpf/features is per-function: callers invoke individual probe functions on demand.
§ bpftool feature probe runs the full probe set on every invocation.
Other Go projects (libbpfgo, Tetragon, Falco libs) have some feature detection built in, but none is a standalone reusable library. They are either CGO-dependent, tightly coupled to their parent project, or written in C/C++.
go get github.com/leodido/kfeaturesValidate that required kernel features are available:
import (
"errors"
"log"
"github.com/leodido/kfeatures"
)
if err := kfeatures.Check(kfeatures.FeatureBPFLSM, kfeatures.FeatureBTF); err != nil {
var fe *kfeatures.FeatureError
if errors.As(err, &fe) {
log.Fatalf("kernel not ready: %s — %s", fe.Feature, fe.Reason)
}
}Combine Feature enums with parameterized workload requirements:
import (
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/leodido/kfeatures"
)
err := kfeatures.Check(
kfeatures.FeatureBTF,
kfeatures.RequireProgramType(ebpf.XDP),
kfeatures.RequireMapType(ebpf.Hash),
kfeatures.RequireProgramHelper(ebpf.XDP, asm.FnMapLookupElem),
)Gate on a filesystem mounted at an arbitrary path with the expected superblock magic — useful when bpffs lives somewhere other than /sys/fs/bpf:
import (
"github.com/leodido/kfeatures"
"golang.org/x/sys/unix"
)
err := kfeatures.Check(
kfeatures.RequireMount("/run/bpf", unix.BPF_FS_MAGIC),
)Magic constants come from golang.org/x/sys/unix (e.g. unix.BPF_FS_MAGIC, unix.TRACEFS_MAGIC, unix.CGROUP2_SUPER_MAGIC).
FeatureGroup packages a set of requirements as a single value you can pass anywhere a Requirement is accepted:
var TracingTool = kfeatures.FeatureGroup{
kfeatures.FeatureBTF,
kfeatures.FeatureKprobeMulti,
kfeatures.RequireProgramType(ebpf.Kprobe),
}
if err := kfeatures.Check(TracingTool); err != nil {
log.Fatal(err)
}Point FromELF at an eBPF .o and get back a FeatureGroup describing its program types, map types, and helper-per-program requirements — directly consumable by Check:
reqs, err := kfeatures.FromELF("./bpf/probe.o")
if err != nil {
log.Fatal(err)
}
if err := kfeatures.Check(reqs); err != nil {
log.Fatalf("kernel cannot run probe.o: %v", err)
}Output is deterministic (deduplicated, stably ordered). Unknown ELF kinds fail closed.
Check returns the diagnosis for the first failing feature. To inspect any feature against a single probe snapshot, call Diagnose directly:
sf, _ := kfeatures.Probe()
if !sf.BPFLSMEnabled.Supported {
fmt.Println(sf.Diagnose(kfeatures.FeatureBPFLSM))
// CONFIG_BPF_LSM=y but 'bpf' not in active LSM list; add lsm=...,bpf to kernel boot params
}Probe all features for diagnostics and reporting:
sf, err := kfeatures.Probe()
if err != nil {
log.Fatal(err)
}
fmt.Println(sf)Sample output (truncated):
Kernel: 6.1.0-generic
Program Types:
LSM: yes
kprobe: yes
kprobe.multi: yes
Core:
BTF: yes
Security Subsystems:
BPF LSM enabled: yes
IMA enabled: no
IMA directory: yes
Active LSMs: lockdown, capability, yama, apparmor, bpf
Filesystems:
tracefs: yes
bpffs: yes
Individual fields are typed and inspectable programmatically — see SystemFeatures.
Probe only what you need:
sf, err := kfeatures.ProbeWith(
kfeatures.WithProgramTypes(ebpf.LSM, ebpf.Kprobe),
kfeatures.WithSecuritySubsystems(),
kfeatures.WithCapabilities(),
)WithX options select probe scope. They do not define requirements — use Check(...) for gating.
A CLI is included for operator diagnostics and CI/CD gating:
go install github.com/leodido/kfeatures/cmd/kfeatures@latestkfeatures probe # probe all features
kfeatures check --require bpf-lsm,btf,cap-bpf # exit 0 if met, 1 otherwise
kfeatures probe --json # JSON output
kfeatures config # display kernel config| Category | Features |
|---|---|
| Program types | LSM, kprobe, kprobe.multi, tracepoint, fentry |
| Core | BTF availability (CO-RE) |
| Security | BPF LSM enabled, IMA enabled, IMA active measurement, active LSM list |
| Capabilities and runtime gates | CAP_BPF, CAP_SYS_ADMIN, CAP_PERFMON, unprivileged BPF disabled, BPF stats enabled |
| Syscalls | bpf(), perf_event_open() |
| JIT | enabled, hardened, kallsyms, memory limit, CONFIG_BPF_JIT_ALWAYS_ON |
| Filesystems | tracefs, debugfs, securityfs, bpffs (gated tracefs/bpffs checks verify the filesystem is mounted with the expected superblock magic) |
| Custom mount gates | any path + superblock magic via RequireMount |
| Namespaces | initial user namespace, initial PID namespace |
| Parameterized workload requirements | program type, map type, helper-per-program-type via requirement items |
| ELF-derived requirements | program/map types and helper-per-program requirements via FromELF |
| Mitigation context | Spectre v1/v2 vulnerability status |
| Kernel config | CONFIG_BPF_LSM, CONFIG_IMA, CONFIG_DEBUG_INFO_BTF, CONFIG_FPROBE, any CONFIG_* |
Pre-1.0. The public API may change between minor versions; breaking changes are
called out explicitly in CHANGELOG.md. The FromELF contract
(signature, determinism, fail-closed semantics) is frozen — see
CONTRIBUTING.md.
- Linux for runtime probing/checking (uses Linux-specific syscalls and sysfs).
FromELFis parser-only and works on any platform.- Some probes require
CAP_BPForCAP_SYS_ADMIN.
See CONTRIBUTING.md for the API model, the feature-addition checklist, and the development workflow.