diff --git a/CHANGELOG.md b/CHANGELOG.md index 0622bc6..86feefd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once a ## [Unreleased] +### Added +- `examples/preload-gate`: a complete, runnable example of using the + `pkg/bpfcompat` library — `ValidateBeforeLoad` as a bpfman-style pre-load gate + (real load on the node's own kernel, no VM). README gains a "Library mode" + section with the example and a real pass/blocked run. + +### Fixed +- README install snippet pinned the stale `v0.1.6` release; bumped to `v0.2.0`. + ## [0.2.0] - 2026-06-27 ### Added diff --git a/README.md b/README.md index 6bd87e4..8b8dc9a 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,33 @@ different bootstrap. bpfcompat implements it (Ignition config over QEMU image, the **RHEL / AlmaLinux 9 (5.14)** profiles are the interim kernel approximation. Full guide: [docs/rhcos-openshift.md](docs/rhcos-openshift.md). +## Library mode: embed the pre-load gate + +Beyond the CLI and the GitHub Action, bpfcompat is an embeddable Go library +([`pkg/bpfcompat`](https://github.com/Kernel-Guard/bpfcompat/tree/main/pkg/bpfcompat)). +`ValidateBeforeLoad` does a real `bpf()` load against the node's **own running +kernel** — no VM, no network — so a loader (e.g. bpfman) can refuse an object +that won't verify *before* it loads it. Host loading is gated behind the +`hostload` build tag and needs `CAP_BPF`/`CAP_SYS_ADMIN`. + +A complete, real example is [`examples/preload-gate`](examples/preload-gate): + +![preload-gate.go — a real program using ValidateBeforeLoad](docs/images/library/library-code.png) + +```sh +go get github.com/kernel-guard/bpfcompat@v0.2.0 +go build -tags hostload -o preload-gate ./examples/preload-gate +sudo ./preload-gate probe.bpf.o +``` + +A compatible object passes; an incompatible one is blocked with the kernel's own +verdict and a stable classification code — exit 0 vs 1: + +![preload-gate real run: a good object loads, a CO-RE failure is blocked](docs/images/library/library-run.png) + +See [`pkg/bpfcompat/README.md`](pkg/bpfcompat/README.md) for the full API. +(Pre-1.0 / experimental.) + ## Try it in CI without your own KVM box GitHub-hosted Linux runners now expose `/dev/kvm`, so the full QEMU VM @@ -227,7 +254,7 @@ guest-side validator binary and the kernel matrices that ship in this repo. the static validator, checksum-verified: ```bash -VER=v0.1.6 +VER=v0.2.0 base="https://github.com/Kernel-Guard/bpfcompat/releases/download/$VER" curl -fsSLO "$base/bpfcompat-linux-amd64" curl -fsSLO "$base/bpfcompat-validator-static-linux-amd64" diff --git a/docs/images/library/library-code.png b/docs/images/library/library-code.png new file mode 100644 index 0000000..caa54fe Binary files /dev/null and b/docs/images/library/library-code.png differ diff --git a/docs/images/library/library-run.png b/docs/images/library/library-run.png new file mode 100644 index 0000000..56d75a5 Binary files /dev/null and b/docs/images/library/library-run.png differ diff --git a/examples/preload-gate/main.go b/examples/preload-gate/main.go new file mode 100644 index 0000000..fd5345d --- /dev/null +++ b/examples/preload-gate/main.go @@ -0,0 +1,45 @@ +// Command preload-gate is a minimal, real example of using bpfcompat as a +// library: validate a compiled eBPF object against the node's own running +// kernel before loading it. This is the bpfman-style pre-load path — a real +// bpf() load, no VM, no network. +// +// Build with the hostload tag (host-kernel loading is gated off by default): +// +// go build -tags hostload -o preload-gate ./examples/preload-gate +// sudo ./preload-gate probe.bpf.o +// +// Exit code: 0 if it would load, 1 if the gate blocks it (or on error). +package main + +import ( + "context" + "fmt" + "os" + + "github.com/kernel-guard/bpfcompat/pkg/bpfcompat" +) + +// preloadGate refuses to load a BPF object that won't verify on THIS kernel. +func preloadGate(ctx context.Context, path string) error { + res, err := bpfcompat.ValidateBeforeLoad(ctx, path) + if err != nil { + return err + } + if !res.OK() { + return fmt.Errorf("refusing to load on %s: [%s] %s", + res.Kernel.Release, res.Classification.Code, res.Classification.Reason) + } + fmt.Printf("ok: loads on %s (%s)\n", res.Kernel.Release, res.Load.Status) + return nil +} + +func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "usage: preload-gate ") + os.Exit(2) + } + if err := preloadGate(context.Background(), os.Args[1]); err != nil { + fmt.Fprintln(os.Stderr, "blocked:", err) + os.Exit(1) + } +}