Build the eBPF programs in a shape the kernel can load - #293
Merged
Merged
Conversation
The three programs under pyisolate/bpf/ could not be loaded on any kernel, and
the tests covering them asserted that certain strings appeared in the .bpf.c
sources, which passes equally well for a program the verifier rejects.
Three defects, each independently fatal to a load:
* The compile command omitted -g, so the objects carried no BTF. All three
programs use BTF-defined maps, which are described entirely by their BTF
type, so libbpf cannot parse the .maps section of an object built without it;
LSM programs additionally need BTF to resolve their attach target.
* Every lsm/* handler declared typed parameters -- `int h(void *file, int ret)`
-- without libbpf's BPF_PROG wrapper to unpack them. An LSM program is called
with one argument, a pointer to the hook's argument array, so `ret` compiled
to a read of r2, a register the caller never sets. Disassembly of the old
lsm/file_open starts `r0 = r2`; the verifier rejects that with "R2 !read_ok".
* resource_guard.bpf.c declared two `struct { int dummy; }` placeholders in
.maps. Those are not map definitions libbpf can parse, and one is enough to
reject the whole object.
Handlers now take the context array and unpack it by index, with the offset of
the prior LSM decision named per hook so it can be checked against
lsm_hook_defs.h. socket_connect copies sa_family with bpf_probe_read_kernel
rather than dereferencing a kernel pointer that is not BTF-typed. The
placeholder maps and their two dead no-op programs are gone; per-cgroup CPU
accounting was already done for real by account_sched_switch.
The tests now compile the sources with the manager's own command and inspect
the resulting ELF: BTF sections are present, every LSM hook emits its own
program, each one enters with a load through r1, and no program entry reads an
uninitialised argument register. A root-gated test runs the compiled filter
past the real verifier.
Loading and attaching on a live kernel is still only covered by the
PYISOLATE_LIVE_BPF_TESTS=1 tests, which need root, bpftool and BPF-LSM; CI does
not run them. This makes the objects loadable-shaped and regression-tested, not
verified end-to-end.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fs1hTtmF4Hm9h617AG9Gse
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of a series of independent PRs. Orthogonal to #292 — no overlapping files.
The problem
The three programs under
pyisolate/bpf/could not be loaded on any kernel. The tests covering them asserted that certain strings appeared in the.bpf.csources, which passes equally well for a program the verifier rejects:Three defects, each independently fatal to a load.
1. No BTF. The compile command omitted
-g. All three programs use BTF-defined maps (struct { ... } name SEC(".maps")), which are described entirely by their BTF type — libbpf cannot parse the.mapssection of an object built without it. LSM programs additionally need BTF to resolve their attach target.2. LSM handlers read an uninitialised register. Every handler declared typed parameters without libbpf's
BPF_PROGwrapper to unpack them:An LSM program is called with one argument — a pointer to the hook's argument array — so
retcompiled to a read ofr2, which the caller never sets. Disassembling the old object:The verifier rejects that with
R2 !read_ok. All nine hooks had it.3. Placeholder maps.
resource_guard.bpf.cdeclared twostruct { int dummy; }entries in.maps. Those are not map definitions libbpf can parse, and one is enough to reject the whole object.The fix
-gmoved into a singleBPFManager.COMPILE_FLAGS/_compile_commandso the flag cannot drift between the three call sites.PYI_RET_socket_connect 3) so it can be checked againstlsm_hook_defs.h. The entry now compiles tor0 = *(u64 *)(r1 + 0x8).socket_connectcopiessa_familywithbpf_probe_read_kernelinstead of dereferencing a kernel pointer that is not BTF-typed.emit_breach,on_cpu) are gone. Per-cgroup CPU accounting was already done for real byaccount_sched_switch.This also removes the comment claiming the five-register limit forced
sb_mountto drop its last argument — indexing the context array has no such limit.Testing
The tests now compile the sources with the manager's own command and inspect the resulting ELF:
.BTF/.BTF.extpresent on both objects;r1;r2–r5(checked at entry points only — inside.textthat same move is an ordinary call argument);.mapsentries all carry a realBPF_MAP_TYPE_*;test_load_runs_toolchainderives its expectation from_compile_command, so the-gflag cannot silently disappear again.Full suite: 548 passed, 15 skipped. The one failure in my container,
test_apply_confinement_installs_seccomp_and_allows_normal_syscalls, is pre-existing and environmental (seccomp unavailable) and reproduces on unmodifiedmain.pre-commit run --all-filespasses.What this does not claim
I could not load these against a live kernel here — no
bpftool, not root, and no BPF-LSM in this container. This makes the objects loadable-shaped and regression-tested, not verified end-to-end. A new root-gatedtest_verifier_accepts_the_filter_programsruns the compiled filter past the real verifier, but like the existing live test it needsPYISOLATE_LIVE_BPF_TESTS=1and CI does not run it. Worth a privileged CI job; I've noted it in the changelog's known gaps rather than implying more coverage than exists.🤖 Generated with Claude Code
https://claude.ai/code/session_01Fs1hTtmF4Hm9h617AG9Gse
Generated by Claude Code