Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Probe kernel bpf2bpf support #657

Merged
merged 3 commits into from May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions internal/btf/core_reloc_test.go
Expand Up @@ -90,8 +90,6 @@ func TestCORERelocationRead(t *testing.T) {
}
defer tgt.Close()

testutils.SkipOnOldKernel(t, "4.16", "bpf2bpf calls")

for _, progSpec := range spec.Programs {
t.Run(progSpec.Name, func(t *testing.T) {
if _, err := tgt.Seek(0, io.SeekStart); err != nil {
Expand All @@ -101,6 +99,7 @@ func TestCORERelocationRead(t *testing.T) {
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
TargetBTF: tgt,
})
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Load program:", err)
}
Expand Down
8 changes: 7 additions & 1 deletion internal/errors.go
Expand Up @@ -14,7 +14,13 @@ import (
// logErr should be the error returned by the syscall that generated
// the log. It is used to check for truncation of the output.
func ErrorWithLog(err error, log []byte, logErr error) error {
logStr := unix.ByteSliceToString(bytes.Trim(log, "\t\r\n "))
// Convert verifier log C string by truncating it on the first 0 byte
// and trimming trailing whitespace before interpreting as a Go string.
if i := bytes.IndexByte(log, 0); i != -1 {
log = log[:i]
}
logStr := string(bytes.Trim(log, "\t\r\n "))

if errors.Is(logErr, unix.ENOSPC) {
logStr += " (truncated...)"
}
Expand Down
30 changes: 30 additions & 0 deletions internal/errors_test.go
@@ -0,0 +1,30 @@
package internal

import (
"errors"
"testing"

"github.com/cilium/ebpf/internal/unix"
)

func TestErrorWithLog(t *testing.T) {
b := []byte("unreachable insn 28")
b = append(b,
0xa, // \n
0xd, // \r
0x9, // \t
0x20, // space
0, 0, // trailing NUL bytes
)

err := ErrorWithLog(errors.New("test"), b, unix.ENOSPC)

want := "test: unreachable insn 28 (truncated...)"
got := err.Error()

t.Log(got)

if want != got {
t.Fatalf("\nwant: %s\ngot: %s", want, got)
}
}
11 changes: 11 additions & 0 deletions linker.go
Expand Up @@ -110,6 +110,17 @@ func findReferences(progs map[string]*ProgramSpec) error {
return nil
}

// hasReferences returns true if insns contains one or more bpf2bpf
// function references.
func hasReferences(insns asm.Instructions) bool {
for _, i := range insns {
if i.IsFunctionReference() {
return true
}
}
return false
}

// applyRelocations collects and applies any CO-RE relocations in insns.
//
// Passing a nil target will relocate against the running kernel. insns are
Expand Down
6 changes: 2 additions & 4 deletions linker_test.go
Expand Up @@ -42,9 +42,8 @@ func TestFindReferences(t *testing.T) {
t.Fatal(err)
}

testutils.SkipOnOldKernel(t, "4.16", "bpf2bpf calls")

prog, err := NewProgram(progs["entrypoint"])
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}
Expand All @@ -66,8 +65,6 @@ func TestForwardFunctionDeclaration(t *testing.T) {
t.Fatal(err)
}

testutils.SkipOnOldKernel(t, "4.16", "bpf2bpf calls")

if coll.ByteOrder != internal.NativeEndian {
return
}
Expand All @@ -91,6 +88,7 @@ func TestForwardFunctionDeclaration(t *testing.T) {
spec.BTF = nil

prog, err := NewProgram(spec)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 6 additions & 0 deletions prog.go
Expand Up @@ -361,6 +361,12 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
}
}

if (errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM)) && hasReferences(spec.Instructions) {
if err := haveBPFToBPFCalls(); err != nil {
return nil, fmt.Errorf("load program: %w", internal.ErrorWithLog(err, logBuf, logErr))
}
}

if errors.Is(logErr, unix.EPERM) && len(logBuf) > 0 && logBuf[0] == 0 {
// EPERM due to RLIMIT_MEMLOCK happens before the verifier, so we can
// check that the log is empty to reduce false positives.
Expand Down
1 change: 1 addition & 0 deletions prog_test.go
Expand Up @@ -374,6 +374,7 @@ func TestProgramWithUnsatisfiedMap(t *testing.T) {
progSpec.ByteOrder = nil

_, err = NewProgram(progSpec)
testutils.SkipIfNotSupported(t, err)
if !errors.Is(err, asm.ErrUnsatisfiedMapReference) {
t.Fatal("Expected an error wrapping asm.ErrUnsatisfiedMapReference, got", err)
}
Expand Down
46 changes: 35 additions & 11 deletions syscalls.go
Expand Up @@ -38,6 +38,21 @@ func invalidBPFObjNameChar(char rune) bool {
}
}

func progLoad(insns asm.Instructions, typ ProgramType, license string) (*sys.FD, error) {
buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
if err := insns.Marshal(buf, internal.NativeEndian); err != nil {
return nil, err
}
bytecode := buf.Bytes()

return sys.ProgLoad(&sys.ProgLoadAttr{
ProgType: sys.ProgType(typ),
License: sys.NewStringPointer(license),
Insns: sys.NewSlicePointer(bytecode),
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
})
}

var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() error {
_, err := sys.MapCreate(&sys.MapCreateAttr{
MapType: sys.MapType(ArrayOfMaps),
Expand Down Expand Up @@ -226,21 +241,30 @@ var haveProbeReadKernel = internal.FeatureTest("bpf_probe_read_kernel", "5.5", f
asm.FnProbeReadKernel.Call(),
asm.Return(),
}
buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
if err := insns.Marshal(buf, internal.NativeEndian); err != nil {
return err
}
bytecode := buf.Bytes()

fd, err := sys.ProgLoad(&sys.ProgLoadAttr{
ProgType: sys.ProgType(Kprobe),
License: sys.NewStringPointer("GPL"),
Insns: sys.NewSlicePointer(bytecode),
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
})
fd, err := progLoad(insns, Kprobe, "GPL")
if err != nil {
return internal.ErrNotSupported
}
_ = fd.Close()
return nil
})

var haveBPFToBPFCalls = internal.FeatureTest("bpf2bpf calls", "4.16", func() error {
insns := asm.Instructions{
asm.Call.Label("prog2").WithSymbol("prog1"),
asm.Return(),
asm.Mov.Imm(asm.R0, 0).WithSymbol("prog2"),
asm.Return(),
}

fd, err := progLoad(insns, SocketFilter, "MIT")
ti-mo marked this conversation as resolved.
Show resolved Hide resolved
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
if err != nil {
return err
}
_ = fd.Close()
return nil
})
4 changes: 4 additions & 0 deletions syscalls_test.go
Expand Up @@ -54,3 +54,7 @@ func TestHaveInnerMaps(t *testing.T) {
func TestHaveProbeReadKernel(t *testing.T) {
testutils.CheckFeatureTest(t, haveProbeReadKernel)
}

func TestHaveBPFToBPFCalls(t *testing.T) {
testutils.CheckFeatureTest(t, haveBPFToBPFCalls)
}