1 package ebpf
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "runtime"
8 "sync"
9 "unsafe"
10
11 "github.com/cilium/ebpf"
12 "github.com/cilium/ebpf/asm"
13 "github.com/cilium/ebpf/link"
14 "github.com/sirupsen/logrus"
15 "golang.org/x/sys/unix"
16 )
17
18 func nilCloser() error {
19 return nil
20 }
21
22 func findAttachedCgroupDeviceFilters(dirFd int) ([]*ebpf.Program, error) {
23 type bpfAttrQuery struct {
24 TargetFd uint32
25 AttachType uint32
26 QueryType uint32
27 AttachFlags uint32
28 ProgIds uint64
29 ProgCnt uint32
30 }
31
32
33 size := 64
34 retries := 0
35 for retries < 10 {
36 progIds := make([]uint32, size)
37 query := bpfAttrQuery{
38 TargetFd: uint32(dirFd),
39 AttachType: uint32(unix.BPF_CGROUP_DEVICE),
40 ProgIds: uint64(uintptr(unsafe.Pointer(&progIds[0]))),
41 ProgCnt: uint32(len(progIds)),
42 }
43
44
45 _, _, errno := unix.Syscall(unix.SYS_BPF,
46 uintptr(unix.BPF_PROG_QUERY),
47 uintptr(unsafe.Pointer(&query)),
48 unsafe.Sizeof(query))
49 size = int(query.ProgCnt)
50 runtime.KeepAlive(query)
51 if errno != 0 {
52
53 if errno == unix.ENOSPC {
54 retries++
55 continue
56 }
57 return nil, fmt.Errorf("bpf_prog_query(BPF_CGROUP_DEVICE) failed: %w", errno)
58 }
59
60
61 progIds = progIds[:size]
62 programs := make([]*ebpf.Program, 0, len(progIds))
63 for _, progId := range progIds {
64 program, err := ebpf.NewProgramFromID(ebpf.ProgramID(progId))
65 if err != nil {
66
67
68
69
70
71
72
73
74
75 if errors.Is(err, os.ErrPermission) {
76 logrus.Debugf("ignoring existing CGROUP_DEVICE program (prog_id=%v) which cannot be accessed by runc -- likely due to LSM policy: %v", progId, err)
77 continue
78 }
79 return nil, fmt.Errorf("cannot fetch program from id: %w", err)
80 }
81 programs = append(programs, program)
82 }
83 runtime.KeepAlive(progIds)
84 return programs, nil
85 }
86
87 return nil, errors.New("could not get complete list of CGROUP_DEVICE programs")
88 }
89
90 var (
91 haveBpfProgReplaceBool bool
92 haveBpfProgReplaceOnce sync.Once
93 )
94
95
96
97
98
99 func haveBpfProgReplace() bool {
100 haveBpfProgReplaceOnce.Do(func() {
101 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
102 Type: ebpf.CGroupDevice,
103 License: "MIT",
104 Instructions: asm.Instructions{
105 asm.Mov.Imm(asm.R0, 0),
106 asm.Return(),
107 },
108 })
109 if err != nil {
110 logrus.Debugf("checking for BPF_F_REPLACE support: ebpf.NewProgram failed: %v", err)
111 return
112 }
113 defer prog.Close()
114
115 devnull, err := os.Open("/dev/null")
116 if err != nil {
117 logrus.Debugf("checking for BPF_F_REPLACE support: open dummy target fd: %v", err)
118 return
119 }
120 defer devnull.Close()
121
122
123
124
125 err = link.RawAttachProgram(link.RawAttachProgramOptions{
126
127 Target: int(devnull.Fd()),
128
129 Program: prog,
130 Attach: ebpf.AttachCGroupDevice,
131 Flags: unix.BPF_F_ALLOW_MULTI | unix.BPF_F_REPLACE,
132 })
133 if errors.Is(err, unix.EINVAL) {
134
135 return
136 }
137
138 if !errors.Is(err, unix.EBADF) {
139 logrus.Debugf("checking for BPF_F_REPLACE: got unexpected (not EBADF or EINVAL) error: %v", err)
140 }
141 haveBpfProgReplaceBool = true
142 })
143 return haveBpfProgReplaceBool
144 }
145
146
147
148
149
150
151 func LoadAttachCgroupDeviceFilter(insts asm.Instructions, license string, dirFd int) (func() error, error) {
152
153
154 memlockLimit := &unix.Rlimit{
155 Cur: unix.RLIM_INFINITY,
156 Max: unix.RLIM_INFINITY,
157 }
158 _ = unix.Setrlimit(unix.RLIMIT_MEMLOCK, memlockLimit)
159
160
161 oldProgs, err := findAttachedCgroupDeviceFilters(dirFd)
162 if err != nil {
163 return nilCloser, err
164 }
165 useReplaceProg := haveBpfProgReplace() && len(oldProgs) == 1
166
167
168 spec := &ebpf.ProgramSpec{
169 Type: ebpf.CGroupDevice,
170 Instructions: insts,
171 License: license,
172 }
173 prog, err := ebpf.NewProgram(spec)
174 if err != nil {
175 return nilCloser, err
176 }
177
178
179 var (
180 replaceProg *ebpf.Program
181 attachFlags uint32 = unix.BPF_F_ALLOW_MULTI
182 )
183 if useReplaceProg {
184 replaceProg = oldProgs[0]
185 attachFlags |= unix.BPF_F_REPLACE
186 }
187 err = link.RawAttachProgram(link.RawAttachProgramOptions{
188 Target: dirFd,
189 Program: prog,
190 Replace: replaceProg,
191 Attach: ebpf.AttachCGroupDevice,
192 Flags: attachFlags,
193 })
194 if err != nil {
195 return nilCloser, fmt.Errorf("failed to call BPF_PROG_ATTACH (BPF_CGROUP_DEVICE, BPF_F_ALLOW_MULTI): %w", err)
196 }
197 closer := func() error {
198 err = link.RawDetachProgram(link.RawDetachProgramOptions{
199 Target: dirFd,
200 Program: prog,
201 Attach: ebpf.AttachCGroupDevice,
202 })
203 if err != nil {
204 return fmt.Errorf("failed to call BPF_PROG_DETACH (BPF_CGROUP_DEVICE): %w", err)
205 }
206
207
208 return nil
209 }
210 if !useReplaceProg {
211 logLevel := logrus.DebugLevel
212
213
214
215 if len(oldProgs) > 1 {
216
217
218
219
220 logrus.Infof("found more than one filter (%d) attached to a cgroup -- removing extra filters!", len(oldProgs))
221 logLevel = logrus.InfoLevel
222 }
223 for idx, oldProg := range oldProgs {
224
225 if info, err := oldProg.Info(); err == nil {
226 fields := logrus.Fields{
227 "type": info.Type.String(),
228 "tag": info.Tag,
229 "name": info.Name,
230 }
231 if id, ok := info.ID(); ok {
232 fields["id"] = id
233 }
234 if runCount, ok := info.RunCount(); ok {
235 fields["run_count"] = runCount
236 }
237 if runtime, ok := info.Runtime(); ok {
238 fields["runtime"] = runtime.String()
239 }
240 logrus.WithFields(fields).Logf(logLevel, "removing old filter %d from cgroup", idx)
241 }
242 err = link.RawDetachProgram(link.RawDetachProgramOptions{
243 Target: dirFd,
244 Program: oldProg,
245 Attach: ebpf.AttachCGroupDevice,
246 })
247 if err != nil {
248 return closer, fmt.Errorf("failed to call BPF_PROG_DETACH (BPF_CGROUP_DEVICE) on old filter program: %w", err)
249 }
250 }
251 }
252 return closer, nil
253 }
254
View as plain text