Skip to content

Commit

Permalink
features: make HaveProgramHelper more accurate
Browse files Browse the repository at this point in the history
Parse the verifier output to figure out whether a helper call is
really not supported. This makes the result more reliable and
allows us to get rid of asm.BuiltinFunc.Max().

Signed-off-by: Lorenz Bauer <[email protected]>
  • Loading branch information
lmb committed Jan 8, 2025
1 parent b805828 commit 1afe479
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 18 deletions.
6 changes: 0 additions & 6 deletions asm/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ package asm
// BuiltinFunc is a built-in eBPF function.
type BuiltinFunc int32

func (_ BuiltinFunc) Max() BuiltinFunc {
return maxBuiltinFunc - 1
}

// eBPF built-in functions
//
// You can regenerate this list using the following gawk script:
Expand Down Expand Up @@ -237,8 +233,6 @@ const (
FnUserRingbufDrain
FnCgrpStorageGet
FnCgrpStorageDelete

maxBuiltinFunc
)

// Call emits a function call.
Expand Down
5 changes: 2 additions & 3 deletions asm/func_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 39 additions & 9 deletions features/prog.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package features
import (
"errors"
"fmt"
"os"
"strings"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
Expand Down Expand Up @@ -229,10 +229,6 @@ var helperCache = internal.NewFeatureCache(func(key helperKey) *internal.Feature
//
// Probe results are cached and persist throughout any process capability changes.
func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
if helper > helper.Max() {
return os.ErrInvalid
}

return helperCache.Result(helperKey{pt, helper})
}

Expand Down Expand Up @@ -267,30 +263,64 @@ func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
}

prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
LogDisabled: true,
LogLevel: 1,
})
if err == nil {
prog.Close()
}

var verr *ebpf.VerifierError
if !errors.As(err, &verr) {
return err
}

helperTag := fmt.Sprintf("#%d", helper)

switch {
// EACCES occurs when attempting to create a program probe with a helper
// while the register args when calling this helper aren't set up properly.
// We interpret this as the helper being available, because the verifier
// returns EINVAL if the helper is not supported by the running kernel.
case errors.Is(err, unix.EACCES):
// TODO: possibly we need to check verifier output here to be sure
err = nil

// EINVAL occurs when attempting to create a program with an unknown helper.
case errors.Is(err, unix.EINVAL):
// TODO: possibly we need to check verifier output here to be sure
err = ebpf.ErrNotSupported
// https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10663
if logContainsAll(verr.Log, "invalid func", helperTag) {
return ebpf.ErrNotSupported
}

// https://github.com/torvalds/linux/blob/09a0fa92e5b45e99cf435b2fbf5ebcf889cf8780/kernel/bpf/verifier.c#L10668
if logContainsAll(verr.Log, "program of this type cannot use helper", helperTag) {
return ebpf.ErrNotSupported
}

// https://github.com/torvalds/linux/blob/59b418c7063d30e0a3e1f592d47df096db83185c/kernel/bpf/verifier.c#L10204
if logContainsAll(verr.Log, "unknown func", helperTag) {
return ebpf.ErrNotSupported
}
}

return err
}

func logContainsAll(log []string, needles ...string) bool {
first := max(len(log)-5, 0) // Check last 5 lines.
nextLine:
for _, line := range log[first:] {
for _, needle := range needles {
if !strings.Contains(line, needle) {
continue nextLine
}
}

return true
}

return false
}

func helperProbeNotImplemented(pt ebpf.ProgramType) bool {
switch pt {
case ebpf.Extension, ebpf.LSM, ebpf.StructOps, ebpf.Tracing:
Expand Down

0 comments on commit 1afe479

Please sign in to comment.