From 7f98dfa75dbc817c3beb9ac942528187b8839703 Mon Sep 17 00:00:00 2001 From: Nick Zavaritsky Date: Wed, 18 Jun 2025 11:08:52 +0000 Subject: [PATCH] prog: get context out from syscall program Linux syscall program errors on non-nil ctxOut, uses ctxIn for both input and output [1]. We have separate Context and ContextOut fields in RunOptions. It should be possible to capture output from syscall programs which is currently not: non-nil ContextOut triggers EINVAL, while Context is (expectedly) not updated. Implement the following semantics: either Context, or ContextOut or both can be non-nil. Map to ctxIn internally, complain if Context and ContextOut lengths are different. [1] https://elixir.bootlin.com/linux/v6.15.2/source/net/bpf/test_run.c#L1530 Signed-off-by: Nick Zavaritsky --- prog.go | 10 ++++++++++ prog_linux_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/prog.go b/prog.go index c7eead7cc..7643829a5 100644 --- a/prog.go +++ b/prog.go @@ -884,6 +884,16 @@ func (p *Program) run(opts *RunOptions) (uint32, time.Duration, error) { Cpu: opts.CPU, } + if p.Type() == Syscall && ctxIn != nil && ctxOut != nil { + // Linux syscall program errors on non-nil ctxOut, uses ctxIn + // for both input and output. Shield the user from this wart. + if len(ctxIn) != len(ctxOut) { + return 0, 0, errors.New("length mismatch: Context and ContextOut") + } + attr.CtxOut, attr.CtxSizeOut = sys.TypedPointer[uint8]{}, 0 + ctxOut = ctxIn + } + retry: for { err := sys.ProgRun(&attr) diff --git a/prog_linux_test.go b/prog_linux_test.go index 392867b39..0b6b8fd09 100644 --- a/prog_linux_test.go +++ b/prog_linux_test.go @@ -12,6 +12,7 @@ import ( "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/testutils" "github.com/cilium/ebpf/internal/unix" ) @@ -135,3 +136,37 @@ func TestProgramVerifierLogLinux(t *testing.T) { }) qt.Assert(t, qt.IsTrue(len(prog.VerifierLog) > minVerifierLogSize)) } + +func TestProgramTestRunSyscall(t *testing.T) { + testutils.SkipOnOldKernel(t, "5.14", "BPF_PROG_TYPE_SYSCALL") + + prog := mustNewProgram(t, &ProgramSpec{ + Type: Syscall, + Flags: sys.BPF_F_SLEEPABLE, + License: "MIT", + Instructions: []asm.Instruction{ + // fn (ctx *u64) { *ctx++; return *ctx } + asm.LoadMem(asm.R0, asm.R1, 0, asm.DWord), + asm.Add.Imm(asm.R0, 1), + asm.StoreMem(asm.R1, 0, asm.R0, asm.DWord), + asm.Return(), + }, + }, nil) + + // only Context + rc, err := prog.Run(&RunOptions{Context: uint64(42)}) + testutils.SkipIfNotSupported(t, err) + if err != nil { + t.Fatal(err) + } + qt.Assert(t, qt.Equals(rc, 43)) + + // Context and ContextOut + out := uint64(0) + rc, err = prog.Run(&RunOptions{Context: uint64(99), ContextOut: &out}) + if err != nil { + t.Fatal(err) + } + qt.Assert(t, qt.Equals(rc, 100)) + qt.Assert(t, qt.Equals(out, 100)) +}