Repository: cilium/ebpf
Pull request #792 (2022-09-14 12:49:28)

Provide 15-byte context inputs to `Program.{Run,Benchmark,Test}`
Centralize allocation of the buffer to avoid some duplication and ease future changes to the minimum buffer size.

The patch that introduced this regression doesn't officially land until 6.0, but it has already been backported to kernels as early as 5.15.65 (potentially even earlier?).

cc @dylandreimerink 


Patch:
diff --git a/internal/prog.go b/internal/prog.go
new file mode 100644
index 0000000..d629145
--- /dev/null
+++ b/internal/prog.go
@@ -0,0 +1,11 @@
+package internal
+
+// EmptyBPFContext is the smallest-possible BPF input context to be used for
+// invoking `Program.{Run,Benchmark,Test}`.
+//
+// Programs require a context input buffer of at least 15 bytes. Looking in
+// net/bpf/test_run.c, bpf_test_init() requires that the input is at least
+// ETH_HLEN (14) bytes. As of Linux commit fd18942 ("bpf: Don't redirect packets
+// with invalid pkt_len"), it also requires the skb to be non-empty after
+// removing the Layer 2 header.
+var EmptyBPFContext = make([]byte, 15)
diff --git a/prog.go b/prog.go
index 3d2b8b2..4193bc5 100644
--- a/prog.go
+++ b/prog.go
@@ -586,7 +586,7 @@ func (p *Program) Run(opts *RunOptions) (uint32, error) {
 // run or an error. reset is called whenever the benchmark syscall is
 // interrupted, and should be set to testing.B.ResetTimer or similar.
 //
-// Note: profiling a call to this function will skew it's results, see
+// Note: profiling a call to this function will skew its results, see
 // https://github.com/cilium/ebpf/issues/24
 //
 // This function requires at least Linux 4.12.
@@ -624,12 +624,7 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() e
 	}
 	defer prog.Close()
 
-	// Programs require at least 15 bytes input
-	// Looking in net/bpf/test_run.c, bpf_test_init() requires that the input is
-	// at least ETH_HLEN (14) bytes.  A recent patch[0] also ensures that the skb
-	// is not empty after the layer 2 header is removed.
-	// [0]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fd1894224407c484f652ad456e1ce423e89bb3eb
-	in := make([]byte, 15)
+	in := internal.EmptyBPFContext
 	attr := sys.ProgRunAttr{
 		ProgFd:     uint32(prog.FD()),
 		DataSizeIn: uint32(len(in)),


Test patch:
diff --git a/btf/core_reloc_test.go b/btf/core_reloc_test.go
index 5199089..f0956ac 100644
--- a/btf/core_reloc_test.go
+++ b/btf/core_reloc_test.go
@@ -54,7 +54,7 @@ func TestCORERelocationLoad(t *testing.T) {
 				}
 				defer prog.Close()
 
-				ret, _, err := prog.Test(make([]byte, 14))
+				ret, _, err := prog.Test(internal.EmptyBPFContext)
 				testutils.SkipIfNotSupported(t, err)
 				if err != nil {
 					t.Fatal("Error when running:", err)
@@ -96,7 +96,7 @@ func TestCORERelocationRead(t *testing.T) {
 				}
 				defer prog.Close()
 
-				ret, _, err := prog.Test(make([]byte, 14))
+				ret, _, err := prog.Test(internal.EmptyBPFContext)
 				testutils.SkipIfNotSupported(t, err)
 				if err != nil {
 					t.Fatal("Error when running:", err)
diff --git a/collection_test.go b/collection_test.go
index 064bf55..b4d3752 100644
--- a/collection_test.go
+++ b/collection_test.go
@@ -176,7 +176,7 @@ func TestCollectionSpecRewriteMaps(t *testing.T) {
 	}
 	defer coll.Close()
 
-	ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14))
+	ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
 	if err != nil {
 		t.Fatal(err)
@@ -242,7 +242,7 @@ func TestCollectionSpecMapReplacements(t *testing.T) {
 	}
 	defer coll.Close()
 
-	ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14))
+	ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
 	if err != nil {
 		t.Fatal(err)
diff --git a/elf_reader_test.go b/elf_reader_test.go
index 385b645..84adcd8 100644
--- a/elf_reader_test.go
+++ b/elf_reader_test.go
@@ -216,7 +216,7 @@ func TestLoadCollectionSpec(t *testing.T) {
 		}
 		defer coll.Close()
 
-		ret, _, err := coll.Programs["xdp_prog"].Test(make([]byte, 14))
+		ret, _, err := coll.Programs["xdp_prog"].Test(internal.EmptyBPFContext)
 		if err != nil {
 			t.Fatal("Can't run program:", err)
 		}
@@ -255,7 +255,7 @@ func TestDataSections(t *testing.T) {
 	}
 	defer obj.Program.Close()
 
-	ret, _, err := obj.Program.Test(make([]byte, 14))
+	ret, _, err := obj.Program.Test(internal.EmptyBPFContext)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -462,7 +462,7 @@ func TestStringSection(t *testing.T) {
 	}
 
 	_, err = prog.Run(&RunOptions{
-		Data: make([]byte, 14), // Min size for XDP programs
+		Data: internal.EmptyBPFContext, // Min size for XDP programs
 	})
 	if err != nil {
 		t.Fatalf("prog run: %s", err)
@@ -530,7 +530,7 @@ func TestTailCall(t *testing.T) {
 		defer obj.TailMain.Close()
 		defer obj.ProgArray.Close()
 
-		ret, _, err := obj.TailMain.Test(make([]byte, 14))
+		ret, _, err := obj.TailMain.Test(internal.EmptyBPFContext)
 		testutils.SkipIfNotSupported(t, err)
 		if err != nil {
 			t.Fatal(err)
@@ -569,7 +569,7 @@ func TestSubprogRelocation(t *testing.T) {
 		defer obj.Main.Close()
 		defer obj.HashMap.Close()
 
-		ret, _, err := obj.Main.Test(make([]byte, 14))
+		ret, _, err := obj.Main.Test(internal.EmptyBPFContext)
 		testutils.SkipIfNotSupported(t, err)
 		if err != nil {
 			t.Fatal(err)
diff --git a/info_test.go b/info_test.go
index b30aab7..9655387 100644
--- a/info_test.go
+++ b/info_test.go
@@ -246,7 +246,7 @@ func BenchmarkStats(b *testing.B) {
 // be of a specific value after a call to Test() is therefore not possible.
 // See https://golang.org/doc/go1.14#runtime for more details.
 func testStats(prog *Program) error {
-	in := make([]byte, 14)
+	in := internal.EmptyBPFContext
 
 	stats, err := EnableStats(uint32(unix.BPF_STATS_RUN_TIME))
 	if err != nil {
diff --git a/linker_test.go b/linker_test.go
index b4e04a6..94e3d32 100644
--- a/linker_test.go
+++ b/linker_test.go
@@ -47,7 +47,7 @@ func TestFindReferences(t *testing.T) {
 	}
 	defer prog.Close()
 
-	ret, _, err := prog.Test(make([]byte, 14))
+	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -93,7 +93,7 @@ func TestForwardFunctionDeclaration(t *testing.T) {
 		}
 		defer prog.Close()
 
-		ret, _, err := prog.Test(make([]byte, 14))
+		ret, _, err := prog.Test(internal.EmptyBPFContext)
 		if err != nil {
 			t.Fatal("Running program:", err)
 		}
diff --git a/perf/reader_test.go b/perf/reader_test.go
index 396a7ba..f419551 100644
--- a/perf/reader_test.go
+++ b/perf/reader_test.go
@@ -32,7 +32,7 @@ func TestPerfReader(t *testing.T) {
 	}
 	defer rd.Close()
 
-	ret, _, err := prog.Test(make([]byte, 14))
+	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
 	if err != nil {
 		t.Fatal(err)
@@ -236,7 +236,7 @@ func TestPerfReaderLostSample(t *testing.T) {
 	}
 	defer rd.Close()
 
-	ret, _, err := prog.Test(make([]byte, 14))
+	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
 	if err != nil {
 		t.Fatal(err)
@@ -338,7 +338,7 @@ func TestPause(t *testing.T) {
 	}
 
 	// Write a sample. The reader should read it.
-	ret, _, err := prog.Test(make([]byte, 14))
+	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
 	if err != nil || ret != 0 {
 		t.Fatal("Can't write sample")
@@ -357,7 +357,7 @@ func TestPause(t *testing.T) {
 		_, err := rd.Read()
 		errChan <- err
 	}()
-	ret, _, err = prog.Test(make([]byte, 14))
+	ret, _, err = prog.Test(internal.EmptyBPFContext)
 	if err == nil && ret == 0 {
 		t.Fatal("Unexpectedly wrote sample while paused")
 	} // else Success
@@ -378,7 +378,7 @@ func TestPause(t *testing.T) {
 	if err = rd.Resume(); err != nil {
 		t.Fatal(err)
 	}
-	ret, _, err = prog.Test(make([]byte, 14))
+	ret, _, err = prog.Test(internal.EmptyBPFContext)
 	if err != nil || ret != 0 {
 		t.Fatal("Can't write sample")
 	}
@@ -414,7 +414,7 @@ func BenchmarkReader(b *testing.B) {
 	}
 	defer rd.Close()
 
-	buf := make([]byte, 14)
+	buf := internal.EmptyBPFContext
 
 	b.ResetTimer()
 	b.ReportAllocs()
@@ -441,7 +441,7 @@ func BenchmarkReadInto(b *testing.B) {
 	}
 	defer rd.Close()
 
-	buf := make([]byte, 14)
+	buf := internal.EmptyBPFContext
 
 	b.ResetTimer()
 	b.ReportAllocs()
@@ -503,7 +503,7 @@ func ExampleReader() {
 	defer rd.Close()
 
 	// Writes out a sample with content 1,2,3,4,4
-	ret, _, err := prog.Test(make([]byte, 14))
+	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	if err != nil || ret != 0 {
 		panic("Can't write sample")
 	}
@@ -531,7 +531,7 @@ func ExampleReader_ReadInto() {
 
 	for i := 0; i < 2; i++ {
 		// Write out two samples
-		ret, _, err := prog.Test(make([]byte, 14))
+		ret, _, err := prog.Test(internal.EmptyBPFContext)
 		if err != nil || ret != 0 {
 			panic("Can't write sample")
 		}
diff --git a/prog_test.go b/prog_test.go
index 722d7f5..f531919 100644
--- a/prog_test.go
+++ b/prog_test.go
@@ -28,7 +28,7 @@ func TestProgramRun(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "4.8", "XDP program")
 
 	pat := []byte{0xDE, 0xAD, 0xBE, 0xEF}
-	buf := make([]byte, 14)
+	buf := internal.EmptyBPFContext
 
 	// r1  : ctx_start
 	// r1+4: ctx_end
@@ -110,10 +110,10 @@ func TestProgramRunWithOptions(t *testing.T) {
 	}
 	defer prog.Close()
 
-	buf := make([]byte, 14)
+	buf := internal.EmptyBPFContext
 	xdp := sys.XdpMd{
 		Data:    0,
-		DataEnd: 14,
+		DataEnd: uint32(len(buf)),
 	}
 	xdpOut := sys.XdpMd{}
 	opts := RunOptions{
@@ -176,7 +176,7 @@ func TestProgramRunEmptyData(t *testing.T) {
 func TestProgramBenchmark(t *testing.T) {
 	prog := mustSocketFilter(t)
 
-	ret, duration, err := prog.Benchmark(make([]byte, 14), 1, nil)
+	ret, duration, err := prog.Benchmark(internal.EmptyBPFContext, 1, nil)
 	testutils.SkipIfNotSupported(t, err)
 	if err != nil {
 		t.Fatal("Error from Benchmark:", err)
@@ -220,7 +220,7 @@ func TestProgramTestRunInterrupt(t *testing.T) {
 		// Block this thread in the BPF syscall, so that we can
 		// trigger EINTR by sending a signal.
 		opts := RunOptions{
-			Data:   make([]byte, 14),
+			Data:   internal.EmptyBPFContext,
 			Repeat: math.MaxInt32,
 			Reset: func() {
 				// We don't know how long finishing the
diff --git a/ringbuf/reader_test.go b/ringbuf/reader_test.go
index 7fb3f03..d6633de 100644
--- a/ringbuf/reader_test.go
+++ b/ringbuf/reader_test.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/asm"
+	"github.com/cilium/ebpf/internal"
 	"github.com/cilium/ebpf/internal/testutils"
 	"github.com/google/go-cmp/cmp"
 )
@@ -47,7 +48,7 @@ func TestRingbufReader(t *testing.T) {
 			}
 			defer rd.Close()
 
-			ret, _, err := prog.Test(make([]byte, 14))
+			ret, _, err := prog.Test(internal.EmptyBPFContext)
 			testutils.SkipIfNotSupported(t, err)
 			if err != nil {
 				t.Fatal(err)
@@ -172,7 +173,7 @@ func TestReaderBlocking(t *testing.T) {
 	testutils.SkipOnOldKernel(t, "5.8", "BPF ring buffer")
 
 	prog, events := mustOutputSamplesProg(t, 0, 5)
-	ret, _, err := prog.Test(make([]byte, 14))
+	ret, _, err := prog.Test(internal.EmptyBPFContext)
 	testutils.SkipIfNotSupported(t, err)
 	if err != nil {
 		t.Fatal(err)
@@ -269,7 +270,7 @@ func BenchmarkReader(b *testing.B) {
 			}
 			defer rd.Close()
 
-			buf := make([]byte, 14)
+			buf := internal.EmptyBPFContext
 
 			b.ResetTimer()
 			b.ReportAllocs()
@@ -301,7 +302,7 @@ func BenchmarkReadInto(b *testing.B) {
 	}
 	defer rd.Close()
 
-	buf := make([]byte, 14)
+	buf := internal.EmptyBPFContext
 
 	b.ResetTimer()
 	b.ReportAllocs()