diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-29 11:00:30 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-29 11:00:30 +0300 |
| commit | 49662fd127b9d9db062a052a5249750a1fc1b8a3 (patch) | |
| tree | e1b16ede061968b2185e1e381134687eb2bbec18 /internal | |
| parent | 6561d63e5a47092d8b8f2a8128ad05ca9ffd2203 (diff) | |
test(generate): lock in madvise BPF handler field wiring
Audit of madvise(2) (int madvise(void *addr, size_t length, int advice))
confirmed the existing classification and BPF wiring are correct: KindMem /
FamilyMemory, addr=args[0], length=args[1], advice (flags-like) at args[2],
length2=0, and the int return captured generically as UNCLASSIFIED. This is
correctly distinct from process_madvise(2) (KindFd, pidfd at args[0]).
Unlike its KindMem siblings (mprotect, mlock2, brk, map_shadow_stack), madvise
lacked a dedicated handler-field lock-in test. Add TestGenerateMemHandlerMadvise
with positive field assertions plus negative guards: advice must come from
args[2] (not args[0]/addr), length2 must stay zero (no second region), and the
exit must return ctx->ret as UNCLASSIFIED.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/generate/codegen_test.go | 34 |
1 files changed, 34 insertions, 0 deletions
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index 09a23a5..cc5ad7c 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -268,6 +268,40 @@ func TestGenerateMemHandlerMprotect(t *testing.T) { requireContains(t, output, "ev->flags = (__u64)ctx->args[2];") } +// TestGenerateMemHandlerMadvise locks in the BPF handler wiring for madvise(2): +// int madvise(void addr[.size], size_t size, int advice). +// The address range is args[0]/args[1] (addr/length) and the `advice` enum +// (MADV_DONTNEED, MADV_WILLNEED, ...) is flags-like, so it maps to ev->flags at +// args[2]. There is no second length, so length2 must stay zero. madvise returns +// int 0 on success / -1 on error, captured generically via ev->ret as +// UNCLASSIFIED like every other KindMem exit. This must NOT be confused with the +// sibling process_madvise(2), whose first arg is a pidfd and is classified KindFd +// (covered by TestGenerateProcessMadviseHandlerUsesFirstArgumentAsFd). +func TestGenerateMemHandlerMadvise(t *testing.T) { + output := GenerateTracepointsC(mustParseAll(t, syntheticPair("madvise"))) + + requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_madvise")`) + requireContains(t, output, "struct mem_event *ev") + requireContains(t, output, "ev->event_type = ENTER_MEM_EVENT;") + requireContains(t, output, "ev->addr = (__u64)ctx->args[0];") + requireContains(t, output, "ev->length = (__u64)ctx->args[1];") + requireContains(t, output, "ev->length2 = 0;") + requireContains(t, output, "ev->flags = (__u64)ctx->args[2];") + // The advice enum lives at args[2]; addr (args[0]) must never be reused as flags. + if strings.Contains(output, "ev->flags = (__u64)ctx->args[0];") { + t.Error("madvise handler must use args[2] (advice) as flags, not args[0] (addr)") + } + // madvise has no second length region (unlike mremap/pkey_mprotect). + if strings.Contains(output, "ev->length2 = (__u64)ctx->args") { + t.Error("madvise handler must keep length2 zero; it has no second length argument") + } + // The exit handler returns the int status generically, not via a byte-count + // classification. + requireContains(t, output, `SEC("tracepoint/syscalls/sys_exit_madvise")`) + requireContains(t, output, "ev->ret = ctx->ret;") + requireContains(t, output, "ev->ret_type = UNCLASSIFIED;") +} + func TestGenerateMemHandlerPkeyMprotect(t *testing.T) { output := GenerateTracepointsC(mustParseAll(t, syntheticPair("pkey_mprotect"))) |
