Skip to content

Commit 1bff0ee

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 3cdd0e1 commit 1bff0ee

3 files changed

Lines changed: 87 additions & 2 deletions

File tree

btf/feature.go

Lines changed: 19 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,24 @@ 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.Get()
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+
attr.BtfTokenFd = int32(tok.Int())
164+
attr.BtfFlags |= sys.BPF_F_TOKEN_FD
165+
}
166+
}
167+
168+
fd, err := sys.BtfLoad(attr)
152169

153170
if err == nil {
154171
fd.Close()

btf/handle.go

Lines changed: 26 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,20 @@ 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.Get()
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+
attr.BtfTokenFd = int32(tok.Int())
71+
attr.BtfFlags |= sys.BPF_F_TOKEN_FD
72+
}
73+
}
74+
5975
var (
6076
logBuf []byte
6177
err error
@@ -99,6 +115,16 @@ func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
99115
attr.BtfLogLevel = 1
100116
}
101117

118+
if errors.Is(err, unix.EPERM) && tok != nil {
119+
delegated, delErr := tok.Delegated()
120+
if delErr != nil {
121+
return nil, err
122+
}
123+
if !slices.Contains(delegated.Cmds, sys.BPF_BTF_LOAD) {
124+
return nil, fmt.Errorf("%w (BPF token does not allow BPF command BPF_BTF_LOAD)", err)
125+
}
126+
}
127+
102128
if err := haveBTF(); err != nil {
103129
return nil, err
104130
}

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)