Skip to content

Commit c2149ea

Browse files
Add ambient token support for BTF
This commit adds token support for BTF. It uses the "ambient" token approach, which means that the token is not explicitly passed by a user but instead we always attempt to create a token when loading BTF and fall back to loading without a token if token creation fails. This commit also adds rich errors for token related failure. If loading a BTF fails with EPERM we check if the token is missing the BPF command permission which would explain the failure. If we find an issue, we enrich the error to assist users in debugging token related issues. Signed-off-by: Dylan Reimerink <dylan.reimerink@isovalent.com>
1 parent d93c6ea commit c2149ea

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

btf/feature.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package btf
22

33
import (
44
"errors"
5+
"fmt"
56
"math"
67

78
"github.com/cilium/ebpf/internal"
9+
"github.com/cilium/ebpf/internal/platform"
810
"github.com/cilium/ebpf/internal/sys"
11+
"github.com/cilium/ebpf/internal/token"
912
"github.com/cilium/ebpf/internal/unix"
1013
)
1114

@@ -145,10 +148,25 @@ func probeBTF(typ Type) error {
145148
return err
146149
}
147150

148-
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
151+
attr := &sys.BtfLoadAttr{
149152
Btf: sys.SlicePointer(buf),
150153
BtfSize: uint32(len(buf)),
151-
})
154+
}
155+
156+
if platform.IsLinux {
157+
tok, err := token.Create()
158+
if err != nil && !errors.Is(err, token.ErrTokenNotAvailable) {
159+
return fmt.Errorf("get BPF token: %w", err)
160+
}
161+
162+
if tok != nil {
163+
defer tok.Close()
164+
attr.BtfTokenFd = int32(tok.Int())
165+
attr.BtfFlags |= sys.BPF_F_TOKEN_FD
166+
}
167+
}
168+
169+
fd, err := sys.BtfLoad(attr)
152170

153171
if err == nil {
154172
fd.Close()

btf/handle.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"fmt"
66
"math"
77
"os"
8+
"slices"
89

910
"github.com/cilium/ebpf/internal"
1011
"github.com/cilium/ebpf/internal/platform"
1112
"github.com/cilium/ebpf/internal/sys"
13+
"github.com/cilium/ebpf/internal/token"
1214
"github.com/cilium/ebpf/internal/unix"
1315
)
1416

@@ -56,6 +58,21 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
5658
BtfSize: uint32(len(btf)),
5759
}
5860

61+
var tok *token.Token
62+
if platform.IsLinux {
63+
var err error
64+
tok, err = token.Create()
65+
if err != nil && !errors.Is(err, token.ErrTokenNotAvailable) {
66+
return nil, fmt.Errorf("get BPF token: %w", err)
67+
}
68+
69+
if tok != nil {
70+
defer tok.Close()
71+
attr.BtfTokenFd = int32(tok.Int())
72+
attr.BtfFlags |= sys.BPF_F_TOKEN_FD
73+
}
74+
}
75+
5976
var (
6077
logBuf []byte
6178
err error
@@ -99,6 +116,12 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
99116
attr.BtfLogLevel = 1
100117
}
101118

119+
if errors.Is(err, unix.EPERM) {
120+
if tok != nil && !slices.Contains(tok.Mount.Cmds, sys.BPF_BTF_LOAD) {
121+
return nil, fmt.Errorf("%w (BPF token does not allow BPF command BPF_BTF_LOAD)", err)
122+
}
123+
}
124+
102125
if err := haveBTF(); err != nil {
103126
return nil, err
104127
}

btf/handle_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import (
44
"fmt"
55
"testing"
66

7+
"github.com/go-quicktest/qt"
8+
79
"github.com/cilium/ebpf/btf"
10+
"github.com/cilium/ebpf/internal/sys"
811
"github.com/cilium/ebpf/internal/testutils"
12+
"github.com/cilium/ebpf/internal/unix"
913
)
1014

1115
func TestHandleIterator(t *testing.T) {
@@ -83,6 +87,44 @@ func TestParseModuleSplitSpec(t *testing.T) {
8387
}
8488
}
8589

90+
func TestNewHandleFromBTFWithToken(t *testing.T) {
91+
b, err := btf.NewBuilder([]btf.Type{
92+
&btf.Int{Name: "example", Size: 4, Encoding: btf.Unsigned},
93+
})
94+
qt.Assert(t, qt.IsNil(err))
95+
buf, err := b.Marshal(nil, nil)
96+
qt.Assert(t, qt.IsNil(err))
97+
98+
t.Run("no-cmd", func(t *testing.T) {
99+
if testutils.RunWithToken(t, testutils.Delegated{
100+
Cmds: []sys.Cmd{},
101+
// We need to delegate at least one permission, so picking a random map type that we don't use in this test.
102+
Maps: []sys.MapType{sys.BPF_MAP_TYPE_ARRAY},
103+
}) {
104+
return
105+
}
106+
107+
h, err := btf.NewHandleFromRawBTF(buf)
108+
testutils.SkipIfNotSupported(t, err)
109+
qt.Assert(t, qt.ErrorIs(err, unix.EPERM))
110+
h.Close()
111+
})
112+
113+
t.Run("success", func(t *testing.T) {
114+
if testutils.RunWithToken(t, testutils.Delegated{
115+
Cmds: []sys.Cmd{sys.BPF_BTF_LOAD},
116+
Maps: []sys.MapType{},
117+
}) {
118+
return
119+
}
120+
121+
h, err := btf.NewHandleFromRawBTF(buf)
122+
testutils.SkipIfNotSupported(t, err)
123+
qt.Assert(t, qt.IsNil(err))
124+
h.Close()
125+
})
126+
}
127+
86128
func ExampleHandleIterator() {
87129
it := new(btf.HandleIterator)
88130
defer it.Handle.Close()

0 commit comments

Comments
 (0)