Repository: cilium/ebpf
Pull request #94 (2020-05-11 21:59:19)

Friendly map create errors, document rlimit and perf ring garbage
Hi all,

While migrating a project off of gobpf, I encountered some issues loading an ELF binary, most of which have been addressed below. There's another part that deals with overwriting the binary's `version` section with the running kernel's `LINUX_VERSION_CODE`, but that I'd like to address in another PR.

The removal of BTF requirements in data sections and the rlimit changes might need some extra test coverage, but please let me know what you think before I get started on that.

I've added a bit more detailed info about the perf event buffer underrun in [the commit that introduced it](https://github.com/ti-mo/ebpf/commit/2cc7e4625e0baab30bfd8be9d5a5c4e6d8012dd2):
> https://elixir.bootlin.com/linux/v5.5.13/source/kernel/events/core.c#L6611 raw->size is used to size the buffer, but frag->size is used to fill it.

> https://elixir.bootlin.com/linux/v5.5.13/source/kernel/events/core.c#L6827
> While preparing the sample output, raw->size is set to the sum of all
> fragments' sizes minus 4 bytes. However, header->size doesn't include this
> 4-byte deduction.

I think I might've stumbled across a kernel bug, but looking at bcc and gobpf, it seems like many userspace libraries already depend on that behaviour by now and discard those bytes by default. Raw perf events are 64-bit aligned, but the test case wasn't.

ti-mo: Hi @lmb, is this good to go? Any other feedback?

Patch:
diff --git a/elf_reader.go b/elf_reader.go
index cdbdcb5..1d35c8f 100644
--- a/elf_reader.go
+++ b/elf_reader.go
@@ -525,7 +525,7 @@ func uintFromBTF(typ btf.Type) (uint32, error) {
 
 func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
 	if spec == nil {
-		return xerrors.New("data sections require BTF")
+		return xerrors.New("data sections require BTF, make sure all consts are marked as static")
 	}
 
 	for _, sec := range dataSections {
diff --git a/map.go b/map.go
index 5350513..a09e85e 100644
--- a/map.go
+++ b/map.go
@@ -108,6 +108,10 @@ func NewMapFromFD(fd int) (*Map, error) {
 //
 // Creating a map for the first time will perform feature detection
 // by creating small, temporary maps.
+//
+// The caller is responsible for ensuring the process' rlimit is set
+// sufficiently high for locking memory during map creation. This can be done
+// by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMap.
 func NewMap(spec *MapSpec) (*Map, error) {
 	if spec.BTF == nil {
 		return newMapWithBTF(spec, nil)
diff --git a/perf/reader.go b/perf/reader.go
index 652c622..151cc4a 100644
--- a/perf/reader.go
+++ b/perf/reader.go
@@ -59,9 +59,8 @@ type Record struct {
 	CPU int
 
 	// The data submitted via bpf_perf_event_output.
-	// They are padded with 0 to have a 64-bit alignment.
-	// If you are using variable length samples you need to take
-	// this into account.
+	// Due to a kernel bug, this can contain between 0 and 7 bytes of trailing
+	// garbage from the ring depending on the input sample's length.
 	RawSample []byte
 
 	// The number of samples which could not be output, since
@@ -317,9 +316,11 @@ func (pr *Reader) Close() error {
 // Read the next record from the perf ring buffer.
 //
 // The function blocks until there are at least Watermark bytes in one
-// of the per CPU buffers.
+// of the per CPU buffers. Records from buffers below the Watermark
+// are not returned.
 //
-// Records from buffers below the Watermark are not returned.
+// Records can contain between 0 and 7 bytes of trailing garbage from the ring
+// depending on the input sample's length.
 //
 // Calling Close interrupts the function.
 func (pr *Reader) Read() (Record, error) {
diff --git a/syscalls.go b/syscalls.go
index 4a244f0..7d29344 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -1,6 +1,7 @@
 package ebpf
 
 import (
+	"os"
 	"path/filepath"
 	"unsafe"
 
@@ -13,7 +14,7 @@ import (
 
 // Generic errors returned by BPF syscalls.
 var (
-	ErrNotExist = xerrors.New("requested object does not exit")
+	ErrNotExist = xerrors.New("requested object does not exist")
 )
 
 // bpfObjName is a null-terminated string made up of
@@ -185,6 +186,10 @@ func bpfProgAlter(cmd int, attr *bpfProgAlterAttr) error {
 
 func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
 	fd, err := internal.BPF(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
+	if xerrors.Is(err, os.ErrPermission) {
+		return nil, xerrors.New("permission denied or insufficient rlimit to lock memory for map")
+	}
+
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/perf/reader_test.go b/perf/reader_test.go
index e9f99e0..45ccff9 100644
--- a/perf/reader_test.go
+++ b/perf/reader_test.go
@@ -21,7 +21,7 @@ var (
 )
 
 func TestMain(m *testing.M) {
-	err := unix.Setrlimit(8, &unix.Rlimit{
+	err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{
 		Cur: unix.RLIM_INFINITY,
 		Max: unix.RLIM_INFINITY,
 	})