From e24c2c5463cd8463aa65502b3ca3fbb7f27dafee Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Sun, 7 Jul 2024 21:17:49 +0000 Subject: [PATCH 01/11] tetragon: Add local ebpf version Update ebpf version to latest upstream to get link Pin/Unpin changes. Signed-off-by: Jiri Olsa --- go.mod | 2 +- go.sum | 12 +- vendor/github.com/cilium/ebpf/.vimto.toml | 12 ++ vendor/github.com/cilium/ebpf/Makefile | 2 +- vendor/github.com/cilium/ebpf/elf_reader.go | 3 + .../github.com/cilium/ebpf/features/prog.go | 3 + vendor/github.com/cilium/ebpf/info.go | 33 +++- .../cilium/ebpf/internal/epoll/poller.go | 117 +++++++++---- .../cilium/ebpf/internal/sys/types.go | 23 +-- vendor/github.com/cilium/ebpf/link/cgroup.go | 19 +++ vendor/github.com/cilium/ebpf/link/kprobe.go | 10 +- .../cilium/ebpf/link/kprobe_multi.go | 21 ++- vendor/github.com/cilium/ebpf/link/link.go | 115 +------------ .../github.com/cilium/ebpf/link/netfilter.go | 20 +++ vendor/github.com/cilium/ebpf/link/netkit.go | 18 ++ vendor/github.com/cilium/ebpf/link/netns.go | 19 +++ .../github.com/cilium/ebpf/link/perf_event.go | 102 +++++++++--- vendor/github.com/cilium/ebpf/link/tcx.go | 18 ++ .../github.com/cilium/ebpf/link/tracepoint.go | 2 + vendor/github.com/cilium/ebpf/link/tracing.go | 19 +++ vendor/github.com/cilium/ebpf/link/uprobe.go | 4 + .../cilium/ebpf/link/uprobe_multi.go | 8 - vendor/github.com/cilium/ebpf/link/xdp.go | 24 ++- vendor/github.com/cilium/ebpf/map.go | 42 ++++- vendor/github.com/cilium/ebpf/perf/reader.go | 154 ++++++++++-------- vendor/github.com/cilium/ebpf/perf/ring.go | 43 ++--- vendor/github.com/cilium/ebpf/run-tests.sh | 144 ---------------- vendor/modules.txt | 4 +- 28 files changed, 559 insertions(+), 434 deletions(-) create mode 100644 vendor/github.com/cilium/ebpf/.vimto.toml delete mode 100644 vendor/github.com/cilium/ebpf/run-tests.sh diff --git a/go.mod b/go.mod index 7fe9bb601cb..27d93ab5a30 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ toolchain go1.22.3 require ( github.com/bombsimon/logrusr/v4 v4.1.0 github.com/cilium/cilium v1.15.6 - github.com/cilium/ebpf v0.15.0 + github.com/cilium/ebpf v0.15.1-0.20240703142256-12aca1027ee8 github.com/cilium/little-vm-helper v0.0.18 github.com/cilium/lumberjack/v2 v2.3.0 github.com/cilium/tetragon/api v0.0.0-00010101000000-000000000000 diff --git a/go.sum b/go.sum index 27fa7e55b19..523fc7d247d 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/cilium/cilium v1.15.6 h1:YT6UYuvdua6N1KQ6mRprymCct6Ee7uCE1hckbAR2bRM= github.com/cilium/cilium v1.15.6/go.mod h1:UEP0tpPVhdrLC7rCHZwZ8hTpd6d01dF/1GvFPo8UhXE= github.com/cilium/controller-tools v0.8.0-1 h1:D5xhwSUZZceaKAacHOyfcpUMgLbs2TGeJEijNHlAQlc= github.com/cilium/controller-tools v0.8.0-1/go.mod h1:qE2DXhVOiEq5ijmINcFbqi9GZrrUjzB1TuJU0xa6eoY= -github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= -github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= +github.com/cilium/ebpf v0.15.1-0.20240703142256-12aca1027ee8 h1:+AMtTVJtVC6OGInoYSGtTW9YTblQBi/sysXD4rtc3to= +github.com/cilium/ebpf v0.15.1-0.20240703142256-12aca1027ee8/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= github.com/cilium/little-vm-helper v0.0.18 h1:Sx3D9lQ6glUwWyF9b8I/sd/mo+2qobnpMGT1n6VlS04= github.com/cilium/little-vm-helper v0.0.18/go.mod h1:Cq9INShkRoeR4LC46dwHkfL3EZfHsN+e+xAsJKJ/wJM= github.com/cilium/lumberjack/v2 v2.3.0 h1:IhVJMvPpqDYmQzC0KDhAoy7KlaRsyOsZnT97Nsa3u0o= @@ -322,8 +322,12 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629 h1:1dSBUfGlorLAua2CRx0zFN7kQsTpE2DQSmr7rrTNgY8= github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629/go.mod h1:mb5nS4uRANwOJSZj8rlCWAfAcGi72GGMIXx+xGOjA7M= +github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= +github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -372,6 +376,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mennanov/fieldmask-utils v1.1.2 h1:f5hd3hYeWdl+q2thiKYyZZmqTqn90uayWG03bca9U+E= github.com/mennanov/fieldmask-utils v1.1.2/go.mod h1:xRqd9Fjz/gFEDYCQw7pxGouxqLhSPrkOdx2yhEAXEls= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= diff --git a/vendor/github.com/cilium/ebpf/.vimto.toml b/vendor/github.com/cilium/ebpf/.vimto.toml new file mode 100644 index 00000000000..49a12dbc090 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/.vimto.toml @@ -0,0 +1,12 @@ +kernel="ghcr.io/cilium/ci-kernels:stable" +smp="cpus=2" +memory="1G" +user="root" +setup=[ + "mount -t cgroup2 -o nosuid,noexec,nodev cgroup2 /sys/fs/cgroup", + "/bin/sh -c 'modprobe bpf_testmod || true'", + "dmesg --clear", +] +teardown=[ + "dmesg --read-clear", +] diff --git a/vendor/github.com/cilium/ebpf/Makefile b/vendor/github.com/cilium/ebpf/Makefile index c55a93d9cb2..d355eea71ca 100644 --- a/vendor/github.com/cilium/ebpf/Makefile +++ b/vendor/github.com/cilium/ebpf/Makefile @@ -106,7 +106,7 @@ testdata/loader-%-eb.elf: testdata/loader.c $(STRIP) -g $@ .PHONY: update-kernel-deps -update-kernel-deps: export KERNEL_VERSION?=6.7 +update-kernel-deps: export KERNEL_VERSION?=6.8 update-kernel-deps: ./testdata/sh/update-kernel-deps.sh $(MAKE) container-all diff --git a/vendor/github.com/cilium/ebpf/elf_reader.go b/vendor/github.com/cilium/ebpf/elf_reader.go index d55ab88928a..620037d80a8 100644 --- a/vendor/github.com/cilium/ebpf/elf_reader.go +++ b/vendor/github.com/cilium/ebpf/elf_reader.go @@ -972,6 +972,9 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("resolving values contents: %w", err) } + case "map_extra": + return nil, fmt.Errorf("BTF map definition: field %s: %w", member.Name, ErrNotSupported) + default: return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name) } diff --git a/vendor/github.com/cilium/ebpf/features/prog.go b/vendor/github.com/cilium/ebpf/features/prog.go index a1136379672..dc13b86d3a6 100644 --- a/vendor/github.com/cilium/ebpf/features/prog.go +++ b/vendor/github.com/cilium/ebpf/features/prog.go @@ -125,6 +125,9 @@ var haveProgramTypeMatrix = internal.FeatureMatrix[ebpf.ProgramType]{ Name: "a", Type: &btf.FuncProto{ Return: &btf.Int{}, + Params: []btf.FuncParam{ + {Name: "ctx", Type: &btf.Pointer{Target: &btf.Struct{Name: "xdp_md"}}}, + }, }, Linkage: btf.GlobalFunc, } diff --git a/vendor/github.com/cilium/ebpf/info.go b/vendor/github.com/cilium/ebpf/info.go index 79b11c951f4..25adbb94e7f 100644 --- a/vendor/github.com/cilium/ebpf/info.go +++ b/vendor/github.com/cilium/ebpf/info.go @@ -30,6 +30,8 @@ type MapInfo struct { Flags uint32 // Name as supplied by user space at load time. Available from 4.15. Name string + + btf btf.ID } func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { @@ -50,6 +52,7 @@ func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { info.MaxEntries, uint32(info.MapFlags), unix.ByteSliceToString(info.Name[:]), + btf.ID(info.BtfId), }, nil } @@ -77,12 +80,27 @@ func (mi *MapInfo) ID() (MapID, bool) { return mi.id, mi.id > 0 } +// BTFID returns the BTF ID associated with the Map. +// +// The ID is only valid as long as the associated Map is kept alive. +// Available from 4.18. +// +// The bool return value indicates whether this optional field is available and +// populated. (The field may be available but not populated if the kernel +// supports the field but the Map was loaded without BTF information.) +func (mi *MapInfo) BTFID() (btf.ID, bool) { + return mi.btf, mi.btf > 0 +} + // programStats holds statistics of a program. type programStats struct { // Total accumulated runtime of the program ins ns. runtime time.Duration // Total number of times the program was called. runCount uint64 + // Total number of times the programm was NOT called. + // Added in commit 9ed9e9ba2337 ("bpf: Count the number of times recursion was prevented"). + recursionMisses uint64 } // ProgramInfo describes a program. @@ -125,8 +143,9 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { Name: unix.ByteSliceToString(info.Name[:]), btf: btf.ID(info.BtfId), stats: &programStats{ - runtime: time.Duration(info.RunTimeNs), - runCount: info.RunCnt, + runtime: time.Duration(info.RunTimeNs), + runCount: info.RunCnt, + recursionMisses: info.RecursionMisses, }, } @@ -259,6 +278,16 @@ func (pi *ProgramInfo) Runtime() (time.Duration, bool) { return time.Duration(0), false } +// RecursionMisses returns the total number of times the program was NOT called. +// This can happen when another bpf program is already running on the cpu, which +// is likely to happen for example when you interrupt bpf program execution. +func (pi *ProgramInfo) RecursionMisses() (uint64, bool) { + if pi.stats != nil { + return pi.stats.recursionMisses, true + } + return 0, false +} + // Instructions returns the 'xlated' instruction stream of the program // after it has been verified and rewritten by the kernel. These instructions // cannot be loaded back into the kernel as-is, this is mainly used for diff --git a/vendor/github.com/cilium/ebpf/internal/epoll/poller.go b/vendor/github.com/cilium/ebpf/internal/epoll/poller.go index ee86a2e7588..ed1c3a3c8f4 100644 --- a/vendor/github.com/cilium/ebpf/internal/epoll/poller.go +++ b/vendor/github.com/cilium/ebpf/internal/epoll/poller.go @@ -1,10 +1,12 @@ package epoll import ( + "errors" "fmt" "math" "os" "runtime" + "slices" "sync" "time" @@ -12,6 +14,8 @@ import ( "github.com/cilium/ebpf/internal/unix" ) +var ErrFlushed = errors.New("data was flushed") + // Poller waits for readiness notifications from multiple file descriptors. // // The wait can be interrupted by calling Close. @@ -21,27 +25,48 @@ type Poller struct { epollMu sync.Mutex epollFd int - eventMu sync.Mutex - event *eventFd + eventMu sync.Mutex + closeEvent *eventFd + flushEvent *eventFd } -func New() (*Poller, error) { +func New() (_ *Poller, err error) { + closeFDOnError := func(fd int) { + if err != nil { + unix.Close(fd) + } + } + closeEventFDOnError := func(e *eventFd) { + if err != nil { + e.close() + } + } + epollFd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) if err != nil { return nil, fmt.Errorf("create epoll fd: %v", err) } + defer closeFDOnError(epollFd) p := &Poller{epollFd: epollFd} - p.event, err = newEventFd() + p.closeEvent, err = newEventFd() if err != nil { - unix.Close(epollFd) return nil, err } + defer closeEventFDOnError(p.closeEvent) + + p.flushEvent, err = newEventFd() + if err != nil { + return nil, err + } + defer closeEventFDOnError(p.flushEvent) + + if err := p.Add(p.closeEvent.raw, 0); err != nil { + return nil, fmt.Errorf("add close eventfd: %w", err) + } - if err := p.Add(p.event.raw, 0); err != nil { - unix.Close(epollFd) - p.event.close() - return nil, fmt.Errorf("add eventfd: %w", err) + if err := p.Add(p.flushEvent.raw, 0); err != nil { + return nil, fmt.Errorf("add flush eventfd: %w", err) } runtime.SetFinalizer(p, (*Poller).Close) @@ -55,8 +80,8 @@ func New() (*Poller, error) { func (p *Poller) Close() error { runtime.SetFinalizer(p, nil) - // Interrupt Wait() via the event fd if it's currently blocked. - if err := p.wakeWait(); err != nil { + // Interrupt Wait() via the closeEvent fd if it's currently blocked. + if err := p.wakeWaitForClose(); err != nil { return err } @@ -73,9 +98,14 @@ func (p *Poller) Close() error { p.epollFd = -1 } - if p.event != nil { - p.event.close() - p.event = nil + if p.closeEvent != nil { + p.closeEvent.close() + p.closeEvent = nil + } + + if p.flushEvent != nil { + p.flushEvent.close() + p.flushEvent = nil } return nil @@ -118,8 +148,11 @@ func (p *Poller) Add(fd int, id int) error { // Wait for events. // -// Returns the number of pending events or an error wrapping os.ErrClosed if -// Close is called, or os.ErrDeadlineExceeded if EpollWait timeout. +// Returns the number of pending events and any errors. +// +// - [os.ErrClosed] if interrupted by [Close]. +// - [ErrFlushed] if interrupted by [Flush]. +// - [os.ErrDeadlineExceeded] if deadline is reached. func (p *Poller) Wait(events []unix.EpollEvent, deadline time.Time) (int, error) { p.epollMu.Lock() defer p.epollMu.Unlock() @@ -132,13 +165,11 @@ func (p *Poller) Wait(events []unix.EpollEvent, deadline time.Time) (int, error) timeout := int(-1) if !deadline.IsZero() { msec := time.Until(deadline).Milliseconds() - if msec < 0 { - // Deadline is in the past. - msec = 0 - } else if msec > math.MaxInt { - // Deadline is too far in the future. - msec = math.MaxInt - } + // Deadline is in the past, don't block. + msec = max(msec, 0) + // Deadline is too far in the future. + msec = min(msec, math.MaxInt) + timeout = int(msec) } @@ -156,16 +187,26 @@ func (p *Poller) Wait(events []unix.EpollEvent, deadline time.Time) (int, error) return 0, fmt.Errorf("epoll wait: %w", os.ErrDeadlineExceeded) } - for _, event := range events[:n] { - if int(event.Fd) == p.event.raw { - // Since we don't read p.event the event is never cleared and + for i := 0; i < n; { + event := events[i] + if int(event.Fd) == p.closeEvent.raw { + // Since we don't read p.closeEvent the event is never cleared and // we'll keep getting this wakeup until Close() acquires the // lock and sets p.epollFd = -1. return 0, fmt.Errorf("epoll wait: %w", os.ErrClosed) } + if int(event.Fd) == p.flushEvent.raw { + // read event to prevent it from continuing to wake + p.flushEvent.read() + err = ErrFlushed + events = slices.Delete(events, i, i+1) + n -= 1 + continue + } + i++ } - return n, nil + return n, err } } @@ -173,16 +214,28 @@ type temporaryError interface { Temporary() bool } -// wakeWait unblocks Wait if it's epoll_wait. -func (p *Poller) wakeWait() error { +// wakeWaitForClose unblocks Wait if it's epoll_wait. +func (p *Poller) wakeWaitForClose() error { + p.eventMu.Lock() + defer p.eventMu.Unlock() + + if p.closeEvent == nil { + return fmt.Errorf("epoll wake: %w", os.ErrClosed) + } + + return p.closeEvent.add(1) +} + +// Flush unblocks Wait if it's epoll_wait, for purposes of reading pending samples +func (p *Poller) Flush() error { p.eventMu.Lock() defer p.eventMu.Unlock() - if p.event == nil { + if p.flushEvent == nil { return fmt.Errorf("epoll wake: %w", os.ErrClosed) } - return p.event.add(1) + return p.flushEvent.add(1) } // eventFd wraps a Linux eventfd. @@ -213,7 +266,7 @@ func (efd *eventFd) close() error { func (efd *eventFd) add(n uint64) error { var buf [8]byte - internal.NativeEndian.PutUint64(buf[:], 1) + internal.NativeEndian.PutUint64(buf[:], n) _, err := efd.file.Write(buf[:]) return err } diff --git a/vendor/github.com/cilium/ebpf/internal/sys/types.go b/vendor/github.com/cilium/ebpf/internal/sys/types.go index d2ae9426689..70e754de71d 100644 --- a/vendor/github.com/cilium/ebpf/internal/sys/types.go +++ b/vendor/github.com/cilium/ebpf/internal/sys/types.go @@ -359,7 +359,7 @@ const ( BPF_LINK_TYPE_TCX LinkType = 11 BPF_LINK_TYPE_UPROBE_MULTI LinkType = 12 BPF_LINK_TYPE_NETKIT LinkType = 13 - MAX_BPF_LINK_TYPE LinkType = 14 + __MAX_BPF_LINK_TYPE LinkType = 14 ) type MapType uint32 @@ -528,7 +528,7 @@ type LinkInfo struct { Id LinkID ProgId uint32 _ [4]byte - Extra [40]uint8 + Extra [48]uint8 } type MapInfo struct { @@ -1263,7 +1263,7 @@ type CgroupLinkInfo struct { _ [4]byte CgroupId uint64 AttachType AttachType - _ [28]byte + _ [36]byte } type IterLinkInfo struct { @@ -1287,6 +1287,7 @@ type KprobeLinkInfo struct { Offset uint32 Addr uint64 Missed uint64 + _ [8]byte } type KprobeMultiLinkInfo struct { @@ -1298,7 +1299,7 @@ type KprobeMultiLinkInfo struct { Count uint32 Flags uint32 Missed uint64 - _ [16]byte + _ [24]byte } type NetNsLinkInfo struct { @@ -1308,7 +1309,7 @@ type NetNsLinkInfo struct { _ [4]byte NetnsIno uint32 AttachType AttachType - _ [32]byte + _ [40]byte } type NetfilterLinkInfo struct { @@ -1320,7 +1321,7 @@ type NetfilterLinkInfo struct { Hooknum uint32 Priority int32 Flags uint32 - _ [24]byte + _ [32]byte } type NetkitLinkInfo struct { @@ -1330,7 +1331,7 @@ type NetkitLinkInfo struct { _ [4]byte Ifindex uint32 AttachType AttachType - _ [32]byte + _ [40]byte } type PerfEventLinkInfo struct { @@ -1348,7 +1349,7 @@ type RawTracepointLinkInfo struct { _ [4]byte TpName Pointer TpNameLen uint32 - _ [28]byte + _ [36]byte } type TcxLinkInfo struct { @@ -1358,7 +1359,7 @@ type TcxLinkInfo struct { _ [4]byte Ifindex uint32 AttachType AttachType - _ [32]byte + _ [40]byte } type TracingLinkInfo struct { @@ -1369,7 +1370,7 @@ type TracingLinkInfo struct { AttachType AttachType TargetObjId uint32 TargetBtfId TypeID - _ [28]byte + _ [36]byte } type XDPLinkInfo struct { @@ -1378,5 +1379,5 @@ type XDPLinkInfo struct { ProgId uint32 _ [4]byte Ifindex uint32 - _ [36]byte + _ [44]byte } diff --git a/vendor/github.com/cilium/ebpf/link/cgroup.go b/vendor/github.com/cilium/ebpf/link/cgroup.go index 79f3d2b7f4c..f17d34f03c0 100644 --- a/vendor/github.com/cilium/ebpf/link/cgroup.go +++ b/vendor/github.com/cilium/ebpf/link/cgroup.go @@ -6,6 +6,7 @@ import ( "os" "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/sys" ) type cgroupAttachFlags uint32 @@ -187,3 +188,21 @@ func newLinkCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program) return &linkCgroup{*link}, err } + +func (cg *linkCgroup) Info() (*Info, error) { + var info sys.CgroupLinkInfo + if err := sys.ObjInfo(cg.fd, &info); err != nil { + return nil, fmt.Errorf("cgroup link info: %s", err) + } + extra := &CgroupInfo{ + CgroupId: info.CgroupId, + AttachType: info.AttachType, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/kprobe.go b/vendor/github.com/cilium/ebpf/link/kprobe.go index b54ca908533..fe3f17c3717 100644 --- a/vendor/github.com/cilium/ebpf/link/kprobe.go +++ b/vendor/github.com/cilium/ebpf/link/kprobe.go @@ -59,6 +59,8 @@ func (ko *KprobeOptions) cookie() uint64 { // If attaching to symbol fails, automatically retries with the running // platform's syscall prefix (e.g. __x64_) to support attaching to syscalls // in a portable fashion. +// +// The returned Link may implement [PerfEvent]. func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) { k, err := kprobe(symbol, prog, opts, false) if err != nil { @@ -90,6 +92,8 @@ func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error // // On kernels 5.10 and earlier, setting a kretprobe on a nonexistent symbol // incorrectly returns unix.EINVAL instead of os.ErrNotExist. +// +// The returned Link may implement [PerfEvent]. func Kretprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) { k, err := kprobe(symbol, prog, opts, true) if err != nil { @@ -274,7 +278,11 @@ func pmuProbe(args tracefs.ProbeArgs) (*perfEvent, error) { } } - rawFd, err := unix.PerfEventOpen(&attr, args.Pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) + cpu := 0 + if args.Pid != perfAllThreads { + cpu = -1 + } + rawFd, err := unix.PerfEventOpen(&attr, args.Pid, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) // On some old kernels, kprobe PMU doesn't allow `.` in symbol names and // return -EINVAL. Return ErrNotSupported to allow falling back to tracefs. diff --git a/vendor/github.com/cilium/ebpf/link/kprobe_multi.go b/vendor/github.com/cilium/ebpf/link/kprobe_multi.go index 4d364d80ebc..f7a8291f945 100644 --- a/vendor/github.com/cilium/ebpf/link/kprobe_multi.go +++ b/vendor/github.com/cilium/ebpf/link/kprobe_multi.go @@ -130,12 +130,23 @@ func (kml *kprobeMultiLink) Update(prog *ebpf.Program) error { return fmt.Errorf("update kprobe_multi: %w", ErrNotSupported) } -func (kml *kprobeMultiLink) Pin(string) error { - return fmt.Errorf("pin kprobe_multi: %w", ErrNotSupported) -} +func (kml *kprobeMultiLink) Info() (*Info, error) { + var info sys.KprobeMultiLinkInfo + if err := sys.ObjInfo(kml.fd, &info); err != nil { + return nil, fmt.Errorf("kprobe multi link info: %s", err) + } + extra := &KprobeMultiInfo{ + count: info.Count, + flags: info.Flags, + missed: info.Missed, + } -func (kml *kprobeMultiLink) Unpin() error { - return fmt.Errorf("unpin kprobe_multi: %w", ErrNotSupported) + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil } var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", "5.18", func() error { diff --git a/vendor/github.com/cilium/ebpf/link/link.go b/vendor/github.com/cilium/ebpf/link/link.go index 81428568f89..9c34616c9a9 100644 --- a/vendor/github.com/cilium/ebpf/link/link.go +++ b/vendor/github.com/cilium/ebpf/link/link.go @@ -119,13 +119,15 @@ func wrapRawLink(raw *RawLink) (_ Link, err error) { case UprobeMultiType: return &uprobeMultiLink{*raw}, nil case PerfEventType: - return nil, fmt.Errorf("recovering perf event fd: %w", ErrNotSupported) + return &perfEventLink{*raw, nil}, nil case TCXType: return &tcxLink{*raw}, nil case NetfilterType: return &netfilterLink{*raw}, nil case NetkitType: return &netkitLink{*raw}, nil + case XDPType: + return &xdpLink{*raw}, nil default: return raw, nil } @@ -438,6 +440,9 @@ func (l *RawLink) UpdateArgs(opts RawLinkUpdateOptions) error { } // Info returns metadata about the link. +// +// Linktype specific metadata is not included and can be retrieved +// via the linktype specific Info() method. func (l *RawLink) Info() (*Info, error) { var info sys.LinkInfo @@ -445,117 +450,11 @@ func (l *RawLink) Info() (*Info, error) { return nil, fmt.Errorf("link info: %s", err) } - var extra interface{} - switch info.Type { - case CgroupType: - var cgroupInfo sys.CgroupLinkInfo - if err := sys.ObjInfo(l.fd, &cgroupInfo); err != nil { - return nil, fmt.Errorf("cgroup link info: %s", err) - } - extra = &CgroupInfo{ - CgroupId: cgroupInfo.CgroupId, - AttachType: cgroupInfo.AttachType, - } - case NetNsType: - var netnsInfo sys.NetNsLinkInfo - if err := sys.ObjInfo(l.fd, &netnsInfo); err != nil { - return nil, fmt.Errorf("netns link info: %s", err) - } - extra = &NetNsInfo{ - NetnsIno: netnsInfo.NetnsIno, - AttachType: netnsInfo.AttachType, - } - case TracingType: - var tracingInfo sys.TracingLinkInfo - if err := sys.ObjInfo(l.fd, &tracingInfo); err != nil { - return nil, fmt.Errorf("tracing link info: %s", err) - } - extra = &TracingInfo{ - TargetObjId: tracingInfo.TargetObjId, - TargetBtfId: tracingInfo.TargetBtfId, - AttachType: tracingInfo.AttachType, - } - case XDPType: - var xdpInfo sys.XDPLinkInfo - if err := sys.ObjInfo(l.fd, &xdpInfo); err != nil { - return nil, fmt.Errorf("xdp link info: %s", err) - } - extra = &XDPInfo{ - Ifindex: xdpInfo.Ifindex, - } - case RawTracepointType, IterType, UprobeMultiType: - // Extra metadata not supported. - case TCXType: - var tcxInfo sys.TcxLinkInfo - if err := sys.ObjInfo(l.fd, &tcxInfo); err != nil { - return nil, fmt.Errorf("tcx link info: %s", err) - } - extra = &TCXInfo{ - Ifindex: tcxInfo.Ifindex, - AttachType: tcxInfo.AttachType, - } - case NetfilterType: - var netfilterInfo sys.NetfilterLinkInfo - if err := sys.ObjInfo(l.fd, &netfilterInfo); err != nil { - return nil, fmt.Errorf("netfilter link info: %s", err) - } - extra = &NetfilterInfo{ - Pf: netfilterInfo.Pf, - Hooknum: netfilterInfo.Hooknum, - Priority: netfilterInfo.Priority, - Flags: netfilterInfo.Flags, - } - case NetkitType: - var netkitInfo sys.NetkitLinkInfo - if err := sys.ObjInfo(l.fd, &netkitInfo); err != nil { - return nil, fmt.Errorf("tcx link info: %s", err) - } - extra = &NetkitInfo{ - Ifindex: netkitInfo.Ifindex, - AttachType: netkitInfo.AttachType, - } - case KprobeMultiType: - var kprobeMultiInfo sys.KprobeMultiLinkInfo - if err := sys.ObjInfo(l.fd, &kprobeMultiInfo); err != nil { - return nil, fmt.Errorf("kprobe multi link info: %s", err) - } - extra = &KprobeMultiInfo{ - count: kprobeMultiInfo.Count, - flags: kprobeMultiInfo.Flags, - missed: kprobeMultiInfo.Missed, - } - case PerfEventType: - var perfEventInfo sys.PerfEventLinkInfo - if err := sys.ObjInfo(l.fd, &perfEventInfo); err != nil { - return nil, fmt.Errorf("perf event link info: %s", err) - } - - var extra2 interface{} - switch perfEventInfo.PerfEventType { - case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE: - var kprobeInfo sys.KprobeLinkInfo - if err := sys.ObjInfo(l.fd, &kprobeInfo); err != nil { - return nil, fmt.Errorf("kprobe multi link info: %s", err) - } - extra2 = &KprobeInfo{ - address: kprobeInfo.Addr, - missed: kprobeInfo.Missed, - } - } - - extra = &PerfEventInfo{ - Type: perfEventInfo.PerfEventType, - extra: extra2, - } - default: - return nil, fmt.Errorf("unknown link info type: %d", info.Type) - } - return &Info{ info.Type, info.Id, ebpf.ProgramID(info.ProgId), - extra, + nil, }, nil } diff --git a/vendor/github.com/cilium/ebpf/link/netfilter.go b/vendor/github.com/cilium/ebpf/link/netfilter.go index 250c87677b0..34be3908597 100644 --- a/vendor/github.com/cilium/ebpf/link/netfilter.go +++ b/vendor/github.com/cilium/ebpf/link/netfilter.go @@ -67,4 +67,24 @@ func (*netfilterLink) Update(new *ebpf.Program) error { return fmt.Errorf("netfilter update: %w", ErrNotSupported) } +func (nf *netfilterLink) Info() (*Info, error) { + var info sys.NetfilterLinkInfo + if err := sys.ObjInfo(nf.fd, &info); err != nil { + return nil, fmt.Errorf("netfilter link info: %s", err) + } + extra := &NetfilterInfo{ + Pf: info.Pf, + Hooknum: info.Hooknum, + Priority: info.Priority, + Flags: info.Flags, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} + var _ Link = (*netfilterLink)(nil) diff --git a/vendor/github.com/cilium/ebpf/link/netkit.go b/vendor/github.com/cilium/ebpf/link/netkit.go index 36ed72a480e..5eee3b023ae 100644 --- a/vendor/github.com/cilium/ebpf/link/netkit.go +++ b/vendor/github.com/cilium/ebpf/link/netkit.go @@ -69,3 +69,21 @@ type netkitLink struct { } var _ Link = (*netkitLink)(nil) + +func (netkit *netkitLink) Info() (*Info, error) { + var info sys.NetkitLinkInfo + if err := sys.ObjInfo(netkit.fd, &info); err != nil { + return nil, fmt.Errorf("netkit link info: %s", err) + } + extra := &NetkitInfo{ + Ifindex: info.Ifindex, + AttachType: info.AttachType, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/netns.go b/vendor/github.com/cilium/ebpf/link/netns.go index 344ecced6be..b1edd340a3f 100644 --- a/vendor/github.com/cilium/ebpf/link/netns.go +++ b/vendor/github.com/cilium/ebpf/link/netns.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/sys" ) // NetNsLink is a program attached to a network namespace. @@ -34,3 +35,21 @@ func AttachNetNs(ns int, prog *ebpf.Program) (*NetNsLink, error) { return &NetNsLink{*link}, nil } + +func (ns *NetNsLink) Info() (*Info, error) { + var info sys.NetNsLinkInfo + if err := sys.ObjInfo(ns.fd, &info); err != nil { + return nil, fmt.Errorf("netns link info: %s", err) + } + extra := &NetNsInfo{ + NetnsIno: info.NetnsIno, + AttachType: info.AttachType, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/perf_event.go b/vendor/github.com/cilium/ebpf/link/perf_event.go index 5f7a628b3d7..1d8feb58c1c 100644 --- a/vendor/github.com/cilium/ebpf/link/perf_event.go +++ b/vendor/github.com/cilium/ebpf/link/perf_event.go @@ -3,6 +3,7 @@ package link import ( "errors" "fmt" + "os" "runtime" "unsafe" @@ -78,6 +79,18 @@ func (pe *perfEvent) Close() error { return nil } +// PerfEvent is implemented by some Link types which use a perf event under +// the hood. +type PerfEvent interface { + // PerfEvent returns a file for the underlying perf event. + // + // It is the callers responsibility to close the returned file. + // + // Making changes to the associated perf event lead to + // undefined behaviour. + PerfEvent() (*os.File, error) +} + // perfEventLink represents a bpf perf link. type perfEventLink struct { RawLink @@ -86,30 +99,16 @@ type perfEventLink struct { func (pl *perfEventLink) isLink() {} -// Pinning requires the underlying perf event FD to stay open. -// -// | PerfEvent FD | BpfLink FD | Works | -// |--------------|------------|-------| -// | Open | Open | Yes | -// | Closed | Open | No | -// | Open | Closed | No (Pin() -> EINVAL) | -// | Closed | Closed | No (Pin() -> EINVAL) | -// -// There is currently no pretty way to recover the perf event FD -// when loading a pinned link, so leave as not supported for now. -func (pl *perfEventLink) Pin(string) error { - return fmt.Errorf("perf event link pin: %w", ErrNotSupported) -} - -func (pl *perfEventLink) Unpin() error { - return fmt.Errorf("perf event link unpin: %w", ErrNotSupported) -} - func (pl *perfEventLink) Close() error { if err := pl.fd.Close(); err != nil { return fmt.Errorf("perf link close: %w", err) } + // when created from pinned link + if pl.pe == nil { + return nil + } + if err := pl.pe.Close(); err != nil { return fmt.Errorf("perf event close: %w", err) } @@ -120,6 +119,54 @@ func (pl *perfEventLink) Update(prog *ebpf.Program) error { return fmt.Errorf("perf event link update: %w", ErrNotSupported) } +var _ PerfEvent = (*perfEventLink)(nil) + +func (pl *perfEventLink) PerfEvent() (*os.File, error) { + // when created from pinned link + if pl.pe == nil { + return nil, ErrNotSupported + } + + fd, err := pl.pe.fd.Dup() + if err != nil { + return nil, err + } + + return fd.File("perf-event"), nil +} + +func (pl *perfEventLink) Info() (*Info, error) { + var info sys.PerfEventLinkInfo + if err := sys.ObjInfo(pl.fd, &info); err != nil { + return nil, fmt.Errorf("perf event link info: %s", err) + } + + var extra2 interface{} + switch info.PerfEventType { + case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE: + var kprobeInfo sys.KprobeLinkInfo + if err := sys.ObjInfo(pl.fd, &kprobeInfo); err != nil { + return nil, fmt.Errorf("kprobe link info: %s", err) + } + extra2 = &KprobeInfo{ + address: kprobeInfo.Addr, + missed: kprobeInfo.Missed, + } + } + + extra := &PerfEventInfo{ + Type: info.PerfEventType, + extra: extra2, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} + // perfEventIoctl implements Link and handles the perf event lifecycle // via ioctl(). type perfEventIoctl struct { @@ -154,6 +201,17 @@ func (pi *perfEventIoctl) Info() (*Info, error) { return nil, fmt.Errorf("perf event ioctl info: %w", ErrNotSupported) } +var _ PerfEvent = (*perfEventIoctl)(nil) + +func (pi *perfEventIoctl) PerfEvent() (*os.File, error) { + fd, err := pi.fd.Dup() + if err != nil { + return nil, err + } + + return fd.File("perf-event"), nil +} + // attach the given eBPF prog to the perf event stored in pe. // pe must contain a valid perf event fd. // prog's type must match the program type stored in pe. @@ -229,7 +287,11 @@ func openTracepointPerfEvent(tid uint64, pid int) (*sys.FD, error) { Wakeup: 1, } - fd, err := unix.PerfEventOpen(&attr, pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) + cpu := 0 + if pid != perfAllThreads { + cpu = -1 + } + fd, err := unix.PerfEventOpen(&attr, pid, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) if err != nil { return nil, fmt.Errorf("opening tracepoint perf event: %w", err) } diff --git a/vendor/github.com/cilium/ebpf/link/tcx.go b/vendor/github.com/cilium/ebpf/link/tcx.go index 88f2237d290..ac045b71da0 100644 --- a/vendor/github.com/cilium/ebpf/link/tcx.go +++ b/vendor/github.com/cilium/ebpf/link/tcx.go @@ -69,3 +69,21 @@ type tcxLink struct { } var _ Link = (*tcxLink)(nil) + +func (tcx *tcxLink) Info() (*Info, error) { + var info sys.TcxLinkInfo + if err := sys.ObjInfo(tcx.fd, &info); err != nil { + return nil, fmt.Errorf("tcx link info: %s", err) + } + extra := &TCXInfo{ + Ifindex: info.Ifindex, + AttachType: info.AttachType, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} diff --git a/vendor/github.com/cilium/ebpf/link/tracepoint.go b/vendor/github.com/cilium/ebpf/link/tracepoint.go index 95f5fae3b09..6fc78b98287 100644 --- a/vendor/github.com/cilium/ebpf/link/tracepoint.go +++ b/vendor/github.com/cilium/ebpf/link/tracepoint.go @@ -30,6 +30,8 @@ type TracepointOptions struct { // // Note that attaching eBPF programs to syscalls (sys_enter_*/sys_exit_*) is // only possible as of kernel 4.14 (commit cf5f5ce). +// +// The returned Link may implement [PerfEvent]. func Tracepoint(group, name string, prog *ebpf.Program, opts *TracepointOptions) (Link, error) { if group == "" || name == "" { return nil, fmt.Errorf("group and name cannot be empty: %w", errInvalidInput) diff --git a/vendor/github.com/cilium/ebpf/link/tracing.go b/vendor/github.com/cilium/ebpf/link/tracing.go index 1e1a7834d8e..9e570afc96a 100644 --- a/vendor/github.com/cilium/ebpf/link/tracing.go +++ b/vendor/github.com/cilium/ebpf/link/tracing.go @@ -18,6 +18,25 @@ func (f *tracing) Update(new *ebpf.Program) error { return fmt.Errorf("tracing update: %w", ErrNotSupported) } +func (f *tracing) Info() (*Info, error) { + var info sys.TracingLinkInfo + if err := sys.ObjInfo(f.fd, &info); err != nil { + return nil, fmt.Errorf("tracing link info: %s", err) + } + extra := &TracingInfo{ + TargetObjId: info.TargetObjId, + TargetBtfId: info.TargetBtfId, + AttachType: info.AttachType, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil +} + // AttachFreplace attaches the given eBPF program to the function it replaces. // // The program and name can either be provided at link time, or can be provided diff --git a/vendor/github.com/cilium/ebpf/link/uprobe.go b/vendor/github.com/cilium/ebpf/link/uprobe.go index ad85024e38e..194d1d319a7 100644 --- a/vendor/github.com/cilium/ebpf/link/uprobe.go +++ b/vendor/github.com/cilium/ebpf/link/uprobe.go @@ -222,6 +222,8 @@ func (ex *Executable) address(symbol string, address, offset uint64) (uint64, er // // Functions provided by shared libraries can currently not be traced and // will result in an ErrNotSupported. +// +// The returned Link may implement [PerfEvent]. func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) { u, err := ex.uprobe(symbol, prog, opts, false) if err != nil { @@ -256,6 +258,8 @@ func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti // // Functions provided by shared libraries can currently not be traced and // will result in an ErrNotSupported. +// +// The returned Link may implement [PerfEvent]. func (ex *Executable) Uretprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) { u, err := ex.uprobe(symbol, prog, opts, true) if err != nil { diff --git a/vendor/github.com/cilium/ebpf/link/uprobe_multi.go b/vendor/github.com/cilium/ebpf/link/uprobe_multi.go index 9a8d329c8f9..aea807b329a 100644 --- a/vendor/github.com/cilium/ebpf/link/uprobe_multi.go +++ b/vendor/github.com/cilium/ebpf/link/uprobe_multi.go @@ -172,14 +172,6 @@ func (kml *uprobeMultiLink) Update(prog *ebpf.Program) error { return fmt.Errorf("update uprobe_multi: %w", ErrNotSupported) } -func (kml *uprobeMultiLink) Pin(string) error { - return fmt.Errorf("pin uprobe_multi: %w", ErrNotSupported) -} - -func (kml *uprobeMultiLink) Unpin() error { - return fmt.Errorf("unpin uprobe_multi: %w", ErrNotSupported) -} - var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", "6.6", func() error { prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ Name: "probe_upm_link", diff --git a/vendor/github.com/cilium/ebpf/link/xdp.go b/vendor/github.com/cilium/ebpf/link/xdp.go index aa8dd3a4cb3..dbfa529964e 100644 --- a/vendor/github.com/cilium/ebpf/link/xdp.go +++ b/vendor/github.com/cilium/ebpf/link/xdp.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/sys" ) // XDPAttachFlags represents how XDP program will be attached to interface. @@ -50,5 +51,26 @@ func AttachXDP(opts XDPOptions) (Link, error) { Flags: uint32(opts.Flags), }) - return rawLink, err + return &xdpLink{*rawLink}, err +} + +type xdpLink struct { + RawLink +} + +func (xdp *xdpLink) Info() (*Info, error) { + var info sys.XDPLinkInfo + if err := sys.ObjInfo(xdp.fd, &info); err != nil { + return nil, fmt.Errorf("xdp link info: %s", err) + } + extra := &XDPInfo{ + Ifindex: info.Ifindex, + } + + return &Info{ + info.Type, + info.Id, + ebpf.ProgramID(info.ProgId), + extra, + }, nil } diff --git a/vendor/github.com/cilium/ebpf/map.go b/vendor/github.com/cilium/ebpf/map.go index e46fa3f12ef..e48412cbf48 100644 --- a/vendor/github.com/cilium/ebpf/map.go +++ b/vendor/github.com/cilium/ebpf/map.go @@ -571,6 +571,24 @@ func (m *Map) Info() (*MapInfo, error) { return newMapInfoFromFd(m.fd) } +// Handle returns a reference to the Map's type information in the kernel. +// +// Returns ErrNotSupported if the kernel has no BTF support, or if there is no +// BTF associated with the Map. +func (m *Map) Handle() (*btf.Handle, error) { + info, err := m.Info() + if err != nil { + return nil, err + } + + id, ok := info.BTFID() + if !ok { + return nil, fmt.Errorf("map %s: retrieve BTF ID: %w", m, ErrNotSupported) + } + + return btf.NewHandleFromID(id) +} + // MapLookupFlags controls the behaviour of the map lookup calls. type MapLookupFlags uint64 @@ -652,7 +670,7 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) { } func (m *Map) lookupPerCPU(key, valueOut any, flags MapLookupFlags) error { - slice, err := ensurePerCPUSlice(valueOut, int(m.valueSize)) + slice, err := ensurePerCPUSlice(valueOut) if err != nil { return err } @@ -683,7 +701,7 @@ func (m *Map) lookup(key interface{}, valueOut sys.Pointer, flags MapLookupFlags } func (m *Map) lookupAndDeletePerCPU(key, valueOut any, flags MapLookupFlags) error { - slice, err := ensurePerCPUSlice(valueOut, int(m.valueSize)) + slice, err := ensurePerCPUSlice(valueOut) if err != nil { return err } @@ -695,7 +713,7 @@ func (m *Map) lookupAndDeletePerCPU(key, valueOut any, flags MapLookupFlags) err } // ensurePerCPUSlice allocates a slice for a per-CPU value if necessary. -func ensurePerCPUSlice(sliceOrPtr any, elemLength int) (any, error) { +func ensurePerCPUSlice(sliceOrPtr any) (any, error) { sliceOrPtrType := reflect.TypeOf(sliceOrPtr) if sliceOrPtrType.Kind() == reflect.Slice { // The target is a slice, the caller is responsible for ensuring that @@ -985,7 +1003,11 @@ func (m *Map) guessNonExistentKey() ([]byte, error) { // the end of all possible results, even when partial results // are returned. It should be used to evaluate when lookup is "done". func (m *Map) BatchLookup(cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { - return m.batchLookup(sys.BPF_MAP_LOOKUP_BATCH, cursor, keysOut, valuesOut, opts) + n, err := m.batchLookup(sys.BPF_MAP_LOOKUP_BATCH, cursor, keysOut, valuesOut, opts) + if err != nil { + return n, fmt.Errorf("map batch lookup: %w", err) + } + return n, nil } // BatchLookupAndDelete looks up many elements in a map at once, @@ -1005,7 +1027,11 @@ func (m *Map) BatchLookup(cursor *MapBatchCursor, keysOut, valuesOut interface{} // the end of all possible results, even when partial results // are returned. It should be used to evaluate when lookup is "done". func (m *Map) BatchLookupAndDelete(cursor *MapBatchCursor, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { - return m.batchLookup(sys.BPF_MAP_LOOKUP_AND_DELETE_BATCH, cursor, keysOut, valuesOut, opts) + n, err := m.batchLookup(sys.BPF_MAP_LOOKUP_AND_DELETE_BATCH, cursor, keysOut, valuesOut, opts) + if err != nil { + return n, fmt.Errorf("map batch lookup and delete: %w", err) + } + return n, nil } // MapBatchCursor represents a starting point for a batch operation. @@ -1027,7 +1053,11 @@ func (m *Map) batchLookup(cmd sys.Cmd, cursor *MapBatchCursor, keysOut, valuesOu valueBuf := sysenc.SyscallOutput(valuesOut, count*int(m.fullValueSize)) n, err := m.batchLookupCmd(cmd, cursor, count, keysOut, valueBuf.Pointer(), opts) - if err != nil { + if errors.Is(err, unix.ENOSPC) { + // Hash tables return ENOSPC when the size of the batch is smaller than + // any bucket. + return n, fmt.Errorf("%w (batch size too small?)", err) + } else if err != nil { return n, err } diff --git a/vendor/github.com/cilium/ebpf/perf/reader.go b/vendor/github.com/cilium/ebpf/perf/reader.go index 22548c0d823..ee557e44954 100644 --- a/vendor/github.com/cilium/ebpf/perf/reader.go +++ b/vendor/github.com/cilium/ebpf/perf/reader.go @@ -13,12 +13,14 @@ import ( "github.com/cilium/ebpf" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/epoll" + "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) var ( - ErrClosed = os.ErrClosed - errEOR = errors.New("end of ring") + ErrClosed = os.ErrClosed + ErrFlushed = epoll.ErrFlushed + errEOR = errors.New("end of ring") ) var perfEventHeaderSize = binary.Size(perfEventHeader{}) @@ -30,10 +32,6 @@ type perfEventHeader struct { Size uint16 } -func cpuForEvent(event *unix.EpollEvent) int { - return int(event.Pad) -} - // Record contains either a sample or a counter of the // number of lost samples. type Record struct { @@ -138,32 +136,27 @@ func readRawSample(rd io.Reader, buf, sampleBuf []byte) ([]byte, error) { // Reader allows reading bpf_perf_event_output // from user space. type Reader struct { - poller *epoll.Poller - deadline time.Time + poller *epoll.Poller // mu protects read/write access to the Reader structure with the - // exception of 'pauseFds', which is protected by 'pauseMu'. + // exception fields protected by 'pauseMu'. // If locking both 'mu' and 'pauseMu', 'mu' must be locked first. - mu sync.Mutex - - // Closing a PERF_EVENT_ARRAY removes all event fds - // stored in it, so we keep a reference alive. - array *ebpf.Map - rings []*perfEventRing - epollEvents []unix.EpollEvent - epollRings []*perfEventRing - eventHeader []byte - - // pauseFds are a copy of the fds in 'rings', protected by 'pauseMu'. - // These allow Pause/Resume to be executed independently of any ongoing - // Read calls, which would otherwise need to be interrupted. - pauseMu sync.Mutex - pauseFds []int - - paused bool + mu sync.Mutex + array *ebpf.Map + rings []*perfEventRing + epollEvents []unix.EpollEvent + epollRings []*perfEventRing + eventHeader []byte + deadline time.Time overwritable bool + bufferSize int + pendingErr error - bufferSize int + // pauseMu protects eventFds so that Pause / Resume can be invoked while + // Read is blocked. + pauseMu sync.Mutex + eventFds []*sys.FD + paused bool } // ReaderOptions control the behaviour of the user @@ -193,6 +186,12 @@ func NewReader(array *ebpf.Map, perCPUBuffer int) (*Reader, error) { // NewReaderWithOptions creates a new reader with the given options. func NewReaderWithOptions(array *ebpf.Map, perCPUBuffer int, opts ReaderOptions) (pr *Reader, err error) { + closeOnError := func(c io.Closer) { + if err != nil { + c.Close() + } + } + if perCPUBuffer < 1 { return nil, errors.New("perCPUBuffer must be larger than 0") } @@ -201,57 +200,47 @@ func NewReaderWithOptions(array *ebpf.Map, perCPUBuffer int, opts ReaderOptions) } var ( - fds []int nCPU = int(array.MaxEntries()) rings = make([]*perfEventRing, 0, nCPU) - pauseFds = make([]int, 0, nCPU) + eventFds = make([]*sys.FD, 0, nCPU) ) poller, err := epoll.New() if err != nil { return nil, err } - - defer func() { - if err != nil { - poller.Close() - for _, fd := range fds { - unix.Close(fd) - } - for _, ring := range rings { - if ring != nil { - ring.Close() - } - } - } - }() + defer closeOnError(poller) // bpf_perf_event_output checks which CPU an event is enabled on, // but doesn't allow using a wildcard like -1 to specify "all CPUs". // Hence we have to create a ring for each CPU. bufferSize := 0 for i := 0; i < nCPU; i++ { - ring, err := newPerfEventRing(i, perCPUBuffer, opts) + event, ring, err := newPerfEventRing(i, perCPUBuffer, opts) if errors.Is(err, unix.ENODEV) { // The requested CPU is currently offline, skip it. rings = append(rings, nil) - pauseFds = append(pauseFds, -1) + eventFds = append(eventFds, nil) continue } if err != nil { return nil, fmt.Errorf("failed to create perf ring for CPU %d: %v", i, err) } + defer closeOnError(event) + defer closeOnError(ring) bufferSize = ring.size() rings = append(rings, ring) - pauseFds = append(pauseFds, ring.fd) + eventFds = append(eventFds, event) - if err := poller.Add(ring.fd, i); err != nil { + if err := poller.Add(event.Int(), 0); err != nil { return nil, err } } + // Closing a PERF_EVENT_ARRAY removes all event fds + // stored in it, so we keep a reference alive. array, err = array.Clone() if err != nil { return nil, err @@ -265,7 +254,7 @@ func NewReaderWithOptions(array *ebpf.Map, perCPUBuffer int, opts ReaderOptions) epollEvents: make([]unix.EpollEvent, len(rings)), epollRings: make([]*perfEventRing, 0, len(rings)), eventHeader: make([]byte, perfEventHeaderSize), - pauseFds: pauseFds, + eventFds: eventFds, overwritable: opts.Overwritable, bufferSize: bufferSize, } @@ -291,17 +280,25 @@ func (pr *Reader) Close() error { } // Trying to poll will now fail, so Read() can't block anymore. Acquire the - // lock so that we can clean up. + // locks so that we can clean up. pr.mu.Lock() defer pr.mu.Unlock() + pr.pauseMu.Lock() + defer pr.pauseMu.Unlock() + for _, ring := range pr.rings { if ring != nil { ring.Close() } } + for _, event := range pr.eventFds { + if event != nil { + event.Close() + } + } pr.rings = nil - pr.pauseFds = nil + pr.eventFds = nil pr.array.Close() return nil @@ -320,16 +317,20 @@ func (pr *Reader) SetDeadline(t time.Time) { // Read the next record from the perf ring buffer. // -// The function blocks until there are at least Watermark bytes in one +// The method blocks until there are at least Watermark bytes in one // of the per CPU buffers. Records from buffers below the Watermark // are not returned. // // Records can contain between 0 and 7 bytes of trailing garbage from the ring // depending on the input sample's length. // -// Calling Close interrupts the function. +// Calling [Close] interrupts the method with [os.ErrClosed]. Calling [Flush] +// makes it return all records currently in the ring buffer, followed by [ErrFlushed]. +// +// Returns [os.ErrDeadlineExceeded] if a deadline was set and after all records +// have been read from the ring. // -// Returns os.ErrDeadlineExceeded if a deadline was set. +// See [Reader.ReadInto] for a more efficient version of this method. func (pr *Reader) Read() (Record, error) { var r Record @@ -338,7 +339,7 @@ func (pr *Reader) Read() (Record, error) { var errMustBePaused = fmt.Errorf("perf ringbuffer: must have been paused before reading overwritable buffer") -// ReadInto is like Read except that it allows reusing Record and associated buffers. +// ReadInto is like [Reader.Read] except that it allows reusing Record and associated buffers. func (pr *Reader) ReadInto(rec *Record) error { pr.mu.Lock() defer pr.mu.Unlock() @@ -356,12 +357,24 @@ func (pr *Reader) ReadInto(rec *Record) error { for { if len(pr.epollRings) == 0 { + if pe := pr.pendingErr; pe != nil { + // All rings have been emptied since the error occurred, return + // appropriate error. + pr.pendingErr = nil + return pe + } + // NB: The deferred pauseMu.Unlock will panic if Wait panics, which // might obscure the original panic. pr.pauseMu.Unlock() - nEvents, err := pr.poller.Wait(pr.epollEvents, pr.deadline) + _, err := pr.poller.Wait(pr.epollEvents, pr.deadline) pr.pauseMu.Lock() - if err != nil { + + if errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, ErrFlushed) { + // We've hit the deadline, check whether there is any data in + // the rings that we've not been woken up for. + pr.pendingErr = err + } else if err != nil { return err } @@ -370,14 +383,11 @@ func (pr *Reader) ReadInto(rec *Record) error { return errMustBePaused } - for _, event := range pr.epollEvents[:nEvents] { - ring := pr.rings[cpuForEvent(&event)] - pr.epollRings = append(pr.epollRings, ring) - - // Read the current head pointer now, not every time - // we read a record. This prevents a single fast producer - // from keeping the reader busy. + // Waking up userspace is expensive, make the most of it by checking + // all rings. + for _, ring := range pr.rings { ring.loadHead() + pr.epollRings = append(pr.epollRings, ring) } } @@ -406,11 +416,11 @@ func (pr *Reader) Pause() error { pr.pauseMu.Lock() defer pr.pauseMu.Unlock() - if pr.pauseFds == nil { + if pr.eventFds == nil { return fmt.Errorf("%w", ErrClosed) } - for i := range pr.pauseFds { + for i := range pr.eventFds { if err := pr.array.Delete(uint32(i)); err != nil && !errors.Is(err, ebpf.ErrKeyNotExist) { return fmt.Errorf("could't delete event fd for CPU %d: %w", i, err) } @@ -428,16 +438,16 @@ func (pr *Reader) Resume() error { pr.pauseMu.Lock() defer pr.pauseMu.Unlock() - if pr.pauseFds == nil { + if pr.eventFds == nil { return fmt.Errorf("%w", ErrClosed) } - for i, fd := range pr.pauseFds { - if fd == -1 { + for i, fd := range pr.eventFds { + if fd == nil { continue } - if err := pr.array.Put(uint32(i), uint32(fd)); err != nil { + if err := pr.array.Put(uint32(i), fd.Uint()); err != nil { return fmt.Errorf("couldn't put event fd %d for CPU %d: %w", fd, i, err) } } @@ -452,6 +462,12 @@ func (pr *Reader) BufferSize() int { return pr.bufferSize } +// Flush unblocks Read/ReadInto and successive Read/ReadInto calls will return pending samples at this point, +// until you receive a [ErrFlushed] error. +func (pr *Reader) Flush() error { + return pr.poller.Flush() +} + // NB: Has to be preceded by a call to ring.loadHead. func (pr *Reader) readRecordFromRing(rec *Record, ring *perfEventRing) error { defer ring.writeTail() diff --git a/vendor/github.com/cilium/ebpf/perf/ring.go b/vendor/github.com/cilium/ebpf/perf/ring.go index 9bd95926369..63555f323b0 100644 --- a/vendor/github.com/cilium/ebpf/perf/ring.go +++ b/vendor/github.com/cilium/ebpf/perf/ring.go @@ -10,31 +10,37 @@ import ( "sync/atomic" "unsafe" + "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) // perfEventRing is a page of metadata followed by // a variable number of pages which form a ring buffer. type perfEventRing struct { - fd int cpu int mmap []byte ringReader } -func newPerfEventRing(cpu, perCPUBuffer int, opts ReaderOptions) (*perfEventRing, error) { +func newPerfEventRing(cpu, perCPUBuffer int, opts ReaderOptions) (_ *sys.FD, _ *perfEventRing, err error) { + closeOnError := func(c io.Closer) { + if err != nil { + c.Close() + } + } + if opts.Watermark >= perCPUBuffer { - return nil, errors.New("watermark must be smaller than perCPUBuffer") + return nil, nil, errors.New("watermark must be smaller than perCPUBuffer") } fd, err := createPerfEvent(cpu, opts) if err != nil { - return nil, err + return nil, nil, err } + defer closeOnError(fd) - if err := unix.SetNonblock(fd, true); err != nil { - unix.Close(fd) - return nil, err + if err := unix.SetNonblock(fd.Int(), true); err != nil { + return nil, nil, err } protections := unix.PROT_READ @@ -42,10 +48,9 @@ func newPerfEventRing(cpu, perCPUBuffer int, opts ReaderOptions) (*perfEventRing protections |= unix.PROT_WRITE } - mmap, err := unix.Mmap(fd, 0, perfBufferSize(perCPUBuffer), protections, unix.MAP_SHARED) + mmap, err := unix.Mmap(fd.Int(), 0, perfBufferSize(perCPUBuffer), protections, unix.MAP_SHARED) if err != nil { - unix.Close(fd) - return nil, fmt.Errorf("can't mmap: %v", err) + return nil, nil, fmt.Errorf("can't mmap: %v", err) } // This relies on the fact that we allocate an extra metadata page, @@ -62,14 +67,13 @@ func newPerfEventRing(cpu, perCPUBuffer int, opts ReaderOptions) (*perfEventRing } ring := &perfEventRing{ - fd: fd, cpu: cpu, mmap: mmap, ringReader: reader, } runtime.SetFinalizer(ring, (*perfEventRing).Close) - return ring, nil + return fd, ring, nil } // perfBufferSize returns a valid mmap buffer size for use with perf_event_open (1+2^n pages) @@ -88,17 +92,14 @@ func perfBufferSize(perCPUBuffer int) int { return nPages * pageSize } -func (ring *perfEventRing) Close() { +func (ring *perfEventRing) Close() error { runtime.SetFinalizer(ring, nil) - - _ = unix.Close(ring.fd) - _ = unix.Munmap(ring.mmap) - - ring.fd = -1 + mmap := ring.mmap ring.mmap = nil + return unix.Munmap(mmap) } -func createPerfEvent(cpu int, opts ReaderOptions) (int, error) { +func createPerfEvent(cpu int, opts ReaderOptions) (*sys.FD, error) { wakeup := 0 bits := 0 if opts.WakeupEvents > 0 { @@ -126,9 +127,9 @@ func createPerfEvent(cpu int, opts ReaderOptions) (int, error) { attr.Size = uint32(unsafe.Sizeof(attr)) fd, err := unix.PerfEventOpen(&attr, -1, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) if err != nil { - return -1, fmt.Errorf("can't create perf event: %w", err) + return nil, fmt.Errorf("can't create perf event: %w", err) } - return fd, nil + return sys.NewFD(fd) } type ringReader interface { diff --git a/vendor/github.com/cilium/ebpf/run-tests.sh b/vendor/github.com/cilium/ebpf/run-tests.sh deleted file mode 100644 index c7ff7ea333d..00000000000 --- a/vendor/github.com/cilium/ebpf/run-tests.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env bash -# Test the current package under a different kernel. -# Requires virtme and qemu to be installed. -# Examples: -# Run all tests on a 5.4 kernel -# $ ./run-tests.sh 5.4 -# Run a subset of tests: -# $ ./run-tests.sh 5.4 ./link -# Run using a local kernel image -# $ ./run-tests.sh /path/to/bzImage - -set -euo pipefail - -script="$(realpath "$0")" -readonly script - -source "$(dirname "$script")/testdata/sh/lib.sh" - -quote_env() { - for var in "$@"; do - if [ -v "$var" ]; then - printf "%s=%q " "$var" "${!var}" - fi - done -} - -declare -a preserved_env=( - PATH - CI_MAX_KERNEL_VERSION - TEST_SEED - KERNEL_VERSION -) - -# This script is a bit like a Matryoshka doll since it keeps re-executing itself -# in various different contexts: -# -# 1. invoked by the user like run-tests.sh 5.4 -# 2. invoked by go test like run-tests.sh --exec-vm -# 3. invoked by init in the vm like run-tests.sh --exec-test -# -# This allows us to use all available CPU on the host machine to compile our -# code, and then only use the VM to execute the test. This is because the VM -# is usually slower at compiling than the host. -if [[ "${1:-}" = "--exec-vm" ]]; then - shift - - input="$1" - shift - - # Use sudo if /dev/kvm isn't accessible by the current user. - sudo="" - if [[ ! -r /dev/kvm || ! -w /dev/kvm ]]; then - sudo="sudo" - fi - readonly sudo - - testdir="$(dirname "$1")" - output="$(mktemp -d)" - printf -v cmd "%q " "$@" - - if [[ "$(stat -c '%t:%T' -L /proc/$$/fd/0)" == "1:3" ]]; then - # stdin is /dev/null, which doesn't play well with qemu. Use a fifo as a - # blocking substitute. - mkfifo "${output}/fake-stdin" - # Open for reading and writing to avoid blocking. - exec 0<> "${output}/fake-stdin" - rm "${output}/fake-stdin" - fi - - if ! $sudo virtme-run --kimg "${input}/boot/vmlinuz" --cpus 2 --memory 1G --pwd \ - --rwdir="${testdir}=${testdir}" \ - --rodir=/run/input="${input}" \ - --rwdir=/run/output="${output}" \ - --script-sh "$(quote_env "${preserved_env[@]}") \"$script\" \ - --exec-test $cmd"; then - exit 23 - fi - - if ! [[ -e "${output}/status" ]]; then - exit 42 - fi - - rc=$(<"${output}/status") - $sudo rm -r "$output" - exit "$rc" -elif [[ "${1:-}" = "--exec-test" ]]; then - shift - - mount -t bpf bpf /sys/fs/bpf - mount -t tracefs tracefs /sys/kernel/debug/tracing - - if [[ -d "/run/input/usr/src/linux/tools/testing/selftests/bpf" ]]; then - export KERNEL_SELFTESTS="/run/input/usr/src/linux/tools/testing/selftests/bpf" - fi - - if [[ -d "/run/input/lib/modules" ]]; then - find /run/input/lib/modules -type f -name bpf_testmod.ko -exec insmod {} \; - fi - - dmesg --clear - rc=0 - "$@" || rc=$? - dmesg - echo $rc > "/run/output/status" - exit $rc # this return code is "swallowed" by qemu -fi - -if [[ -z "${1:-}" ]]; then - echo "Expecting kernel version or path as first argument" - exit 1 -fi - -input="$(mktemp -d)" -readonly input - -if [[ -f "${1}" ]]; then - # First argument is a local file. - readonly kernel="${1}" - cp "${1}" "${input}/boot/vmlinuz" -else - readonly kernel="${1}" - - # LINUX_VERSION_CODE test compares this to discovered value. - export KERNEL_VERSION="${1}" - - if ! extract_oci_image "ghcr.io/cilium/ci-kernels:${kernel}-selftests" "${input}"; then - extract_oci_image "ghcr.io/cilium/ci-kernels:${kernel}" "${input}" - fi -fi -shift - -args=(-short -coverpkg=./... -coverprofile=coverage.out -count 1 ./...) -if (( $# > 0 )); then - args=("$@") -fi - -export GOFLAGS=-mod=readonly -export CGO_ENABLED=0 - -echo Testing on "${kernel}" -go test -exec "$script --exec-vm $input" "${args[@]}" -echo "Test successful on ${kernel}" - -rm -r "${input}" diff --git a/vendor/modules.txt b/vendor/modules.txt index 6526d983fcf..d665f9b009f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -195,8 +195,8 @@ github.com/cilium/cilium/pkg/u8proto github.com/cilium/cilium/pkg/version github.com/cilium/cilium/pkg/versioncheck github.com/cilium/cilium/pkg/wireguard/types -# github.com/cilium/ebpf v0.15.0 -## explicit; go 1.21.0 +# github.com/cilium/ebpf v0.15.1-0.20240703142256-12aca1027ee8 +## explicit; go 1.21 github.com/cilium/ebpf github.com/cilium/ebpf/asm github.com/cilium/ebpf/btf From 44ec08bfcf4f7de342dce85d60c96ce8f97e764a Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Sun, 23 Jun 2024 19:29:45 +0000 Subject: [PATCH 02/11] tetragon: Add HasLinkPin function .. to detect link pinning functionality. Signed-off-by: Jiri Olsa --- cmd/tetragon/main.go | 5 +++-- pkg/bpf/detect.go | 47 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/cmd/tetragon/main.go b/cmd/tetragon/main.go index 7db778c297b..aac09252f39 100644 --- a/cmd/tetragon/main.go +++ b/cmd/tetragon/main.go @@ -208,8 +208,6 @@ func tetragonExecute() error { } defer pidfile.Delete() - log.Info("BPF detected features: ", bpf.LogFeatures()) - if option.Config.ForceLargeProgs && option.Config.ForceSmallProgs { log.Fatalf("Can't specify --force-small-progs and --force-large-progs together") } @@ -245,6 +243,9 @@ func tetragonExecute() error { bpf.CheckOrMountDebugFS() bpf.CheckOrMountCgroup2() + // we need file system mounts setup above before we detect features + log.Info("BPF detected features: ", bpf.LogFeatures()) + if option.Config.PprofAddr != "" { go func() { if err := servePprof(option.Config.PprofAddr); err != nil { diff --git a/pkg/bpf/detect.go b/pkg/bpf/detect.go index bbed59a3074..7c675456488 100644 --- a/pkg/bpf/detect.go +++ b/pkg/bpf/detect.go @@ -9,6 +9,7 @@ package bpf import ( "errors" "fmt" + "path/filepath" "sync" "unsafe" @@ -32,6 +33,7 @@ var ( buildid Feature modifyReturn Feature modifyReturnSyscall Feature + linkPin Feature ) func HasOverrideHelper() bool { @@ -218,8 +220,49 @@ func HasProgramLargeSize() bool { return features.HaveLargeInstructions() == nil } +func detectLinkPin() (bool, error) { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_bpf_kprobe", + Type: ebpf.Kprobe, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + License: "MIT", + }) + if err != nil { + return false, err + } + defer prog.Close() + + lnk, err := link.Kprobe("vprintk", prog, nil) + if err != nil { + return false, err + } + defer lnk.Close() + + if err := lnk.Pin(filepath.Join(GetMapRoot(), "test-link")); err != nil { + return false, err + } + lnk.Unpin() + return true, nil +} + +func HasLinkPin() bool { + linkPin.init.Do(func() { + var err error + + linkPin.detected, err = detectLinkPin() + if err != nil { + logger.GetLogger().WithError(err).Error("detect link pin") + } + }) + return linkPin.detected +} + func LogFeatures() string { - return fmt.Sprintf("override_return: %t, buildid: %t, kprobe_multi: %t, uprobe_multi %t, fmodret: %t, fmodret_syscall: %t, signal: %t, large: %t", + return fmt.Sprintf("override_return: %t, buildid: %t, kprobe_multi: %t, uprobe_multi %t, fmodret: %t, fmodret_syscall: %t, signal: %t, large: %t, link_pin: %t", HasOverrideHelper(), HasBuildId(), HasKprobeMulti(), HasUprobeMulti(), - HasModifyReturn(), HasModifyReturnSyscall(), HasSignalHelper(), HasProgramLargeSize()) + HasModifyReturn(), HasModifyReturnSyscall(), HasSignalHelper(), HasProgramLargeSize(), + HasLinkPin()) } From 316ce1c7ae09ebd83024e219c072416ef80f30a1 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 24 Jun 2024 08:54:38 +0000 Subject: [PATCH 03/11] tetragon: Add --bpf-dir option Adding --bpf-dir option to alter to 'map dir' for tetragon, the default stays '/sys/fs/bpf/tetragon'. Signed-off-by: Jiri Olsa --- cmd/tetragon/main.go | 1 + docs/data/tetragon_flags.yaml | 3 +++ pkg/option/config.go | 2 ++ pkg/option/flags.go | 6 ++++++ 4 files changed, 12 insertions(+) diff --git a/cmd/tetragon/main.go b/cmd/tetragon/main.go index aac09252f39..0726eb6b3f4 100644 --- a/cmd/tetragon/main.go +++ b/cmd/tetragon/main.go @@ -242,6 +242,7 @@ func tetragonExecute() error { bpf.CheckOrMountFS("") bpf.CheckOrMountDebugFS() bpf.CheckOrMountCgroup2() + bpf.SetMapPrefix(option.Config.BpfDir) // we need file system mounts setup above before we detect features log.Info("BPF detected features: ", bpf.LogFeatures()) diff --git a/docs/data/tetragon_flags.yaml b/docs/data/tetragon_flags.yaml index d6bcb06f28d..7a1973742c2 100644 --- a/docs/data/tetragon_flags.yaml +++ b/docs/data/tetragon_flags.yaml @@ -3,6 +3,9 @@ synopsis: | Tetragon - eBPF-based Security Observability and Runtime Enforcement usage: tetragon [flags] options: + - name: bpf-dir + default_value: tetragon + usage: Set tetragon bpf directory (default 'tetragon') - name: bpf-lib default_value: /var/lib/tetragon/ usage: Location of Tetragon libs (btf and bpf files) diff --git a/pkg/option/config.go b/pkg/option/config.go index 25925822a26..d9f809c9f55 100644 --- a/pkg/option/config.go +++ b/pkg/option/config.go @@ -34,6 +34,8 @@ type config struct { GopsAddr string + // On start used to store bpf prefix for --bpf-dir option, + // then it's updated to cary the whole path BpfDir string LogOpts map[string]string diff --git a/pkg/option/flags.go b/pkg/option/flags.go index 6445cab1469..074eda9502d 100644 --- a/pkg/option/flags.go +++ b/pkg/option/flags.go @@ -103,6 +103,8 @@ const ( KeyHealthServerAddress = "health-server-address" KeyHealthTimeInterval = "health-server-interval" + + KeyBpfDir = "bpf-dir" ) type UsernameMetadaCode int @@ -218,6 +220,8 @@ func ReadAndSetFlags() error { Config.CgroupRate = ParseCgroupRate(viper.GetString(KeyCgroupRate)) Config.HealthServerAddress = viper.GetString(KeyHealthServerAddress) Config.HealthServerInterval = viper.GetInt(KeyHealthTimeInterval) + + Config.BpfDir = viper.GetString(KeyBpfDir) return nil } @@ -372,4 +376,6 @@ func AddFlags(flags *pflag.FlagSet) { flags.String(KeyHealthServerAddress, ":6789", "Health server address (e.g. ':6789')(use '' to disabled it)") flags.Int(KeyHealthTimeInterval, 10, "Health server interval in seconds") + + flags.String(KeyBpfDir, defaults.DefaultMapPrefix, "Set tetragon bpf directory (default 'tetragon')") } From fbdd8fda9920a9ae4d397b45892b327b88984a48 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Sat, 29 Jun 2024 15:00:43 +0000 Subject: [PATCH 04/11] tetragon: Add support to pin link Adding support to pin link through linkPin function. At the moment we limit the link pinning with new PinLink bool in program.Program object, which will be used/set in future to control link pin for program. Adding test for pin link path setup. Signed-off-by: Jiri Olsa --- pkg/sensors/program/loader.go | 34 +++++++++++++++++++++++++++++ pkg/sensors/program/loader_test.go | 35 ++++++++++++++++++++++++++++++ pkg/sensors/program/program.go | 3 +++ 3 files changed, 72 insertions(+) create mode 100644 pkg/sensors/program/loader_test.go diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index 6da127e563c..a6708fec98c 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -13,6 +13,7 @@ import ( "github.com/cilium/ebpf" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/link" + "github.com/cilium/tetragon/pkg/bpf" cachedbtf "github.com/cilium/tetragon/pkg/btf" "github.com/cilium/tetragon/pkg/logger" "github.com/cilium/tetragon/pkg/option" @@ -43,6 +44,39 @@ type LoadOpts struct { TcPrefix string } +func linkPinPath(bpfDir string, load *Program, extra ...string) string { + pinPath := filepath.Join(bpfDir, load.PinPath) + if load.Override { + pinPath = pinPath + "_override" + } + if load.RetProbe { + pinPath = pinPath + "_return" + } + if len(extra) != 0 { + pinPath = pinPath + "_" + strings.Join(extra, "_") + } + return pinPath + "_link" +} + +func linkPin(lnk link.Link, bpfDir string, load *Program, extra ...string) error { + // pinned link is not configured + if !load.PinLink { + return nil + } + // pinned link is not supported + if !bpf.HasLinkPin() { + return nil + } + + pinPath := linkPinPath(bpfDir, load, extra...) + + err := lnk.Pin(pinPath) + if err != nil { + return fmt.Errorf("pinning link '%s' failed: %w", pinPath, err) + } + return nil +} + func RawAttach(targetFD int) AttachFunc { return RawAttachWithFlags(targetFD, 0) } diff --git a/pkg/sensors/program/loader_test.go b/pkg/sensors/program/loader_test.go new file mode 100644 index 00000000000..6fc14f10003 --- /dev/null +++ b/pkg/sensors/program/loader_test.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Tetragon + +package program + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoaderLinkPinPath(t *testing.T) { + bpfDir := "/sys/fs/bpf/tetragon" + + var load *Program + var pin string + + load = Builder("", "", "", "event", "") + pin = linkPinPath(bpfDir, load) + assert.Equal(t, filepath.Join(bpfDir, "event_link"), pin) + + load = Builder("", "", "", "event", "") + load.Override = true + pin = linkPinPath(bpfDir, load) + assert.Equal(t, filepath.Join(bpfDir, "event_override_link"), pin) + + load = Builder("", "", "", "event", "").SetRetProbe(true) + pin = linkPinPath(bpfDir, load) + assert.Equal(t, filepath.Join(bpfDir, "event_return_link"), pin) + + load = Builder("", "", "", "event", "") + pin = linkPinPath(bpfDir, load, "1_sys_exit") + assert.Equal(t, filepath.Join(bpfDir, "event_1_sys_exit_link"), pin) +} diff --git a/pkg/sensors/program/program.go b/pkg/sensors/program/program.go index 2675b3a753b..def2cb8eb87 100644 --- a/pkg/sensors/program/program.go +++ b/pkg/sensors/program/program.go @@ -86,6 +86,9 @@ type Program struct { Override bool OverrideFmodRet bool + // Needs attached link to be pinned + PinLink bool + // Type is the type of BPF program. For example, tc, skb, tracepoint, // etc. Type string From 8cdcc19b5e21b9402fdf5ce1146f37c7f9f4f272 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Sat, 29 Jun 2024 15:00:59 +0000 Subject: [PATCH 05/11] tetragon: Add support to pin tracepoint links Adding support to pin tracepoint links. Signed-off-by: Jiri Olsa --- pkg/sensors/program/loader.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index a6708fec98c..a2bbccd307d 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -109,7 +109,7 @@ func RawAttachWithFlags(targetFD int, flags uint32) AttachFunc { } } -func TracepointAttach(load *Program) AttachFunc { +func TracepointAttach(load *Program, bpfDir string) AttachFunc { return func(_ *ebpf.Collection, _ *ebpf.CollectionSpec, prog *ebpf.Program, spec *ebpf.ProgramSpec) (unloader.Unloader, error) { @@ -121,6 +121,11 @@ func TracepointAttach(load *Program) AttachFunc { if err != nil { return nil, fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) } + err = linkPin(tpLink, bpfDir, load) + if err != nil { + tpLink.Close() + return nil, err + } return &unloader.RelinkUnloader{ UnloadProg: unloader.PinUnloader{Prog: prog}.Unload, IsLinked: true, @@ -520,7 +525,7 @@ func LoadTracepointProgram(bpfDir string, load *Program, verbose int) error { } } opts := &LoadOpts{ - Attach: TracepointAttach(load), + Attach: TracepointAttach(load, bpfDir), TcMap: tc.name, TcPrefix: tc.prefix, } From b850e3d063a16bd0f55e528de123abbcef5162fa Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Thu, 27 Jun 2024 09:15:06 +0000 Subject: [PATCH 06/11] tetragon: Add support to pin kprobe links Adding support to pin kprobe links for both normal and override programs. Signed-off-by: Jiri Olsa --- pkg/sensors/program/loader.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index a2bbccd307d..2040e68cba1 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -196,7 +196,8 @@ func KprobeOpen(load *Program) OpenFunc { } } -func kprobeAttach(load *Program, prog *ebpf.Program, spec *ebpf.ProgramSpec, symbol string) (unloader.Unloader, error) { +func kprobeAttach(load *Program, prog *ebpf.Program, spec *ebpf.ProgramSpec, + symbol string, bpfDir string, extra ...string) (unloader.Unloader, error) { var linkFn func() (link.Link, error) if load.RetProbe { @@ -209,6 +210,11 @@ func kprobeAttach(load *Program, prog *ebpf.Program, spec *ebpf.ProgramSpec, sym if err != nil { return nil, fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) } + err = linkPin(lnk, bpfDir, load, extra...) + if err != nil { + lnk.Close() + return nil, err + } return &unloader.RelinkUnloader{ UnloadProg: unloader.PinUnloader{Prog: prog}.Unload, IsLinked: true, @@ -241,7 +247,7 @@ func kprobeAttachOverride(load *Program, bpfDir string, return fmt.Errorf("pinning '%s' to '%s' failed: %w", load.Label, pinPath, err) } - load.unloaderOverride, err = kprobeAttach(load, prog, spec, load.Attach) + load.unloaderOverride, err = kprobeAttach(load, prog, spec, load.Attach, bpfDir) if err != nil { logger.GetLogger().Warnf("Failed to attach override program: %w", err) } @@ -310,7 +316,7 @@ func KprobeAttach(load *Program, bpfDir string) AttachFunc { } } - return kprobeAttach(load, prog, spec, load.Attach) + return kprobeAttach(load, prog, spec, load.Attach, bpfDir) } } @@ -556,7 +562,7 @@ func LoadKprobeProgram(bpfDir string, load *Program, verbose int) error { return loadProgram(bpfDir, load, opts, verbose) } -func KprobeAttachMany(load *Program, syms []string) AttachFunc { +func KprobeAttachMany(load *Program, syms []string, bpfDir string) AttachFunc { return func(_ *ebpf.Collection, _ *ebpf.CollectionSpec, prog *ebpf.Program, spec *ebpf.ProgramSpec) (unloader.Unloader, error) { @@ -567,7 +573,7 @@ func KprobeAttachMany(load *Program, syms []string) AttachFunc { } for idx := range syms { - un, err := kprobeAttach(load, prog, spec, syms[idx]) + un, err := kprobeAttach(load, prog, spec, syms[idx], bpfDir, fmt.Sprintf("%d_%s", idx, syms[idx])) if err != nil { return nil, err } @@ -580,7 +586,7 @@ func KprobeAttachMany(load *Program, syms []string) AttachFunc { func LoadKprobeProgramAttachMany(bpfDir string, load *Program, syms []string, verbose int) error { opts := &LoadOpts{ - Attach: KprobeAttachMany(load, syms), + Attach: KprobeAttachMany(load, syms, bpfDir), } return loadProgram(bpfDir, load, opts, verbose) } From a94ab3679dc65f2d22c2ec568bd745a8a97ebc7c Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Thu, 27 Jun 2024 09:19:12 +0000 Subject: [PATCH 07/11] tetragon: Add support to pin kprobe multi links Adding support to pin kprobe multi links. Signed-off-by: Jiri Olsa --- pkg/sensors/program/loader.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index 2040e68cba1..0fcc374c130 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -449,7 +449,7 @@ func LSMAttach() AttachFunc { } func multiKprobeAttach(load *Program, prog *ebpf.Program, - spec *ebpf.ProgramSpec, opts link.KprobeMultiOptions) (unloader.Unloader, error) { + spec *ebpf.ProgramSpec, opts link.KprobeMultiOptions, bpfDir string) (unloader.Unloader, error) { var lnk link.Link var err error @@ -462,6 +462,11 @@ func multiKprobeAttach(load *Program, prog *ebpf.Program, if err != nil { return nil, fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) } + err = linkPin(lnk, bpfDir, load) + if err != nil { + lnk.Close() + return nil, err + } return unloader.ChainUnloader{ unloader.PinUnloader{ Prog: prog, @@ -507,7 +512,7 @@ func MultiKprobeAttach(load *Program, bpfDir string) AttachFunc { Symbols: data.Overrides, } - load.unloaderOverride, err = multiKprobeAttach(load, progOverride, progOverrideSpec, opts) + load.unloaderOverride, err = multiKprobeAttach(load, progOverride, progOverrideSpec, opts, bpfDir) if err != nil { logger.GetLogger().Warnf("Failed to attach override program: %w", err) } @@ -518,7 +523,7 @@ func MultiKprobeAttach(load *Program, bpfDir string) AttachFunc { Cookies: data.Cookies, } - return multiKprobeAttach(load, prog, spec, opts) + return multiKprobeAttach(load, prog, spec, opts, bpfDir) } } From 394c2482383815c0d4e9c28d7659194da8c48732 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Thu, 27 Jun 2024 09:24:31 +0000 Subject: [PATCH 08/11] tetragon: Add support to pin fmodret links Adding support to pin fmodret links. Signed-off-by: Jiri Olsa --- pkg/sensors/program/loader.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index 0fcc374c130..3257369ab0f 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -290,6 +290,12 @@ func fmodretAttachOverride(load *Program, bpfDir string, return fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) } + err = linkPin(lnk, bpfDir, load) + if err != nil { + lnk.Close() + return err + } + load.unloaderOverride = &unloader.RelinkUnloader{ UnloadProg: unloader.PinUnloader{Prog: prog}.Unload, IsLinked: true, From a39bb85f97e5fbc19bdebb4485a83f34fe5fd51f Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Sat, 22 Jun 2024 20:26:27 +0000 Subject: [PATCH 09/11] tetragon: Add --keep-sensors-on-exit option Adding --keep-sensors-on-exit option to global control the pinning of maps/programs/links. By default tetragon creates and destroyes pin files for maps and programs. With --keep-sensors-on-exit option tetragon : - also create pins for links - does not unpin any pns on exit So this option will effectively keep configured policy still running even when tetragon process is gone. Signed-off-by: Jiri Olsa --- cmd/tetragon/main.go | 4 ++++ docs/data/tetragon_flags.yaml | 3 +++ pkg/option/config.go | 2 ++ pkg/option/flags.go | 6 ++++++ pkg/sensors/program/loader.go | 2 +- pkg/sensors/program/map.go | 5 ++++- pkg/sensors/unloader/unloader.go | 6 +++++- 7 files changed, 25 insertions(+), 3 deletions(-) diff --git a/cmd/tetragon/main.go b/cmd/tetragon/main.go index 0726eb6b3f4..859fd476b7d 100644 --- a/cmd/tetragon/main.go +++ b/cmd/tetragon/main.go @@ -220,6 +220,10 @@ func tetragonExecute() error { log.Info("Force loading smallprograms") } + if option.Config.KeepSensorsOnExit { + log.Info("Not unloading sensors on exit") + } + if viper.IsSet(option.KeyNetnsDir) { defaults.NetnsDir = viper.GetString(option.KeyNetnsDir) } diff --git a/docs/data/tetragon_flags.yaml b/docs/data/tetragon_flags.yaml index 7a1973742c2..0bcf725360f 100644 --- a/docs/data/tetragon_flags.yaml +++ b/docs/data/tetragon_flags.yaml @@ -134,6 +134,9 @@ options: usage: help for tetragon - name: k8s-kubeconfig-path usage: Absolute path of the kubernetes kubeconfig file + - name: keep-sensors-on-exit + default_value: "false" + usage: Do not unload sensors on exit - name: kernel usage: Kernel version - name: kmods diff --git a/pkg/option/config.go b/pkg/option/config.go index d9f809c9f55..36e33a2d0e1 100644 --- a/pkg/option/config.go +++ b/pkg/option/config.go @@ -94,6 +94,8 @@ type config struct { HealthServerAddress string HealthServerInterval int + + KeepSensorsOnExit bool } var ( diff --git a/pkg/option/flags.go b/pkg/option/flags.go index 074eda9502d..77826e616cd 100644 --- a/pkg/option/flags.go +++ b/pkg/option/flags.go @@ -105,6 +105,8 @@ const ( KeyHealthTimeInterval = "health-server-interval" KeyBpfDir = "bpf-dir" + + KeyKeepSensorsOnExit = "keep-sensors-on-exit" ) type UsernameMetadaCode int @@ -222,6 +224,8 @@ func ReadAndSetFlags() error { Config.HealthServerInterval = viper.GetInt(KeyHealthTimeInterval) Config.BpfDir = viper.GetString(KeyBpfDir) + + Config.KeepSensorsOnExit = viper.GetBool(KeyKeepSensorsOnExit) return nil } @@ -378,4 +382,6 @@ func AddFlags(flags *pflag.FlagSet) { flags.Int(KeyHealthTimeInterval, 10, "Health server interval in seconds") flags.String(KeyBpfDir, defaults.DefaultMapPrefix, "Set tetragon bpf directory (default 'tetragon')") + + flags.Bool(KeyKeepSensorsOnExit, false, "Do not unload sensors on exit") } diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index 3257369ab0f..9cbc138f8b3 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -60,7 +60,7 @@ func linkPinPath(bpfDir string, load *Program, extra ...string) string { func linkPin(lnk link.Link, bpfDir string, load *Program, extra ...string) error { // pinned link is not configured - if !load.PinLink { + if !option.Config.KeepSensorsOnExit && !load.PinLink { return nil } // pinned link is not supported diff --git a/pkg/sensors/program/map.go b/pkg/sensors/program/map.go index 89718cad8d5..e7c17bfdcc7 100644 --- a/pkg/sensors/program/map.go +++ b/pkg/sensors/program/map.go @@ -10,6 +10,7 @@ import ( "github.com/cilium/ebpf" "github.com/cilium/tetragon/pkg/logger" + "github.com/cilium/tetragon/pkg/option" "github.com/sirupsen/logrus" ) @@ -70,7 +71,9 @@ func (m *Map) Unload() error { } log.Info("map was unloaded") if m.MapHandle != nil { - m.MapHandle.Unpin() + if !option.Config.KeepSensorsOnExit { + m.MapHandle.Unpin() + } err := m.MapHandle.Close() m.MapHandle = nil return err diff --git a/pkg/sensors/unloader/unloader.go b/pkg/sensors/unloader/unloader.go index 6a31eb7e7d0..47429f32321 100644 --- a/pkg/sensors/unloader/unloader.go +++ b/pkg/sensors/unloader/unloader.go @@ -10,6 +10,7 @@ import ( "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" + "github.com/cilium/tetragon/pkg/option" "github.com/vishvananda/netlink" "go.uber.org/multierr" "golang.org/x/sys/unix" @@ -57,7 +58,10 @@ type PinUnloader struct { func (pu PinUnloader) Unload() error { defer pu.Prog.Close() - return pu.Prog.Unpin() + if !option.Config.KeepSensorsOnExit { + return pu.Prog.Unpin() + } + return nil } // PinUnloader unpins and closes a BPF program. From 2f3a4f2afaedfcdd1bdfd9df5927cbf73ebb84ce Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 24 Jun 2024 12:12:20 +0000 Subject: [PATCH 10/11] tetragon: Detect move and remove previous bpf instance Adding support to detect and remove previous bpf instance of /sysfs/bpf/tetragon directory. On start tetragon now: - detect existing '/sysfs/bpf/tetragon' (or any other configured path) - rename it to '/sysfs/bpf/tetragon_old' - loads configured policy - deletes '/sysfs/bpf/tetragon_old' directory Signed-off-by: Jiri Olsa --- cmd/tetragon/main.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/cmd/tetragon/main.go b/cmd/tetragon/main.go index 859fd476b7d..390de6a43dd 100644 --- a/cmd/tetragon/main.go +++ b/cmd/tetragon/main.go @@ -157,6 +157,34 @@ func stopProfile() { } } +func getOldBpfDir(path string) (string, error) { + if _, err := os.Stat(path); err != nil { + return "", nil + } + old := path + "_old" + // remove the 'xxx_old' leftover if neded + if _, err := os.Stat(old); err == nil { + os.RemoveAll(old) + log.Info("Found bpf leftover instance, removing: %s", old) + } + if err := os.Rename(path, old); err != nil { + return "", err + } + log.Infof("Found bpf instance: %s, moved to: %s", path, old) + return old, nil +} + +func deleteOldBpfDir(path string) { + if path == "" { + return + } + if err := os.RemoveAll(path); err != nil { + log.Errorf("Failed to remove old bpf instance '%s': %s\n", path, err) + return + } + log.Infof("Removed bpf instance: %s", path) +} + func tetragonExecute() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -248,6 +276,11 @@ func tetragonExecute() error { bpf.CheckOrMountCgroup2() bpf.SetMapPrefix(option.Config.BpfDir) + oldBpfDir, err := getOldBpfDir(bpf.MapPrefixPath()) + if err != nil { + return fmt.Errorf("Failed to move old tetragon base directory: %w", err) + } + // we need file system mounts setup above before we detect features log.Info("BPF detected features: ", bpf.LogFeatures()) @@ -483,6 +516,8 @@ func tetragonExecute() error { } } + deleteOldBpfDir(oldBpfDir) + // k8s should have metrics, so periodically log only in a non k8s if !option.Config.EnableK8s { go logStatus(ctx, obs) From 3683daf3c44b531eb7a75daab6c7b4641f723737 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Sat, 29 Jun 2024 20:44:18 +0000 Subject: [PATCH 11/11] tetragon: Add persistent enforcer tests Adding tests for persistent enforcement - kill and override. Signed-off-by: Jiri Olsa --- pkg/sensors/tracing/enforcer_test.go | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/pkg/sensors/tracing/enforcer_test.go b/pkg/sensors/tracing/enforcer_test.go index 35fb044ced9..58002e3466d 100644 --- a/pkg/sensors/tracing/enforcer_test.go +++ b/pkg/sensors/tracing/enforcer_test.go @@ -10,6 +10,7 @@ import ( "sync" "syscall" "testing" + "time" "github.com/cilium/tetragon/api/v1/tetragon" "github.com/cilium/tetragon/api/v1/tetragon/codegen/eventchecker" @@ -24,6 +25,7 @@ import ( "github.com/cilium/tetragon/pkg/observer/observertesthelper" "github.com/cilium/tetragon/pkg/option" "github.com/cilium/tetragon/pkg/policyfilter" + "github.com/cilium/tetragon/pkg/sensors" "github.com/cilium/tetragon/pkg/sensors/base" "github.com/cilium/tetragon/pkg/testutils" tus "github.com/cilium/tetragon/pkg/testutils/sensors" @@ -675,3 +677,83 @@ spec: t.Fatalf("Wrong error '%v' expected 'exit status 22'", err) } } + +func testEnforcerPersistent(t *testing.T, builder func() *EnforcerSpecBuilder, expected, test string) { + testEnforcerCheckSkip(t) + + if !bpf.HasLinkPin() { + t.Skip("skipping persistent enforcer test, link pin is not available") + } + + run := func(idx int, exp string) { + cmd := exec.Command(test, "0xfffe") + err := cmd.Run() + + if err == nil || err.Error() != exp { + t.Fatalf("run %d: Wrong error '%v' expected '%s'", idx, err, exp) + } + } + + yaml := builder().WithoutMultiKprobe().MustYAML() + configHook := []byte(yaml) + err := os.WriteFile(testConfigFile, configHook, 0644) + if err != nil { + t.Fatalf("writeFile(%s): err %s", testConfigFile, err) + } + + option.Config.KeepSensorsOnExit = true + defer func() { option.Config.KeepSensorsOnExit = false }() + + sens, err := observertesthelper.GetDefaultSensorsWithFile(t, testConfigFile, tus.Conf().TetragonLib, observertesthelper.WithMyPid()) + if err != nil { + t.Fatalf("GetDefaultObserverWithFile error: %s", err) + } + + // first run - sensors are loaded, we should get kill/override + run(1, expected) + + sensi := make([]sensors.SensorIface, 0, len(sens)) + for _, s := range sens { + sensi = append(sensi, s) + } + sensors.UnloadSensors(sensi) + + // second run - sensors are unloaded, but pins stay, we should get kill/override + run(2, expected) + + // ... and finally get rid of pinned progs/maps/links + os.RemoveAll(bpf.MapPrefixPath()) + + // bpf pinned links removal is asynchronous, we need to wait to be sure it's gone + time.Sleep(2 * time.Second) + + // third run - sensors are unloaded, map dir is removed, we should get no enforcement + run(3, "exit status 22") +} + +func TestEnforcerPersistentOverride(t *testing.T) { + test := testutils.RepoRootPath("contrib/tester-progs/enforcer-tester") + + builder := func() *EnforcerSpecBuilder { + return NewEnforcerSpecBuilder("enforcer-signal"). + WithSyscallList("sys_prctl"). + WithMatchBinaries(test). + WithOverrideValue(-17) // EEXIST + } + + testEnforcerPersistent(t, builder, "exit status 17", test) +} + +func TestEnforcerPersistentKill(t *testing.T) { + + test := testutils.RepoRootPath("contrib/tester-progs/enforcer-tester") + + builder := func() *EnforcerSpecBuilder { + return NewEnforcerSpecBuilder("enforcer-signal"). + WithSyscallList("sys_prctl"). + WithMatchBinaries(test). + WithKill(9) // SigKill + } + + testEnforcerPersistent(t, builder, "signal: killed", test) +}