Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions btf/btf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const btfMagic = 0xeB9F
// Errors returned by BTF functions.
var (
ErrNotSupported = internal.ErrNotSupported
ErrNotPermitted = internal.ErrNotPermitted
ErrNotFound = errors.New("not found")
ErrNoExtendedInfo = errors.New("no extended info")
ErrMultipleMatches = errors.New("multiple matching types")
Expand Down
21 changes: 15 additions & 6 deletions btf/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package btf

import (
"errors"
"fmt"
"math"

"github.com/cilium/ebpf/internal"
Expand All @@ -14,10 +15,14 @@ import (
var haveBTF = internal.NewFeatureTest("BTF", func() error {
// 0-length anonymous integer
err := probeBTF(&Int{})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
switch {
case errors.Is(err, unix.EINVAL):
return internal.ErrNotSupported
case errors.Is(err, unix.EPERM):
return fmt.Errorf("%w: %w", internal.ErrNotPermitted, err)
default:
return err
}
return err
}, "4.18")

// haveMapBTF attempts to load a minimal BTF blob containing a Var. It is
Expand All @@ -34,11 +39,12 @@ var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", func() error {
}

err := probeBTF(v)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
// Treat both EINVAL and EPERM as not supported: creating the map may still
// succeed without Btf* attrs.
if errors.Is(err, unix.EINVAL) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, the style as mentioned in #1949 (comment) should also be applied here.

return internal.ErrNotSupported
}
if errors.Is(err, unix.EPERM) {
return fmt.Errorf("%w: %w", internal.ErrNotPermitted, err)
}
return err
}, "5.2")

Expand All @@ -56,9 +62,12 @@ var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", func()
}

err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
if errors.Is(err, unix.EINVAL) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, the style as mentioned in #1949 (comment) should also be applied here.

return internal.ErrNotSupported
}
if errors.Is(err, unix.EPERM) {
return fmt.Errorf("%w: %w", internal.ErrNotPermitted, err)
}
return err
}, "5.0")

Expand Down
10 changes: 7 additions & 3 deletions docs/examples/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ func DocDetectXDP() {
fmt.Println("XDP program type is not supported")
return
}
if errors.Is(err, ebpf.ErrNotPermitted) {
fmt.Println("XDP program type is supported but permission denied")
return
}
if err != nil {
// Feature detection was inconclusive.
//
// Note: always log and investigate these errors! These can be caused
// by a lack of permissions, verifier errors, etc. Unless stated
// otherwise, probes are expected to be conclusive. Please file
// an issue if this is not the case in your environment.
// by verifier errors, etc. Unless stated otherwise, probes are
// expected to be conclusive. Please file an issue if this is not the
// case in your environment.
panic(err)
}

Expand Down
7 changes: 7 additions & 0 deletions features/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package features

import (
"errors"
"fmt"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
Expand Down Expand Up @@ -51,6 +52,8 @@ var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", fu
return nil
case errors.Is(err, unix.EINVAL):
return ebpf.ErrNotSupported
case errors.Is(err, unix.EPERM):
return fmt.Errorf("%w: %w", ebpf.ErrNotPermitted, err)
case err != nil:
return err
}
Expand Down Expand Up @@ -99,6 +102,8 @@ var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", fu
// If CONFIG_FPROBE isn't set.
case errors.Is(err, unix.EOPNOTSUPP):
return ebpf.ErrNotSupported
case errors.Is(err, unix.EPERM):
return fmt.Errorf("%w: %w", ebpf.ErrNotPermitted, err)
case err != nil:
return err
}
Expand Down Expand Up @@ -147,6 +152,8 @@ var haveBPFLinkKprobeSession = internal.NewFeatureTest("bpf_link_kprobe_session"
// If CONFIG_FPROBE isn't set.
case errors.Is(err, unix.EOPNOTSUPP):
return ebpf.ErrNotSupported
case errors.Is(err, unix.EPERM):
return fmt.Errorf("%w: %w", ebpf.ErrNotPermitted, err)
case err != nil:
return err
}
Expand Down
4 changes: 4 additions & 0 deletions features/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ func createMap(attr *sys.MapCreateAttr) error {
// to support the given map type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
return ebpf.ErrNotSupported
// EPERM occurs when the caller lacks permission. The feature exists but
// cannot be used due to insufficient privileges.
case errors.Is(err, unix.EPERM):
return fmt.Errorf("%w: %w", ebpf.ErrNotPermitted, err)
}

return err
Expand Down
4 changes: 4 additions & 0 deletions features/prog.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func probeProgram(spec *ebpf.ProgramSpec) error {
// to support the given prog type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
err = ebpf.ErrNotSupported
// EPERM occurs when the caller lacks permission. The feature exists but
// cannot be used due to insufficient privileges.
case errors.Is(err, unix.EPERM):
err = fmt.Errorf("%w: %w", ebpf.ErrNotPermitted, err)
}

return err
Expand Down
5 changes: 5 additions & 0 deletions internal/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import (
// ErrNotSupported indicates that a feature is not supported.
var ErrNotSupported = errors.New("not supported")

// ErrNotPermitted indicates that an operation was denied by the kernel due to
// insufficient permissions. This is distinct from ErrNotSupported: the feature
// exists in the kernel but the caller lacks permission to use it.
var ErrNotPermitted = errors.New("operation not permitted")

// ErrNotSupportedOnOS indicates that a feature is not supported on the current
// operating system.
var ErrNotSupportedOnOS = fmt.Errorf("%w on %s", ErrNotSupported, runtime.GOOS)
Expand Down
12 changes: 12 additions & 0 deletions internal/feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
"errors"
"fmt"
"runtime"
"strings"
"testing"
Expand Down Expand Up @@ -88,3 +89,14 @@ func TestFeatureTestNotSupportedOnOS(t *testing.T) {
qt.Assert(t, qt.ErrorIs(NewFeatureTest("foo", fn, "1.0")(), sentinel))
}
}

func TestErrNotPermitted(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test feels like we are testing Go standard library behavior instead of our own code, so I don't see a lot of value in this test.

// ErrNotPermitted should be distinct from ErrNotSupported
qt.Assert(t, qt.Not(qt.ErrorIs(ErrNotPermitted, ErrNotSupported)))
qt.Assert(t, qt.Not(qt.ErrorIs(ErrNotSupported, ErrNotPermitted)))

// Wrapped errors should be matchable
wrapped := fmt.Errorf("%w: some details", ErrNotPermitted)
qt.Assert(t, qt.ErrorIs(wrapped, ErrNotPermitted))
qt.Assert(t, qt.Not(qt.ErrorIs(wrapped, ErrNotSupported)))
}
1 change: 1 addition & 0 deletions link/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
type Type = sys.LinkType

var ErrNotSupported = internal.ErrNotSupported
var ErrNotPermitted = internal.ErrNotPermitted

// Link represents a Program attached to a BPF hook.
type Link interface {
Expand Down
6 changes: 6 additions & 0 deletions link/syscalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package link

import (
"errors"
"fmt"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
Expand Down Expand Up @@ -105,6 +106,11 @@ var haveProgQuery = internal.NewFeatureTest("BPF_PROG_QUERY", func() error {
if errors.Is(err, unix.EBADF) {
return nil
}
// EPERM means the kernel recognized the syscall but denied permission.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, the style as mentioned in #1949 (comment) should also be applied here.

// The feature exists but is restricted (e.g., in a user namespace).
if errors.Is(err, unix.EPERM) {
return fmt.Errorf("%w: %w", ErrNotPermitted, err)
}
if err != nil {
return ErrNotSupported
}
Expand Down
4 changes: 4 additions & 0 deletions prog.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import (
// ErrNotSupported is returned whenever the kernel doesn't support a feature.
var ErrNotSupported = internal.ErrNotSupported

// ErrNotPermitted is returned when the kernel denied an operation due to
// insufficient permissions. The feature exists but the caller lacks permission.
var ErrNotPermitted = internal.ErrNotPermitted

// ErrProgIncompatible is returned when a loaded Program is incompatible with a
// given spec.
var ErrProgIncompatible = errors.New("program is incompatible")
Expand Down