From bd5f7f66748e950e34701cb52c2265c5ab2e1712 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 6 Oct 2021 11:38:13 -0700 Subject: [PATCH 01/17] *: update libbpfgo --- go.mod | 2 +- go.sum | 4 + .../aquasecurity/libbpfgo/.gitignore | 8 +- .../aquasecurity/libbpfgo/.gitmodules | 7 +- .../github.com/aquasecurity/libbpfgo/Makefile | 186 +++++++- .../aquasecurity/libbpfgo/Readme.md | 81 +++- .../libbpfgo/helpers/argumentParsers.go | 36 +- .../aquasecurity/libbpfgo/helpers/common.go | 75 ++++ .../aquasecurity/libbpfgo/helpers/elf.go | 16 +- .../libbpfgo/helpers/kernel_config.go | 399 ++++++++++++++++++ .../libbpfgo/helpers/kernel_features.go | 228 ---------- .../aquasecurity/libbpfgo/helpers/osinfo.go | 221 ++++++++++ .../aquasecurity/libbpfgo/helpers/rwArray.go | 1 + .../libbpfgo/helpers/tracelisten.go | 9 +- .../aquasecurity/libbpfgo/libbpfgo.go | 185 +++++--- vendor/modules.txt | 2 +- 16 files changed, 1122 insertions(+), 338 deletions(-) create mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/common.go create mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go delete mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go create mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go diff --git a/go.mod b/go.mod index 4ba1ded6fe..47e2f7faf1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-delve/delve go 1.16 require ( - github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac + github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c github.com/cosiner/argv v0.1.0 github.com/creack/pty v1.1.9 github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 diff --git a/go.sum b/go.sum index ab2954078c..2b5fc96495 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,10 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac h1:oehMMAySC3p8eSwcwQ8lTXxeCkkPll+AwNesUNowbJ8= github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw= +github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0 h1:WekP69tdNlVfWdlXUqxGEwbeLCMfNcURBsq0uS5dPSI= +github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw= +github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c h1:MZdKpQb3jqE0q6zWS2nt1/ZAvKAML3PvSOeifi1UQ9w= +github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= diff --git a/vendor/github.com/aquasecurity/libbpfgo/.gitignore b/vendor/github.com/aquasecurity/libbpfgo/.gitignore index 67c39a63ae..c592b1a78f 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/.gitignore +++ b/vendor/github.com/aquasecurity/libbpfgo/.gitignore @@ -1 +1,7 @@ -selftest/dist +output* +selftest/*/*.o +selftest/*/*.skel.h +selftest/*/*-static +selftest/*/*-dynamic +selftest/uprobe/ctest +selftest/uprobe/gotest diff --git a/vendor/github.com/aquasecurity/libbpfgo/.gitmodules b/vendor/github.com/aquasecurity/libbpfgo/.gitmodules index a04deb79a3..1706b4a8fc 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/.gitmodules +++ b/vendor/github.com/aquasecurity/libbpfgo/.gitmodules @@ -1,4 +1,3 @@ - -[submodule "selftest/libbpf-module"] - path = selftest/libbpf-module - url = https://github.com/libbpf/libbpf +[submodule "libbpf"] + path = libbpf + url = https://github.com/libbpf/libbpf.git diff --git a/vendor/github.com/aquasecurity/libbpfgo/Makefile b/vendor/github.com/aquasecurity/libbpfgo/Makefile index d6c26b598e..08abb51b86 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/Makefile +++ b/vendor/github.com/aquasecurity/libbpfgo/Makefile @@ -1,29 +1,171 @@ -TARGET_BPF := test/test.bpf.o -VMLINUX_H = test/vmlinux.h +BASEDIR = $(abspath ./) -GO_SRC := $(shell find . -type f -name '*.go') -BPF_SRC := $(shell find . -type f -name '*.bpf.c') -PWD := $(shell pwd) +OUTPUT = ./output +SELFTEST = ./selftest -LIBBPF_HEADERS := /usr/include/bpf -LIBBPF := "-lbpf" +CC = gcc +CLANG = clang -.PHONY: all -all: test +ARCH := $(shell uname -m) +ARCH := $(subst x86_64,amd64,$(ARCH)) -$(VMLINUX_H): - bpftool btf dump file /sys/kernel/btf/vmlinux format c > test/vmlinux.h +BTFFILE = /sys/kernel/btf/vmlinux +BPFTOOL = $(shell which bpftool || /bin/false) +GIT = $(shell which git || /bin/false) +VMLINUXH = $(OUTPUT)/vmlinux.h -go_env := CC=gcc CGO_CFLAGS="-I $(LIBBPF_HEADERS)" CGO_LDFLAGS="$(LIBBPF)" -.PHONY: test -test: $(TARGET_BPF) $(GO_SRC) - $(go_env) go test -ldflags '-extldflags "-static"' . +# libbpf -$(TARGET_BPF): $(BPF_SRC) $(VMLINUX_H) - clang \ - -g -O2 -c -target bpf \ - -o $@ $< +LIBBPF_SRC = $(abspath ./libbpf/src) +LIBBPF_OBJ = $(abspath ./$(OUTPUT)/libbpf.a) +LIBBPF_OBJDIR = $(abspath ./$(OUTPUT)/libbpf) +LIBBPF_DESTDIR = $(abspath ./$(OUTPUT)) -.PHONY: clean -clean: - rm $(TARGET_BPF) $(VMLINUX_H) +CFLAGS = -g -O2 -Wall -fpie +LDFLAGS = + +# golang + +CGO_CFLAGS_STATIC = "-I$(abspath $(OUTPUT))" +CGO_LDFLAGS_STATIC = "-lelf -lz $(LIBBPF_OBJ)" +CGO_EXTLDFLAGS_STATIC = '-w -extldflags "-static"' + +CGO_CFGLAGS_DYN = "-I. -I/usr/include/" +CGO_LDFLAGS_DYN = "-lelf -lz -lbpf" + +# default == shared lib from OS package + +all: libbpfgo-dynamic +test: libbpfgo-dynamic-test + +# libbpfgo test object + +libbpfgo-test-bpf-static: libbpfgo-static # needed for serialization + $(MAKE) -C $(SELFTEST)/build + +libbpfgo-test-bpf-dynamic: libbpfgo-dynamic # needed for serialization + $(MAKE) -C $(SELFTEST)/build + +libbpfgo-test-bpf-clean: + $(MAKE) -C $(SELFTEST)/build clean + +# libbpf: shared + +libbpfgo-dynamic: $(OUTPUT)/libbpf + CC=$(CLANG) \ + CGO_CFLAGS=$(CGO_CFLAGS_DYN) \ + CGO_LDFLAGS=$(CGO_LDFLAGS_DYN) \ + go build . + +libbpfgo-dynamic-test: libbpfgo-test-bpf-dynamic + CC=$(CLANG) \ + CGO_CFLAGS=$(CGO_CFLAGS_DYN) \ + CGO_LDFLAGS=$(CGO_LDFLAGS_DYN) \ + sudo -E go test . + +# libbpf: static + +libbpfgo-static: $(VMLINUXH) | $(LIBBPF_OBJ) + CC=$(CLANG) \ + CGO_CFLAGS=$(CGO_CFLAGS_STATIC) \ + CGO_LDFLAGS=$(CGO_LDFLAGS_STATIC) \ + GOOS=linux GOARCH=$(ARCH) \ + go build \ + -tags netgo -ldflags $(CGO_EXTLDFLAGS_STATIC) \ + . + +libbpfgo-static-test: libbpfgo-test-bpf-static + CC=$(CLANG) \ + CGO_CFLAGS=$(CGO_CFLAGS_STATIC) \ + CGO_LDFLAGS=$(CGO_LDFLAGS_STATIC) \ + GOOS=linux GOARCH=$(ARCH) \ + sudo -E -- go test \ + -tags netgo -ldflags $(CGO_EXTLDFLAGS_STATIC) \ + . + +# vmlinux header file + +.PHONY: vmlinuxh +vmlinuxh: $(VMLINUXH) + +$(VMLINUXH): $(OUTPUT) + @if [ ! -f $(BTFFILE) ]; then \ + echo "ERROR: kernel does not seem to support BTF"; \ + exit 1; \ + fi + @if [ ! -f $(VMLINUXH) ]; then \ + echo "INFO: generating $(VMLINUXH) from $(BTFFILE)"; \ + $(BPFTOOL) btf dump file $(BTFFILE) format c > $(VMLINUXH); \ + fi + +# static libbpf generation for the git submodule + +.PHONY: libbpf-static +libbpf-static: $(LIBBPF_OBJ) + +$(LIBBPF_OBJ): $(LIBBPF_SRC) $(wildcard $(LIBBPF_SRC)/*.[ch]) | $(OUTPUT)/libbpf + CC="$(CC)" CFLAGS="$(CFLAGS)" LD_FLAGS="$(LDFLAGS)" \ + $(MAKE) -C $(LIBBPF_SRC) \ + BUILD_STATIC_ONLY=1 \ + OBJDIR=$(LIBBPF_OBJDIR) \ + DESTDIR=$(LIBBPF_DESTDIR) \ + INCLUDEDIR= LIBDIR= UAPIDIR= install + +$(LIBBPF_SRC): +ifeq ($(wildcard $@), ) + echo "INFO: updating submodule 'libbpf'" + $(GIT) submodule update --init --recursive +endif + +# selftests + +SELFTESTS = $(shell find $(SELFTEST) -mindepth 1 -maxdepth 1 -type d ! -name 'common' ! -name 'build') + +define FOREACH + SELFTESTERR=0; \ + for DIR in $(SELFTESTS); do \ + echo "INFO: entering $$DIR..."; \ + $(MAKE) -j8 -C $$DIR $(1) || SELFTESTERR=1; \ + done; \ + if [ $$SELFTESTERR -eq 1 ]; then \ + exit 1; \ + fi +endef + +.PHONY: selftest +.PHONY: selftest-static +.PHONY: selftest-dynamic +.PHONY: selftest-run +.PHONY: selftest-static-run +.PHONY: selftest-dynamic-run +.PHONY: selftest-clean + +selftest: selftest-static + +selftest-static: + $(call FOREACH, main-static) +selftest-dynamic: + $(call FOREACH, main-dynamic) + +selftest-run: selftest-static-run + +selftest-static-run: + $(call FOREACH, run-static) +selftest-dynamic-run: + $(call FOREACH, run-dynamic) + +selftest-clean: + $(call FOREACH, clean) + +# output + +$(OUTPUT): + mkdir -p $(OUTPUT) + +$(OUTPUT)/libbpf: + mkdir -p $(OUTPUT)/libbpf + +# cleanup + +clean: selftest-clean libbpfgo-test-bpf-clean + rm -rf $(OUTPUT) diff --git a/vendor/github.com/aquasecurity/libbpfgo/Readme.md b/vendor/github.com/aquasecurity/libbpfgo/Readme.md index 068d2020c6..8d6df5f90c 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/Readme.md +++ b/vendor/github.com/aquasecurity/libbpfgo/Readme.md @@ -2,18 +2,67 @@ -___ +---- -libbpfgo is a Go library for working with Linux's [eBPF](https://ebpf.io/). It was created for [Tracee](https://github.com/aquasecurity/tracee), our open source Runtime Security and eBPF tracing tools written in Go. If you are interested in eBPF and it's applications, check out Tracee on Github: [https://github.com/aquasecurity/tracee](https://github.com/aquasecurity/tracee). +* [Installing](#installing) +* [Building](#building) +* [Concepts](#concepts) +* [Example](#example) +* [Releases](#releases) +* [Learn more](#learn-more) -libbpfgo is built around libbpf - the standard library for interacting with eBPF from userspace, which is a C library maintained in Linux upstream. We have created libbpfgo as a thin Go wrapper around libbpf. + +libbpfgo is a Go library for Linux's [eBPF](https://ebpf.io/) project. It was created for [Tracee](https://github.com/aquasecurity/tracee), our open source Runtime Security, and eBPF tracing tool, written in Go. If you are interested in eBPF and its applications, check out Tracee at Github: [https://github.com/aquasecurity/tracee](https://github.com/aquasecurity/tracee). + +libbpfgo is built around [libbpf](https://github.com/libbpf/libbpf) - the standard library for interacting with eBPF programs from userspace - which is a C library maintained in Linux upstream. We have created libbpfgo as a thin Go wrapper around the libbpf project. ## Installing -libbpfgo is using CGO to interop with libbpf and will expect to be linked with libbpf at run or link time. Simply importing libbpfgo is not enough to get started, and you will need to fulfill the required dependency in one of the following ways: +libbpfgo uses CGO to interop with libbpf and will expect to be linked with libbpf at run or link time. Simply importing libbpfgo is not enough to get started, and you will need to fulfill the required dependency in one of the following ways: + +1. Install libbpf as a shared object in the system. Libbpf may already be packaged for your distribution and, if not, you can build and install from source. More info [here](https://github.com/libbpf/libbpf). +1. Embed libbpf into your Go project as a vendored dependency. This means that the libbpf code is statically linked into the resulting binary, and there are no runtime dependencies. [Tracee](https://github.com/aquasecurity/tracee) takes this approach. + +## Building + +Currently you will find the following GNU Makefile rules: + +| Makefile Rule | Description | +|--------------------------|-----------------------------------| +| all | builds libbpfgo (dynamic) | +| clean | cleans entire tree | +| selftest | builds all selftests (static) | +| selftest-run | runs all selftests (static) | + +* libbpf dynamically linked (libbpf from OS) + +| Makefile Rule | Description | +|--------------------------|-----------------------------------| +| libbpfgo-dynamic | builds dynamic libbpfgo (libbpf) | +| libbpfgo-dynamic-test | 'go test' with dynamic libbpfgo | +| selftest-dynamic | build tests with dynamic libbpfgo | +| selftest-dynamic-run | run tests using dynamic libbpfgo | + +* statically compiled (libbpf submodule) -1. Install the libbpf as a shared object in the system. Libbpf may already be packaged for you distribution, if not, you can build and install from source. More info [here](https://github.com/libbpf/libbpf). -1. Embed libbpf into your Go project as a vendored dependency. This means that the libbpf code is statically linked into the resulting binary, and there are no runtime dependencies. [Tracee](https://github.com/aquasecurity/tracee) takes this approach and you can take example from it's [Makefile](https://github.com/aquasecurity/tracee/blob/f8df7da6a27f729610992b6bd52e89d510fcf384/tracee-ebpf/Makefile#L62). +| Makefile Rule | Description | +|--------------------------|-----------------------------------| +| libbpfgo-static | builds static libbpfgo (libbpf) | +| libbpfgo-static-test | 'go test' with static libbpfgo | +| selftest-static | build tests with static libbpfgo | +| selftest-static-run | run tests using static libbpfgo | + +* examples + +``` +$ make libbpfgo-static => libbpfgo statically linked with libbpf +$ make -C selftest/perfbuffers => single selftest build (static libbpf) +$ make -C selftest/perfbuffers run-dynamic => single selftest run (dynamic libbpf) +$ make selftest-static-run => will build & run all static selftests +``` + +> Note 01: dynamic builds need your OS to have a *recent enough* libbpf package (and its headers) installed. Sometimes, recent features might require the use of backported OS packages in order for your OS to contain latest *libbpf* features (sometimes required by libbpfgo). +> Note 02: static builds need `git submodule init` first. Make sure to sync the *libbpf* git submodule before trying to statically compile or test the *libbpfgo* repository. ## Concepts @@ -47,15 +96,23 @@ rb.Start() e := <-eventsChannel ``` -Please check our github milestones for an idea of the project roadmap. The general goal is to fully implement/expose libbpf's API in Go as seamlessly as possible. +## Releases +libbpfgo does not yet have a regular schedule for cutting releases. There has not yet been a major release but API backwards compatibility will be maintained for all releases with the same major release number. Milestones are created when preparing for release. -## Learn more +- __Major releases__ are cut when backwards compatibility is broken or major milestones are completed, such as reaching parity with libbpf's API. +- __Minor releases__ are cut to incorporate new support for libbpf APIs. +- __Patch releases__ are cut to incorporate important individual or groupings of bug fixes. +- __libbpf support numbering__ indicates the _minimum_ required libbpf version that must be linked in order to ensure libbpfgo compatibility. For example, `v0.2.1-libbpf-0.4.0` means that version 0.2.1 of libbpfgo requires v0.4.0 or newer of libbpf. -- Blost post on [how to Build eBPF Programs with libbpfgo](https://blog.aquasec.com/libbpf-ebpf-programs) +*Note*: some distributions might have local changes to their libbpf package and their version might include backports and/or fixes differently than upstream versions. In those cases we recommend that libbpfgo is used statically compiled. -- The [selftests](./selftest) are small programs that use libbpfgo to verify functionality, they're good examples to look at for usage. -- [tracee-ebpf](https://github.com/aquasecurity/tracee/tree/main/tracee-ebpf) is a robust consumer of this package. +## Learn more + +Please check our github milestones for an idea of the project roadmap. The general goal is to fully implement/expose libbpf's API in Go as seamlessly as possible. -- Feel free to ask questions by creating a new [Discussion](https://github.com/aquasecurity/libbpfgo/discussions) and we'd love to help. +- [How to Build eBPF Programs with libbpfgo](https://blog.aquasec.com/libbpf-ebpf-programs). +- [selftests](./selftest) are small program using libbpfgo and might be good usage examples. +- [tracee-ebpf](https://github.com/aquasecurity/tracee/tree/main/tracee-ebpf) is a robust consumer of this project. +- Feel free to ask questions by creating a new [Discussion](https://github.com/aquasecurity/libbpfgo/discussions), we'd love to help. diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go index 1967297aed..ebd0e52063 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go @@ -95,6 +95,7 @@ func ParseMemProt(prot uint32) string { f = append(f, "PROT_EXEC") } } + return strings.Join(f, "|") } @@ -104,7 +105,7 @@ func ParseMemProt(prot uint32) string { func ParseOpenFlags(flags uint32) string { var f []string - //access mode + // access mode switch { case flags&01 == 01: f = append(f, "O_WRONLY") @@ -184,6 +185,7 @@ func ParseAccessMode(mode uint32) string { f = append(f, "X_OK") } } + return strings.Join(f, "|") } @@ -200,6 +202,7 @@ func ParseExecFlags(flags uint32) string { if len(f) == 0 { f = append(f, "0") } + return strings.Join(f, "|") } @@ -282,6 +285,7 @@ func ParseCloneFlags(flags uint64) string { if len(f) == 0 { f = append(f, "0") } + return strings.Join(f, "|") } @@ -297,7 +301,9 @@ func ParseSocketType(st uint32) string { 6: "SOCK_DCCP", 10: "SOCK_PACKET", } + var f []string + if stName, ok := socketTypes[st&0xf]; ok { f = append(f, stName) } else { @@ -309,6 +315,7 @@ func ParseSocketType(st uint32) string { if st&002000000 == 002000000 { f = append(f, "SOCK_CLOEXEC") } + return strings.Join(f, "|") } @@ -362,12 +369,15 @@ func ParseSocketDomain(sd uint32) string { 43: "AF_SMC", 44: "AF_XDP", } + var res string + if sdName, ok := socketDomains[sd]; ok { res = sdName } else { res = strconv.Itoa(int(sd)) } + return res } @@ -375,19 +385,23 @@ func ParseSocketDomain(sd uint32) string { func ParseUint32IP(in uint32) string { ip := make(net.IP, net.IPv4len) binary.BigEndian.PutUint32(ip, in) + return ip.String() } -// Parse16BytesSliceIP parses the IP address encoded as 16 bytes long PrintBytesSliceIP -// It would be more correct to accept a [16]byte instead of variable lenth slice, but that would case unnecessary memory copying and type conversions +// Parse16BytesSliceIP parses the IP address encoded as 16 bytes long +// PrintBytesSliceIP. It would be more correct to accept a [16]byte instead of +// variable lenth slice, but that would case unnecessary memory copying and +// type conversions. func Parse16BytesSliceIP(in []byte) string { ip := net.IP(in) + return ip.String() } -// ParseCapability parses the `capability` bitmask argument of the `cap_capable` function -// include/uapi/linux/capability.h -func ParseCapability(cap int32) string { +// ParseCapability parses the `capability` bitmask argument of the +// `cap_capable` function include/uapi/linux/capability.h +func ParseCapability(c int32) string { var capabilities = map[int32]string{ 0: "CAP_CHOWN", 1: "CAP_DAC_OVERRIDE", @@ -428,12 +442,15 @@ func ParseCapability(cap int32) string { 36: "CAP_BLOCK_SUSPEND", 37: "CAP_AUDIT_READ", } + var res string - if capName, ok := capabilities[cap]; ok { + + if capName, ok := capabilities[c]; ok { res = capName } else { - res = strconv.Itoa(int(cap)) + res = strconv.Itoa(int(c)) } + return res } @@ -501,6 +518,7 @@ func ParsePrctlOption(op int32) string { } else { res = strconv.Itoa(int(op)) } + return res } @@ -549,6 +567,7 @@ func ParsePtraceRequest(req int64) string { } else { res = strconv.Itoa(int(req)) } + return res } @@ -599,5 +618,6 @@ func ParseBPFCmd(cmd int32) string { } else { res = strconv.Itoa(int(cmd)) } + return res } diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/common.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/common.go new file mode 100644 index 0000000000..a6db47b544 --- /dev/null +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/common.go @@ -0,0 +1,75 @@ +package helpers + +import ( + "fmt" + "os" + "strconv" + "strings" + "syscall" +) + +func checkEnvPath(env string) (string, error) { + filePath, _ := os.LookupEnv(env) + if filePath != "" { + _, err := os.Stat(filePath) + if err != nil { + return "", fmt.Errorf("could not open %s %s", env, filePath) + } + return filePath, nil + } + return "", nil +} + +// UnameRelease gets the version string of the current running kernel +func UnameRelease() (string, error) { + var uname syscall.Utsname + if err := syscall.Uname(&uname); err != nil { + return "", fmt.Errorf("could not get utsname") + } + + var buf [65]byte + for i, b := range uname.Release { + buf[i] = byte(b) + } + + ver := string(buf[:]) + ver = strings.Trim(ver, "\x00") + + return ver, nil +} + +// CompareKernelRelease will compare two given kernel version/release +// strings and return -1, 0 or 1 if given version is less, equal or bigger, +// respectively, than the given one +// +// Examples of $(uname -r): +// +// 5.11.0-31-generic (ubuntu) +// 4.18.0-305.12.1.el8_4.x86_64 (alma) +// 4.18.0-338.el8.x86_64 (stream8) +// 4.18.0-305.7.1.el8_4.centos.x86_64 (centos) +// 4.18.0-305.7.1.el8_4.centos.plus.x86_64 (centos + plus repo) +// 5.13.13-arch1-1 (archlinux) +// +func CompareKernelRelease(base, given string) int { + b := strings.Split(base, "-") // [base]-xxx + b = strings.Split(b[0], ".") // [major][minor][patch] + + g := strings.Split(given, "-") + g = strings.Split(g[0], ".") + + for n := 0; n <= 2; n++ { + i, _ := strconv.Atoi(g[n]) + j, _ := strconv.Atoi(b[n]) + + if i > j { + return 1 // given is bigger + } else if i < j { + return -1 // given is less + } else { + continue // equal + } + } + + return 0 // equal +} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go index bfe2b0e0b9..09e25f7607 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go @@ -9,15 +9,14 @@ import ( // SymbolToOffset attempts to resolve a 'symbol' name in the binary found at // 'path' to an offset. The offset can be used for attaching a u(ret)probe func SymbolToOffset(path, symbol string) (uint32, error) { - f, err := elf.Open(path) if err != nil { - return 0, fmt.Errorf("could not open elf file to resolve symbol offset: %v", err) + return 0, fmt.Errorf("could not open elf file to resolve symbol offset: %w", err) } syms, err := f.Symbols() if err != nil { - return 0, fmt.Errorf("could not open symbol section to resolve symbol offset: %v", err) + return 0, fmt.Errorf("could not open symbol section to resolve symbol offset: %w", err) } sectionsToSearchForSymbol := []*elf.Section{} @@ -29,14 +28,14 @@ func SymbolToOffset(path, symbol string) (uint32, error) { } var executableSection *elf.Section - for i := range syms { - if syms[i].Name == symbol { + for j := range syms { + if syms[j].Name == symbol { // Find what section the symbol is in by checking the executable section's // addr space. for m := range sectionsToSearchForSymbol { - if syms[i].Value > sectionsToSearchForSymbol[m].Addr && - syms[i].Value < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size { + if syms[j].Value > sectionsToSearchForSymbol[m].Addr && + syms[j].Value < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size { executableSection = sectionsToSearchForSymbol[m] } } @@ -45,8 +44,9 @@ func SymbolToOffset(path, symbol string) (uint32, error) { return 0, errors.New("could not find symbol in executable sections of binary") } - return uint32(syms[i].Value - executableSection.Addr + executableSection.Offset), nil + return uint32(syms[j].Value - executableSection.Addr + executableSection.Offset), nil } } + return 0, fmt.Errorf("symbol not found") } diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go new file mode 100644 index 0000000000..c93eda51e5 --- /dev/null +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go @@ -0,0 +1,399 @@ +package helpers + +import ( + "bufio" + "compress/gzip" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// KernelConfigOption is an abstraction of the key in key=value syntax of the kernel config file +type KernelConfigOption uint32 + +// KernelConfigOptionValue is an abstraction of the value in key=value syntax of kernel config file +type KernelConfigOptionValue uint8 + +const ( + UNDEFINED KernelConfigOptionValue = iota + BUILTIN + MODULE + STRING + ANY +) + +func (k KernelConfigOption) String() string { + return kernelConfigKeyIDToString[k] +} + +func (k KernelConfigOptionValue) String() string { + switch k { + case UNDEFINED: + return "UNDEFINED" + case BUILTIN: + return "BUILTIN" + case MODULE: + return "MODULE" + case STRING: + return "STRING" + case ANY: + return "ANY" + } + + return "" +} + +// These constants are a limited number of the total kernel config options, +// but are provided because they are most relevant for BPF development. + +const ( + CONFIG_BPF KernelConfigOption = iota + 1 + CONFIG_BPF_SYSCALL + CONFIG_HAVE_EBPF_JIT + CONFIG_BPF_JIT + CONFIG_BPF_JIT_ALWAYS_ON + CONFIG_CGROUPS + CONFIG_CGROUP_BPF + CONFIG_CGROUP_NET_CLASSID + CONFIG_SOCK_CGROUP_DATA + CONFIG_BPF_EVENTS + CONFIG_KPROBE_EVENTS + CONFIG_UPROBE_EVENTS + CONFIG_TRACING + CONFIG_FTRACE_SYSCALLS + CONFIG_FUNCTION_ERROR_INJECTION + CONFIG_BPF_KPROBE_OVERRIDE + CONFIG_NET + CONFIG_XDP_SOCKETS + CONFIG_LWTUNNEL_BPF + CONFIG_NET_ACT_BPF + CONFIG_NET_CLS_BPF + CONFIG_NET_CLS_ACT + CONFIG_NET_SCH_INGRESS + CONFIG_XFRM + CONFIG_IP_ROUTE_CLASSID + CONFIG_IPV6_SEG6_BPF + CONFIG_BPF_LIRC_MODE2 + CONFIG_BPF_STREAM_PARSER + CONFIG_NETFILTER_XT_MATCH_BPF + CONFIG_BPFILTER + CONFIG_BPFILTER_UMH + CONFIG_TEST_BPF + CONFIG_HZ + CONFIG_DEBUG_INFO_BTF + CONFIG_DEBUG_INFO_BTF_MODULES + CONFIG_BPF_LSM + CONFIG_BPF_PRELOAD + CONFIG_BPF_PRELOAD_UMD + CUSTOM_OPTION_START KernelConfigOption = 1000 +) + +var kernelConfigKeyStringToID = map[string]KernelConfigOption{ + "CONFIG_BPF": CONFIG_BPF, + "CONFIG_BPF_SYSCALL": CONFIG_BPF_SYSCALL, + "CONFIG_HAVE_EBPF_JIT": CONFIG_HAVE_EBPF_JIT, + "CONFIG_BPF_JIT": CONFIG_BPF_JIT, + "CONFIG_BPF_JIT_ALWAYS_ON": CONFIG_BPF_JIT_ALWAYS_ON, + "CONFIG_CGROUPS": CONFIG_CGROUPS, + "CONFIG_CGROUP_BPF": CONFIG_CGROUP_BPF, + "CONFIG_CGROUP_NET_CLASSID": CONFIG_CGROUP_NET_CLASSID, + "CONFIG_SOCK_CGROUP_DATA": CONFIG_SOCK_CGROUP_DATA, + "CONFIG_BPF_EVENTS": CONFIG_BPF_EVENTS, + "CONFIG_KPROBE_EVENTS": CONFIG_KPROBE_EVENTS, + "CONFIG_UPROBE_EVENTS": CONFIG_UPROBE_EVENTS, + "CONFIG_TRACING": CONFIG_TRACING, + "CONFIG_FTRACE_SYSCALLS": CONFIG_FTRACE_SYSCALLS, + "CONFIG_FUNCTION_ERROR_INJECTION": CONFIG_FUNCTION_ERROR_INJECTION, + "CONFIG_BPF_KPROBE_OVERRIDE": CONFIG_BPF_KPROBE_OVERRIDE, + "CONFIG_NET": CONFIG_NET, + "CONFIG_XDP_SOCKETS": CONFIG_XDP_SOCKETS, + "CONFIG_LWTUNNEL_BPF": CONFIG_LWTUNNEL_BPF, + "CONFIG_NET_ACT_BPF": CONFIG_NET_ACT_BPF, + "CONFIG_NET_CLS_BPF": CONFIG_NET_CLS_BPF, + "CONFIG_NET_CLS_ACT": CONFIG_NET_CLS_ACT, + "CONFIG_NET_SCH_INGRESS": CONFIG_NET_SCH_INGRESS, + "CONFIG_XFRM": CONFIG_XFRM, + "CONFIG_IP_ROUTE_CLASSID": CONFIG_IP_ROUTE_CLASSID, + "CONFIG_IPV6_SEG6_BPF": CONFIG_IPV6_SEG6_BPF, + "CONFIG_BPF_LIRC_MODE2": CONFIG_BPF_LIRC_MODE2, + "CONFIG_BPF_STREAM_PARSER": CONFIG_BPF_STREAM_PARSER, + "CONFIG_NETFILTER_XT_MATCH_BPF": CONFIG_NETFILTER_XT_MATCH_BPF, + "CONFIG_BPFILTER": CONFIG_BPFILTER, + "CONFIG_BPFILTER_UMH": CONFIG_BPFILTER_UMH, + "CONFIG_TEST_BPF": CONFIG_TEST_BPF, + "CONFIG_HZ": CONFIG_HZ, + "CONFIG_DEBUG_INFO_BTF": CONFIG_DEBUG_INFO_BTF, + "CONFIG_DEBUG_INFO_BTF_MODULES": CONFIG_DEBUG_INFO_BTF_MODULES, + "CONFIG_BPF_LSM": CONFIG_BPF_LSM, + "CONFIG_BPF_PRELOAD": CONFIG_BPF_PRELOAD, + "CONFIG_BPF_PRELOAD_UMD": CONFIG_BPF_PRELOAD_UMD, +} + +var kernelConfigKeyIDToString = map[KernelConfigOption]string{ + CONFIG_BPF: "CONFIG_BPF", + CONFIG_BPF_SYSCALL: "CONFIG_BPF_SYSCALL", + CONFIG_HAVE_EBPF_JIT: "CONFIG_HAVE_EBPF_JIT", + CONFIG_BPF_JIT: "CONFIG_BPF_JIT", + CONFIG_BPF_JIT_ALWAYS_ON: "CONFIG_BPF_JIT_ALWAYS_ON", + CONFIG_CGROUPS: "CONFIG_CGROUPS", + CONFIG_CGROUP_BPF: "CONFIG_CGROUP_BPF", + CONFIG_CGROUP_NET_CLASSID: "CONFIG_CGROUP_NET_CLASSID", + CONFIG_SOCK_CGROUP_DATA: "CONFIG_SOCK_CGROUP_DATA", + CONFIG_BPF_EVENTS: "CONFIG_BPF_EVENTS", + CONFIG_KPROBE_EVENTS: "CONFIG_KPROBE_EVENTS", + CONFIG_UPROBE_EVENTS: "CONFIG_UPROBE_EVENTS", + CONFIG_TRACING: "CONFIG_TRACING", + CONFIG_FTRACE_SYSCALLS: "CONFIG_FTRACE_SYSCALLS", + CONFIG_FUNCTION_ERROR_INJECTION: "CONFIG_FUNCTION_ERROR_INJECTION", + CONFIG_BPF_KPROBE_OVERRIDE: "CONFIG_BPF_KPROBE_OVERRIDE", + CONFIG_NET: "CONFIG_NET", + CONFIG_XDP_SOCKETS: "CONFIG_XDP_SOCKETS", + CONFIG_LWTUNNEL_BPF: "CONFIG_LWTUNNEL_BPF", + CONFIG_NET_ACT_BPF: "CONFIG_NET_ACT_BPF", + CONFIG_NET_CLS_BPF: "CONFIG_NET_CLS_BPF", + CONFIG_NET_CLS_ACT: "CONFIG_NET_CLS_ACT", + CONFIG_NET_SCH_INGRESS: "CONFIG_NET_SCH_INGRESS", + CONFIG_XFRM: "CONFIG_XFRM", + CONFIG_IP_ROUTE_CLASSID: "CONFIG_IP_ROUTE_CLASSID", + CONFIG_IPV6_SEG6_BPF: "CONFIG_IPV6_SEG6_BPF", + CONFIG_BPF_LIRC_MODE2: "CONFIG_BPF_LIRC_MODE2", + CONFIG_BPF_STREAM_PARSER: "CONFIG_BPF_STREAM_PARSER", + CONFIG_NETFILTER_XT_MATCH_BPF: "CONFIG_NETFILTER_XT_MATCH_BPF", + CONFIG_BPFILTER: "CONFIG_BPFILTER", + CONFIG_BPFILTER_UMH: "CONFIG_BPFILTER_UMH", + CONFIG_TEST_BPF: "CONFIG_TEST_BPF", + CONFIG_HZ: "CONFIG_HZ", + CONFIG_DEBUG_INFO_BTF: "CONFIG_DEBUG_INFO_BTF", + CONFIG_DEBUG_INFO_BTF_MODULES: "CONFIG_DEBUG_INFO_BTF_MODULES", + CONFIG_BPF_LSM: "CONFIG_BPF_LSM", + CONFIG_BPF_PRELOAD: "CONFIG_BPF_PRELOAD", + CONFIG_BPF_PRELOAD_UMD: "CONFIG_BPF_PRELOAD_UMD", +} + +// KernelConfig is a set of kernel configuration options (currently for running OS only) +type KernelConfig struct { + configs map[KernelConfigOption]interface{} // predominantly KernelConfigOptionValue, sometimes string + needed map[KernelConfigOption]interface{} + kConfigFilePath string +} + +// InitKernelConfig inits external KernelConfig object +func InitKernelConfig() (*KernelConfig, error) { + config := KernelConfig{} + + // special case: user provided kconfig file (it MUST exist) + + osKConfigFilePath, err := checkEnvPath("LIBBPFGO_KCONFIG_FILE") // override /proc/config.gz or /boot/config-$(uname -r) if needed (containers) + if err != nil { + return &config, err + } + if len(osKConfigFilePath) > 2 { + if _, err := os.Stat(osKConfigFilePath); err != nil { + return &config, err + } + config.kConfigFilePath = osKConfigFilePath + if err := config.initKernelConfig(osKConfigFilePath); err != nil { + return &config, err + } + + return &config, nil + } + + // fastpath: check config.gz in procfs first + + configGZ := "/proc/config.gz" + if _, err1 := os.Stat(configGZ); err1 == nil { + config.kConfigFilePath = configGZ + if err2 := config.initKernelConfig(configGZ); err2 != nil { + return &config, err2 + } + + return &config, nil + } // ignore if /proc/config.gz does not exist + + // slowerpath: /boot/$(uname -r) + + releaseVersion, err := UnameRelease() + if err != nil { + return &config, err + } + + releaseFilePath := fmt.Sprintf("/boot/config-%s", releaseVersion) + config.kConfigFilePath = releaseFilePath + err = config.initKernelConfig(releaseFilePath) + + return &config, err +} + +// GetKernelConfigFilePath gives the kconfig file chosen by InitKernelConfig during initialization +func (k *KernelConfig) GetKernelConfigFilePath() string { + return k.kConfigFilePath +} + +// AddCustomKernelConfig allows user to extend list of possible existing kconfigs to be parsed from kConfigFilePath +func (k *KernelConfig) AddCustomKernelConfig(key KernelConfigOption, value string) error { + if key < CUSTOM_OPTION_START { + return fmt.Errorf("KConfig key index must be bigger than %d (CUSTOM_OPTION_START)\n", CUSTOM_OPTION_START) + } + + // extend initial list of kconfig options: add other possible existing ones + kernelConfigKeyIDToString[key] = value + kernelConfigKeyStringToID[value] = key + + return nil +} + +// LoadKernelConfig will (re)read kconfig file (likely after AddCustomKernelConfig was called) +func (k *KernelConfig) LoadKernelConfig() error { + return k.initKernelConfig(k.kConfigFilePath) +} + +// initKernelConfig inits internal KernelConfig data by calling appropriate readConfigFromXXX function +func (k *KernelConfig) initKernelConfig(configFilePath string) error { + if _, err := os.Stat(configFilePath); err != nil { + return fmt.Errorf("could not read %v: %w", configFilePath, err) + } + + if strings.Compare(filepath.Ext(configFilePath), ".gz") == 0 { + return k.readConfigFromProcConfigGZ(configFilePath) + } + + return k.readConfigFromBootConfigRelease(configFilePath) // assume it is a txt file by default +} + +// readConfigFromBootConfigRelease prepares io.Reader (/boot/config-$(uname -r)) for readConfigFromScanner +func (k *KernelConfig) readConfigFromBootConfigRelease(filePath string) error { + file, _ := os.Open(filePath) // already checked + k.readConfigFromScanner(file) + file.Close() + + return nil +} + +// readConfigFromProcConfigGZ prepares gziped io.Reader (/proc/config.gz) for readConfigFromScanner +func (k *KernelConfig) readConfigFromProcConfigGZ(filePath string) error { + file, _ := os.Open(filePath) // already checked + zreader, _ := gzip.NewReader(file) + k.readConfigFromScanner(zreader) + zreader.Close() + file.Close() + + return nil +} + +// readConfigFromScanner reads all existing KernelConfigOption's and KernelConfigOptionValue's from given io.Reader +func (k *KernelConfig) readConfigFromScanner(reader io.Reader) { + + if k.configs == nil { + k.configs = make(map[KernelConfigOption]interface{}) + } + if k.needed == nil { + k.needed = make(map[KernelConfigOption]interface{}) + } + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + kv := strings.Split(scanner.Text(), "=") + if len(kv) != 2 { + continue + } + + configKeyID := kernelConfigKeyStringToID[kv[0]] + if configKeyID == 0 { + continue + } + if strings.Compare(kv[1], "m") == 0 { + k.configs[configKeyID] = MODULE + } else if strings.Compare(kv[1], "y") == 0 { + k.configs[configKeyID] = BUILTIN + } else { + k.configs[configKeyID] = kv[1] + } + } +} + +// GetValue will return a KernelConfigOptionValue for a given KernelConfigOption when this is a BUILTIN or a MODULE +func (k *KernelConfig) GetValue(option KernelConfigOption) KernelConfigOptionValue { + value, ok := k.configs[KernelConfigOption(option)].(KernelConfigOptionValue) + if ok { + return value + } + + return UNDEFINED // not an error as the config option might not exist in kconfig file +} + +// GetValueString will return a KernelConfigOptionValue for a given KernelConfigOption when this is actually a string +func (k *KernelConfig) GetValueString(option KernelConfigOption) (string, error) { + value, ok := k.configs[option].(string) + if ok { + return value, nil + } + + return "", fmt.Errorf("given option's value (%s) is not a string", option) +} + +// Exists will return true if a given KernelConfigOption was found in provided KernelConfig +// and it will return false if the KernelConfigOption is not set (# XXXXX is not set) +// +// Examples: +// kernelConfig.Exists(helpers.CONFIG_BPF) +// kernelConfig.Exists(helpers.CONFIG_BPF_PRELOAD) +// kernelConfig.Exists(helpers.CONFIG_HZ) +// +func (k *KernelConfig) Exists(option KernelConfigOption) bool { + if _, ok := k.configs[option]; ok { + return true + } + + return false +} + +// ExistsValue will return true if a given KernelConfigOption was found in provided KernelConfig +// AND its value is the same as the one provided by KernelConfigOptionValue +func (k *KernelConfig) ExistsValue(option KernelConfigOption, value interface{}) bool { + if cfg, ok := k.configs[option]; ok { + switch cfg.(type) { + case KernelConfigOptionValue: + if value == ANY { + return true + } else if k.configs[option].(KernelConfigOptionValue) == value { + return true + } + case string: + if strings.Compare(k.configs[option].(string), value.(string)) == 0 { + return true + } + } + } + + return false +} + +// CheckMissing returns an array of KernelConfigOption's that were added to KernelConfig as needed but couldn't be +// found. It returns an empty array if nothing is missing. +func (k *KernelConfig) CheckMissing() []KernelConfigOption { + missing := make([]KernelConfigOption, 0) + + for key, value := range k.needed { + if !k.ExistsValue(key, value) { + missing = append(missing, key) + } + } + + return missing +} + +// AddNeeded adds a KernelConfigOption and its value, if needed, as required for further checks with CheckMissing +// +// Examples: +// kernelConfig.AddNeeded(helpers.CONFIG_BPF, helpers.ANY) +// kernelConfig.AddNeeded(helpers.CONFIG_BPF_PRELOAD, helpers.ANY) +// kernelConfig.AddNeeded(helpers.CONFIG_HZ, "250") +// +func (k *KernelConfig) AddNeeded(option KernelConfigOption, value interface{}) { + if _, ok := kernelConfigKeyIDToString[option]; ok { + k.needed[option] = value + } +} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go deleted file mode 100644 index f771160820..0000000000 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go +++ /dev/null @@ -1,228 +0,0 @@ -package helpers - -import ( - "bufio" - "bytes" - "compress/gzip" - "errors" - "fmt" - "io" - "os" - "strings" - - "golang.org/x/sys/unix" -) - -// These constants are a limited number of the total kernel config options, -// but are provided because they are most relevant for BPF -// development. -const ( - CONFIG_BPF uint32 = iota + 1 - CONFIG_BPF_SYSCALL - CONFIG_HAVE_EBPF_JIT - CONFIG_BPF_JIT - CONFIG_BPF_JIT_ALWAYS_ON - CONFIG_CGROUPS - CONFIG_CGROUP_BPF - CONFIG_CGROUP_NET_CLASSID - CONFIG_SOCK_CGROUP_DATA - CONFIG_BPF_EVENTS - CONFIG_KPROBE_EVENTS - CONFIG_UPROBE_EVENTS - CONFIG_TRACING - CONFIG_FTRACE_SYSCALLS - CONFIG_FUNCTION_ERROR_INJECTION - CONFIG_BPF_KPROBE_OVERRIDE - CONFIG_NET - CONFIG_XDP_SOCKETS - CONFIG_LWTUNNEL_BPF - CONFIG_NET_ACT_BPF - CONFIG_NET_CLS_BPF - CONFIG_NET_CLS_ACT - CONFIG_NET_SCH_INGRESS - CONFIG_XFRM - CONFIG_IP_ROUTE_CLASSID - CONFIG_IPV6_SEG6_BPF - CONFIG_BPF_LIRC_MODE2 - CONFIG_BPF_STREAM_PARSER - CONFIG_NETFILTER_XT_MATCH_BPF - CONFIG_BPFILTER - CONFIG_BPFILTER_UMH - CONFIG_TEST_BPF - CONFIG_HZ - CONFIG_DEBUG_INFO_BTF - CONFIG_DEBUG_INFO_BTF_MODULES - CONFIG_BPF_LSM - CONFIG_BPF_PRELOAD - CONFIG_BPF_PRELOAD_UMD -) - -var KernelConfigKeyStringToID map[string]uint32 = map[string]uint32{ - "CONFIG_BPF": CONFIG_BPF, - "CONFIG_BPF_SYSCALL": CONFIG_BPF_SYSCALL, - "CONFIG_HAVE_EBPF_JIT": CONFIG_HAVE_EBPF_JIT, - "CONFIG_BPF_JIT": CONFIG_BPF_JIT, - "CONFIG_BPF_JIT_ALWAYS_ON": CONFIG_BPF_JIT_ALWAYS_ON, - "CONFIG_CGROUPS": CONFIG_CGROUPS, - "CONFIG_CGROUP_BPF": CONFIG_CGROUP_BPF, - "CONFIG_CGROUP_NET_CLASSID": CONFIG_CGROUP_NET_CLASSID, - "CONFIG_SOCK_CGROUP_DATA": CONFIG_SOCK_CGROUP_DATA, - "CONFIG_BPF_EVENTS": CONFIG_BPF_EVENTS, - "CONFIG_KPROBE_EVENTS": CONFIG_KPROBE_EVENTS, - "CONFIG_UPROBE_EVENTS": CONFIG_UPROBE_EVENTS, - "CONFIG_TRACING": CONFIG_TRACING, - "CONFIG_FTRACE_SYSCALLS": CONFIG_FTRACE_SYSCALLS, - "CONFIG_FUNCTION_ERROR_INJECTION": CONFIG_FUNCTION_ERROR_INJECTION, - "CONFIG_BPF_KPROBE_OVERRIDE": CONFIG_BPF_KPROBE_OVERRIDE, - "CONFIG_NET": CONFIG_NET, - "CONFIG_XDP_SOCKETS": CONFIG_XDP_SOCKETS, - "CONFIG_LWTUNNEL_BPF": CONFIG_LWTUNNEL_BPF, - "CONFIG_NET_ACT_BPF": CONFIG_NET_ACT_BPF, - "CONFIG_NET_CLS_BPF": CONFIG_NET_CLS_BPF, - "CONFIG_NET_CLS_ACT": CONFIG_NET_CLS_ACT, - "CONFIG_NET_SCH_INGRESS": CONFIG_NET_SCH_INGRESS, - "CONFIG_XFRM": CONFIG_XFRM, - "CONFIG_IP_ROUTE_CLASSID": CONFIG_IP_ROUTE_CLASSID, - "CONFIG_IPV6_SEG6_BPF": CONFIG_IPV6_SEG6_BPF, - "CONFIG_BPF_LIRC_MODE2": CONFIG_BPF_LIRC_MODE2, - "CONFIG_BPF_STREAM_PARSER": CONFIG_BPF_STREAM_PARSER, - "CONFIG_NETFILTER_XT_MATCH_BPF": CONFIG_NETFILTER_XT_MATCH_BPF, - "CONFIG_BPFILTER": CONFIG_BPFILTER, - "CONFIG_BPFILTER_UMH": CONFIG_BPFILTER_UMH, - "CONFIG_TEST_BPF": CONFIG_TEST_BPF, - "CONFIG_HZ": CONFIG_HZ, - "CONFIG_DEBUG_INFO_BTF": CONFIG_DEBUG_INFO_BTF, - "CONFIG_DEBUG_INFO_BTF_MODULES": CONFIG_DEBUG_INFO_BTF_MODULES, - "CONFIG_BPF_LSM": CONFIG_BPF_LSM, - "CONFIG_BPF_PRELOAD": CONFIG_BPF_PRELOAD, - "CONFIG_BPF_PRELOAD_UMD": CONFIG_BPF_PRELOAD_UMD, -} - -var KernelConfigKeyIDToString map[uint32]string = map[uint32]string{ - CONFIG_BPF: "CONFIG_BPF", - CONFIG_BPF_SYSCALL: "CONFIG_BPF_SYSCALL", - CONFIG_HAVE_EBPF_JIT: "CONFIG_HAVE_EBPF_JIT", - CONFIG_BPF_JIT: "CONFIG_BPF_JIT", - CONFIG_BPF_JIT_ALWAYS_ON: "CONFIG_BPF_JIT_ALWAYS_ON", - CONFIG_CGROUPS: "CONFIG_CGROUPS", - CONFIG_CGROUP_BPF: "CONFIG_CGROUP_BPF", - CONFIG_CGROUP_NET_CLASSID: "CONFIG_CGROUP_NET_CLASSID", - CONFIG_SOCK_CGROUP_DATA: "CONFIG_SOCK_CGROUP_DATA", - CONFIG_BPF_EVENTS: "CONFIG_BPF_EVENTS", - CONFIG_KPROBE_EVENTS: "CONFIG_KPROBE_EVENTS", - CONFIG_UPROBE_EVENTS: "CONFIG_UPROBE_EVENTS", - CONFIG_TRACING: "CONFIG_TRACING", - CONFIG_FTRACE_SYSCALLS: "CONFIG_FTRACE_SYSCALLS", - CONFIG_FUNCTION_ERROR_INJECTION: "CONFIG_FUNCTION_ERROR_INJECTION", - CONFIG_BPF_KPROBE_OVERRIDE: "CONFIG_BPF_KPROBE_OVERRIDE", - CONFIG_NET: "CONFIG_NET", - CONFIG_XDP_SOCKETS: "CONFIG_XDP_SOCKETS", - CONFIG_LWTUNNEL_BPF: "CONFIG_LWTUNNEL_BPF", - CONFIG_NET_ACT_BPF: "CONFIG_NET_ACT_BPF", - CONFIG_NET_CLS_BPF: "CONFIG_NET_CLS_BPF", - CONFIG_NET_CLS_ACT: "CONFIG_NET_CLS_ACT", - CONFIG_NET_SCH_INGRESS: "CONFIG_NET_SCH_INGRESS", - CONFIG_XFRM: "CONFIG_XFRM", - CONFIG_IP_ROUTE_CLASSID: "CONFIG_IP_ROUTE_CLASSID", - CONFIG_IPV6_SEG6_BPF: "CONFIG_IPV6_SEG6_BPF", - CONFIG_BPF_LIRC_MODE2: "CONFIG_BPF_LIRC_MODE2", - CONFIG_BPF_STREAM_PARSER: "CONFIG_BPF_STREAM_PARSER", - CONFIG_NETFILTER_XT_MATCH_BPF: "CONFIG_NETFILTER_XT_MATCH_BPF", - CONFIG_BPFILTER: "CONFIG_BPFILTER", - CONFIG_BPFILTER_UMH: "CONFIG_BPFILTER_UMH", - CONFIG_TEST_BPF: "CONFIG_TEST_BPF", - CONFIG_HZ: "CONFIG_HZ", - CONFIG_DEBUG_INFO_BTF: "CONFIG_DEBUG_INFO_BTF", - CONFIG_DEBUG_INFO_BTF_MODULES: "CONFIG_DEBUG_INFO_BTF_MODULES", - CONFIG_BPF_LSM: "CONFIG_BPF_LSM", - CONFIG_BPF_PRELOAD: "CONFIG_BPF_PRELOAD", - CONFIG_BPF_PRELOAD_UMD: "CONFIG_BPF_PRELOAD_UMD", -} - -type KernelConfig map[uint32]string - -// InitKernelConfig populates the passed KernelConfig -// by attempting to read the kernel config into it from: -// /proc/config-$(uname -r) -// or -// /boot/config.gz -func (k KernelConfig) InitKernelConfig() error { - - x := unix.Utsname{} - err := unix.Uname(&x) - if err != nil { - return fmt.Errorf("could not determine uname release: %v", err) - } - - bootConfigPath := fmt.Sprintf("/boot/config-%s", bytes.Trim(x.Release[:], "\x00")) - - err = k.getBootConfigByPath(bootConfigPath) - if err == nil { - return nil - } - - err2 := k.getProcGZConfigByPath("/proc/config.gz") - if err != nil { - return fmt.Errorf("%v %v", err, err2) - } - - return nil -} - -// GetKernelConfigValue retrieves a value from the kernel config -// If the config value does not exist an error will be returned -func (k KernelConfig) GetKernelConfigValue(key uint32) (string, error) { - v, exists := k[key] - if !exists { - return "", errors.New("kernel config value does not exist, it's possible this option is not present in your kernel version or the KernelConfig has not been initialized") - } - return v, nil -} - -func (k KernelConfig) getBootConfigByPath(bootConfigPath string) error { - - configFile, err := os.Open(bootConfigPath) - if err != nil { - return fmt.Errorf("could not open %s: %v", bootConfigPath, err) - } - - k.readConfigFromScanner(configFile) - - return nil -} - -func (k KernelConfig) getProcGZConfigByPath(procConfigPath string) error { - - configFile, err := os.Open(procConfigPath) - if err != nil { - return fmt.Errorf("could not open %s: %v", procConfigPath, err) - } - - return k.getProcGZConfig(configFile) -} - -func (k KernelConfig) getProcGZConfig(reader io.Reader) error { - zreader, err := gzip.NewReader(reader) - if err != nil { - return err - } - - k.readConfigFromScanner(zreader) - return nil -} - -func (k KernelConfig) readConfigFromScanner(reader io.Reader) { - scanner := bufio.NewScanner(reader) - - for scanner.Scan() { - kv := strings.Split(scanner.Text(), "=") - if len(kv) != 2 { - continue - } - configKeyID := KernelConfigKeyStringToID[kv[0]] - if configKeyID == 0 { - continue - } - k[configKeyID] = kv[1] - } -} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go new file mode 100644 index 0000000000..51ddeff0d7 --- /dev/null +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go @@ -0,0 +1,221 @@ +package helpers + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +type OSReleaseID uint32 + +func (o OSReleaseID) String() string { + return osReleaseIDToString[o] +} + +const ( + UBUNTU OSReleaseID = iota + 1 + FEDORA + ARCH + DEBIAN + CENTOS + STREAM + ALMA +) + +// stringToOSReleaseID is a map of supported distributions +var stringToOSReleaseID = map[string]OSReleaseID{ + "ubuntu": UBUNTU, + "fedora": FEDORA, + "arch": ARCH, + "debian": DEBIAN, + "centos": CENTOS, + "stream": STREAM, + "alma": ALMA, +} + +// osReleaseIDToString is a map of supported distributions +var osReleaseIDToString = map[OSReleaseID]string{ + UBUNTU: "ubuntu", + FEDORA: "fedora", + ARCH: "arch", + DEBIAN: "debian", + CENTOS: "centos", + STREAM: "stream", + ALMA: "alma", +} + +const ( + OS_NAME OSReleaseField = iota + 0 + OS_ID + OS_ID_LIKE + OS_PRETTY_NAME + OS_VARIANT + OS_VARIANT_ID + OS_VERSION + OS_VERSION_ID + OS_VERSION_CODENAME + OS_BUILD_ID + OS_IMAGE_ID + OS_IMAGE_VERSION + OS_KERNEL_RELEASE // not part of default os-release, but we can use it here to facilitate things +) + +type OSReleaseField uint32 + +func (o OSReleaseField) String() string { + return osReleaseFieldToString[o] +} + +// stringToOSReleaseField is a map of os-release file fields +var stringToOSReleaseField = map[string]OSReleaseField{ + "NAME": OS_NAME, + "ID": OS_ID, + "ID_LIKE": OS_ID_LIKE, + "PRETTY_NAME": OS_PRETTY_NAME, + "VARIANT": OS_VARIANT, + "VARIANT_ID": OS_VARIANT_ID, + "VERSION": OS_VERSION, + "VERSION_ID": OS_VERSION_ID, + "VERSION_CODENAME": OS_VERSION_CODENAME, + "BUILD_ID": OS_BUILD_ID, + "IMAGE_ID": OS_IMAGE_ID, + "IMAGE_VERSION": OS_IMAGE_VERSION, + "KERNEL_RELEASE": OS_KERNEL_RELEASE, +} + +// osReleaseFieldToString is a map of os-release file fields +var osReleaseFieldToString = map[OSReleaseField]string{ + OS_NAME: "NAME", + OS_ID: "ID", + OS_ID_LIKE: "ID_LIKE", + OS_PRETTY_NAME: "PRETTY_NAME", + OS_VARIANT: "VARIANT", + OS_VARIANT_ID: "VARIANT_ID", + OS_VERSION: "VERSION", + OS_VERSION_ID: "VERSION_ID", + OS_VERSION_CODENAME: "VERSION_CODENAME", + OS_BUILD_ID: "BUILD_ID", + OS_IMAGE_ID: "IMAGE_ID", + OS_IMAGE_VERSION: "IMAGE_VERSION", + OS_KERNEL_RELEASE: "KERNEL_RELEASE", +} + +// OSBTFEnabled checks if kernel has embedded BTF vmlinux file +func OSBTFEnabled() bool { + _, err := os.Stat("/sys/kernel/btf/vmlinux") // TODO: accept a KernelConfig param and check for CONFIG_DEBUG_INFO_BTF=y, or similar + + return err == nil +} + +// GetOSInfo creates a OSInfo object and runs discoverOSDistro() on its creation +func GetOSInfo() (*OSInfo, error) { + info := OSInfo{} + var err error + + if info.osReleaseFieldValues == nil { + info.osReleaseFieldValues = make(map[OSReleaseField]string) + } + + info.osReleaseFieldValues[OS_KERNEL_RELEASE], err = UnameRelease() + if err != nil { + return &info, fmt.Errorf("could not determine uname release: %w", err) + } + + info.osReleaseFilePath, err = checkEnvPath("LIBBPFGO_OSRELEASE_FILE") // useful if users wants to mount host os-release in a container + if err != nil { + return &info, err + } else if info.osReleaseFilePath == "" { + info.osReleaseFilePath = "/etc/os-release" + } + + if err = info.discoverOSDistro(); err != nil { + return &info, err + } + + return &info, nil +} + +// OSInfo object contains all OS relevant information +// +// OSRelease is relevant to examples such as: +// 1) OSInfo.OSReleaseInfo[helpers.OS_KERNEL_RELEASE]) => will provide $(uname -r) string +// 2) if OSInfo.GetReleaseID() == helpers.UBUNTU => {} will allow to run code in specific distribution +// +type OSInfo struct { + osReleaseFieldValues map[OSReleaseField]string + osReleaseID OSReleaseID + osReleaseFilePath string +} + +// GetOSReleaseFieldValue provides access to internal OSInfo OSReleaseField's +func (btfi *OSInfo) GetOSReleaseFieldValue(value OSReleaseField) string { + return btfi.osReleaseFieldValues[value] +} + +// GetOSReleaseFilePath provides the path for the used os-release file as it might +// not necessarily be /etc/os-release, depending on the environment variable +func (btfi *OSInfo) GetOSReleaseFilePath() string { + return btfi.osReleaseFilePath +} + +// GetOSReleaseFilePath provides the ID of current Linux distribution +func (btfi *OSInfo) GetOSReleaseID() OSReleaseID { + return btfi.osReleaseID +} + +// GetOSReleaseAllFieldValues allows user to dump, as strings, the existing OSReleaseField's and its values +func (btfi *OSInfo) GetOSReleaseAllFieldValues() map[OSReleaseField]string { + summary := make(map[OSReleaseField]string) + + for k, v := range btfi.osReleaseFieldValues { + summary[k] = v // create a copy so consumer can read internal data (e.g. debugging) + } + + return summary +} + +// CompareOSBaseKernelRelease will compare a given kernel version/release string +// to the current running version and return -1, 0 or 1 if given version is less, +// equal or bigger, respectively, than running one. Example: +// +// OSInfo.CompareOSBaseKernelRelease("5.11.0")) +// +func (btfi *OSInfo) CompareOSBaseKernelRelease(version string) int { + return CompareKernelRelease(btfi.osReleaseFieldValues[OS_KERNEL_RELEASE], version) +} + +// discoverOSDistro discover running Linux distribution information by reading UTS and +// the /etc/os-releases file (https://man7.org/linux/man-pages/man5/os-release.5.html) +func (btfi *OSInfo) discoverOSDistro() error { + var err error + + if btfi.osReleaseFilePath == "" { + return fmt.Errorf("should specify os-release filepath") + } + + file, err := os.Open(btfi.osReleaseFilePath) + if err != nil { + return err + } + + defer file.Close() + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + val := strings.Split(scanner.Text(), "=") + if len(val) != 2 { + continue + } + keyID := stringToOSReleaseField[val[0]] + if keyID == 0 { // could not find KEY= from os-release in consts + continue + } + btfi.osReleaseFieldValues[keyID] = val[1] + if keyID == OS_ID { + btfi.osReleaseID = stringToOSReleaseID[strings.ToLower(val[1])] + } + } + + return nil +} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go index 3b2beab36d..c9b9f3ef45 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go @@ -39,6 +39,7 @@ func (a *RWArray) Put(v interface{}) int { if !a.slots[i].used { a.slots[i].value = v a.slots[i].used = true + return i } } diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go index ebdab4ceb8..a74874a5d6 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go @@ -15,19 +15,20 @@ import ( func TracePipeListen() error { f, err := os.Open("/sys/kernel/debug/tracing/trace_pipe") if err != nil { - return fmt.Errorf("failed to open trace pipe: %v", err) + return fmt.Errorf("failed to open trace pipe: %w", err) } defer f.Close() r := bufio.NewReader(f) b := make([]byte, 1024) + for { - len, err := r.Read(b) + l, err := r.Read(b) if err != nil { - return fmt.Errorf("failed to read from trace pipe: %v", err) + return fmt.Errorf("failed to read from trace pipe: %w", err) } - s := string(b[:len]) + s := string(b[:l]) fmt.Println(s) } } diff --git a/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go b/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go index d37d32a056..cb525c85ed 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go +++ b/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go @@ -70,7 +70,7 @@ struct perf_buffer * init_perf_buf(int map_fd, int page_cnt, uintptr_t ctx) { pb_opts.lost_cb = perfLostCallback; pb_opts.ctx = (void*)ctx; pb = perf_buffer__new(map_fd, page_cnt, &pb_opts); - if (pb < 0) { + if (libbpf_get_error(pb)) { fprintf(stderr, "Failed to initialize perf buffer!\n"); return NULL; } @@ -197,7 +197,6 @@ import ( "fmt" "net" "path/filepath" - "strings" "sync" "syscall" "unsafe" @@ -252,6 +251,14 @@ type BPFLink struct { eventName string } +func (l *BPFLink) Destroy() error { + ret := C.bpf_link__destroy(l.link) + if ret < 0 { + return syscall.Errno(-ret) + } + return nil +} + type PerfBuffer struct { pb *C.struct_perf_buffer bpfMap *BPFMap @@ -296,14 +303,49 @@ func errptrError(ptr unsafe.Pointer, format string, args ...interface{}) error { return fmt.Errorf(format+": %v", args...) } -func NewModuleFromFile(bpfObjFile string) (*Module, error) { +type NewModuleArgs struct { + KConfigFilePath string + BTFObjPath string + BPFObjName string + BPFObjPath string + BPFObjBuff []byte +} + +func NewModuleFromFile(bpfObjPath string) (*Module, error) { + + return NewModuleFromFileArgs(NewModuleArgs{ + BPFObjPath: bpfObjPath, + }) +} + +func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { C.set_print_fn() - bumpMemlockRlimit() - cs := C.CString(bpfObjFile) - obj := C.bpf_object__open(cs) - C.free(unsafe.Pointer(cs)) + if err := bumpMemlockRlimit(); err != nil { + return nil, err + } + opts := C.struct_bpf_object_open_opts{} + opts.sz = C.sizeof_struct_bpf_object_open_opts + + bpfFile := C.CString(args.BPFObjPath) + defer C.free(unsafe.Pointer(bpfFile)) + + // instruct libbpf to use user provided kernel BTF file + if args.BTFObjPath != "" { + btfFile := C.CString(args.BTFObjPath) + opts.btf_custom_path = btfFile + defer C.free(unsafe.Pointer(btfFile)) + } + + // instruct libbpf to use user provided KConfigFile + if args.KConfigFilePath != "" { + kConfigFile := C.CString(args.KConfigFilePath) + opts.kconfig = kConfigFile + defer C.free(unsafe.Pointer(kConfigFile)) + } + + obj := C.bpf_object__open_file(bpfFile, &opts) if C.IS_ERR_OR_NULL(unsafe.Pointer(obj)) { - return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s", bpfObjFile) + return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s", args.BPFObjPath) } return &Module{ @@ -312,18 +354,46 @@ func NewModuleFromFile(bpfObjFile string) (*Module, error) { } func NewModuleFromBuffer(bpfObjBuff []byte, bpfObjName string) (*Module, error) { + + return NewModuleFromBufferArgs(NewModuleArgs{ + BPFObjBuff: bpfObjBuff, + BPFObjName: bpfObjName, + }) +} + +func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) { C.set_print_fn() - bumpMemlockRlimit() - name := C.CString(bpfObjName) - buffSize := C.size_t(len(bpfObjBuff)) - buffPtr := unsafe.Pointer(C.CBytes(bpfObjBuff)) - obj := C.bpf_object__open_buffer(buffPtr, buffSize, name) - C.free(unsafe.Pointer(name)) - C.free(unsafe.Pointer(buffPtr)) + if err := bumpMemlockRlimit(); err != nil { + return nil, err + } + if args.BTFObjPath == "" { + args.BTFObjPath = "/sys/kernel/btf/vmlinux" + } + btfFile := C.CString(args.BTFObjPath) + bpfName := C.CString(args.BPFObjName) + bpfBuff := unsafe.Pointer(C.CBytes(args.BPFObjBuff)) + bpfBuffSize := C.size_t(len(args.BPFObjBuff)) + + opts := C.struct_bpf_object_open_opts{} + opts.object_name = bpfName + opts.sz = C.sizeof_struct_bpf_object_open_opts + opts.btf_custom_path = btfFile // instruct libbpf to use user provided kernel BTF file + + if len(args.KConfigFilePath) > 2 { + kConfigFile := C.CString(args.KConfigFilePath) + opts.kconfig = kConfigFile // instruct libbpf to use user provided KConfigFile + defer C.free(unsafe.Pointer(kConfigFile)) + } + + obj := C.bpf_object__open_mem(bpfBuff, bpfBuffSize, &opts) if C.IS_ERR_OR_NULL(unsafe.Pointer(obj)) { - return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s: %v", bpfObjName, bpfObjBuff[:20]) + return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s: %v", args.BPFObjName, args.BPFObjBuff[:20]) } + C.free(bpfBuff) + C.free(unsafe.Pointer(bpfName)) + C.free(unsafe.Pointer(btfFile)) + return &Module{ obj: obj, }, nil @@ -378,11 +448,9 @@ func (m *Module) GetMap(mapName string) (*BPFMap, error) { } func (b *BPFMap) Pin(pinPath string) error { - cs := C.CString(b.name) path := C.CString(pinPath) - bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) - errC := C.bpf_map__pin(bpfMap, path) - C.free(unsafe.Pointer(cs)) + errC := C.bpf_map__pin(b.bpfMap, path) + C.free(unsafe.Pointer(path)) if errC != 0 { return fmt.Errorf("failed to pin map %s to path %s", b.name, pinPath) } @@ -390,11 +458,9 @@ func (b *BPFMap) Pin(pinPath string) error { } func (b *BPFMap) Unpin(pinPath string) error { - cs := C.CString(b.name) path := C.CString(pinPath) - bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) - errC := C.bpf_map__unpin(bpfMap, path) - C.free(unsafe.Pointer(cs)) + errC := C.bpf_map__unpin(b.bpfMap, path) + C.free(unsafe.Pointer(path)) if errC != 0 { return fmt.Errorf("failed to unpin map %s from path %s", b.name, pinPath) } @@ -402,11 +468,9 @@ func (b *BPFMap) Unpin(pinPath string) error { } func (b *BPFMap) SetPinPath(pinPath string) error { - cs := C.CString(b.name) path := C.CString(pinPath) - bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) - errC := C.bpf_map__set_pin_path(bpfMap, path) - C.free(unsafe.Pointer(cs)) + errC := C.bpf_map__set_pin_path(b.bpfMap, path) + C.free(unsafe.Pointer(path)) if errC != 0 { return fmt.Errorf("failed to set pin for map %s to path %s", b.name, pinPath) } @@ -419,10 +483,7 @@ func (b *BPFMap) SetPinPath(pinPath string) error { // Note: for ring buffer and perf buffer, maxEntries is the // capacity in bytes. func (b *BPFMap) Resize(maxEntries uint32) error { - cs := C.CString(b.name) - bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) - errC := C.bpf_map__resize(bpfMap, C.uint(maxEntries)) - C.free(unsafe.Pointer(cs)) + errC := C.bpf_map__set_max_entries(b.bpfMap, C.uint(maxEntries)) if errC != 0 { return fmt.Errorf("failed to resize map %s to %v", b.name, maxEntries) } @@ -433,13 +494,35 @@ func (b *BPFMap) Resize(maxEntries uint32) error { // Note: for ring buffer and perf buffer, maxEntries is the // capacity in bytes. func (b *BPFMap) GetMaxEntries() uint32 { - cs := C.CString(b.name) - bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) - maxEntries := C.bpf_map__max_entries(bpfMap) - C.free(unsafe.Pointer(cs)) + maxEntries := C.bpf_map__max_entries(b.bpfMap) return uint32(maxEntries) } +func (b *BPFMap) GetFd() int { + return int(b.fd) +} + +func (b *BPFMap) GetName() string { + return b.name +} + +func (b *BPFMap) GetModule() *Module { + return b.module +} + +func (b *BPFMap) GetPinPath() string { + pinPathGo := C.GoString(C.bpf_map__get_pin_path(b.bpfMap)) + return pinPathGo +} + +func (b *BPFMap) IsPinned() bool { + isPinned := C.bpf_map__is_pinned(b.bpfMap) + if isPinned == C.bool(true) { + return true + } + return false +} + func GetUnsafePointer(data interface{}) (unsafe.Pointer, error) { var dataPtr unsafe.Pointer switch k := data.(type) { @@ -599,8 +682,16 @@ func (m *Module) GetProgram(progName string) (*BPFProg, error) { }, nil } -func (p *BPFProg) GetFd() C.int { - return C.bpf_program__fd(p.prog) +func (p *BPFProg) GetFd() int { + return int(C.bpf_program__fd(p.prog)) +} + +func (p *BPFProg) GetModule() *Module { + return p.module +} + +func (p *BPFProg) GetName() string { + return p.name } // BPFProgType is an enum as defined in https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/bpf.h @@ -661,25 +752,21 @@ func (p *BPFProg) SetTracepoint() error { return nil } -func (p *BPFProg) AttachTracepoint(tp string) (*BPFLink, error) { - tpEvent := strings.Split(tp, ":") - if len(tpEvent) != 2 { - return nil, fmt.Errorf("tracepoint must be in 'category:name' format") - } - tpCategory := C.CString(tpEvent[0]) - tpName := C.CString(tpEvent[1]) +func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { + tpCategory := C.CString(category) + tpName := C.CString(name) link := C.bpf_program__attach_tracepoint(p.prog, tpCategory, tpName) C.free(unsafe.Pointer(tpCategory)) C.free(unsafe.Pointer(tpName)) if C.IS_ERR_OR_NULL(unsafe.Pointer(link)) { - return nil, errptrError(unsafe.Pointer(link), "failed to attach tracepoint %s to program %s", tp, p.name) + return nil, errptrError(unsafe.Pointer(link), "failed to attach tracepoint %s to program %s", name, p.name) } bpfLink := &BPFLink{ link: link, prog: p, linkType: Tracepoint, - eventName: tp, + eventName: name, } p.module.links = append(p.module.links, bpfLink) return bpfLink, nil @@ -801,7 +888,7 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse link := C.bpf_program__attach_uprobe(prog.prog, retCBool, pidCint, pathCString, offsetCsizet) C.free(unsafe.Pointer(pathCString)) if C.IS_ERR_OR_NULL(unsafe.Pointer(link)) { - return nil, errptrError(unsafe.Pointer(link), "failed to attach u(ret)probe to program %s:%d with pid %s, ", path, offset, pid) + return nil, errptrError(unsafe.Pointer(link), "failed to attach u(ret)probe to program %s:%d with pid %d, ", path, offset, pid) } upType := Uprobe @@ -813,7 +900,7 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse link: link, prog: prog, linkType: upType, - eventName: fmt.Sprintf("%s:%d:%s", path, pid, offset), + eventName: fmt.Sprintf("%s:%d:%d", path, pid, offset), } return bpfLink, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 28047986cf..997ad0ae97 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac +# github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c ## explicit github.com/aquasecurity/libbpfgo github.com/aquasecurity/libbpfgo/helpers From c215a214ca216bc8e5d68cb00d76d8338970b20d Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 6 Oct 2021 11:41:18 -0700 Subject: [PATCH 02/17] pkg/proc: parse return args in ebpf tracer --- _fixtures/databpstack2.go | 23 +++++ cmd/dlv/cmds/commands.go | 12 ++- cmd/dlv/dlv_test.go | 40 ++++++++ pkg/proc/breakpoints.go | 47 ++++++++-- pkg/proc/core/core.go | 8 ++ pkg/proc/gdbserial/gdbserver.go | 8 ++ pkg/proc/interface.go | 2 + pkg/proc/internal/ebpf/context.go | 8 +- pkg/proc/internal/ebpf/helpers.go | 93 +++++++++++++++++-- pkg/proc/internal/ebpf/helpers_disabled.go | 30 +++++- .../ebpf/trace_probe/function_vals.bpf.h | 8 +- .../internal/ebpf/trace_probe/trace.bpf.c | 89 ++++++++++++++---- .../internal/ebpf/trace_probe/trace.bpf.h | 1 + pkg/proc/native/proc_linux.go | 38 +++++++- pkg/proc/stackwatch.go | 24 +++-- pkg/proc/target.go | 41 +++++--- service/debugger/debugger.go | 3 + 17 files changed, 405 insertions(+), 70 deletions(-) create mode 100644 _fixtures/databpstack2.go diff --git a/_fixtures/databpstack2.go b/_fixtures/databpstack2.go new file mode 100644 index 0000000000..dab14466de --- /dev/null +++ b/_fixtures/databpstack2.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" +) + +func f(n int) { + w := 0 + g(n, &w) // Position 0 +} + +func g(cnt int, p *int) int { + if cnt == 0 { + *p = 10 + return *p // Position 1 + } + return g(cnt-1, p) +} + +func main() { + f(1000) + fmt.Printf("done\n") // Position 2 +} diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 33ce65cc97..15df5f63ca 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -673,6 +673,7 @@ func traceCmd(cmd *cobra.Command, args []string) { done := make(chan struct{}) defer close(done) go func() { + gFnEntrySeen := map[int]struct{}{} for { select { case <-done: @@ -694,7 +695,16 @@ func traceCmd(cmd *cobra.Command, args []string) { params.WriteString(p.Value) } } - fmt.Fprintf(os.Stderr, "> (%d) %s(%s)\n", t.GoroutineID, t.FunctionName, params.String()) + _, seen := gFnEntrySeen[t.GoroutineID] + if seen { + for _, p := range t.ReturnParams { + fmt.Fprintf(os.Stderr, "=> %#v\n", p.Value) + } + delete(gFnEntrySeen, t.GoroutineID) + } else { + gFnEntrySeen[t.GoroutineID] = struct{}{} + fmt.Fprintf(os.Stderr, "> (%d) %s(%s)\n", t.GoroutineID, t.FunctionName, params.String()) + } } } } diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 7743f522da..0a9f759da6 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -890,6 +890,46 @@ func TestTraceEBPF(t *testing.T) { cmd.Wait() } +func TestTraceEBPFRet(t *testing.T) { + if os.Getenv("CI") == "true" { + t.Skip("cannot run test in CI, requires kernel compiled with btf support") + } + if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { + t.Skip("not implemented on non linux/amd64 systems") + } + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) { + t.Skip("requires at least Go 1.16 to run test") + } + usr, err := user.Current() + if err != nil { + t.Fatal(err) + } + if usr.Uid != "0" { + t.Skip("test must be run as root") + } + + dlvbin, tmpdir := getDlvBinEBPF(t) + defer os.RemoveAll(tmpdir) + + expected := "> (1) main.g(1000, 0xdeadbeef) => 10\n" + + fixtures := protest.FindFixturesDir() + cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "databpstack2.go"), "main.g") + rdr, err := cmd.StderrPipe() + assertNoError(err, t, "stderr pipe") + defer rdr.Close() + + assertNoError(cmd.Start(), t, "running trace") + + output, err := bufio.NewReader(rdr).ReadString('\n') + assertNoError(err, t, "ReadAll") + + if !strings.Contains(output, expected) { + t.Fatalf("expected:\n%s\ngot:\n%s", expected, output) + } + cmd.Wait() +} + func TestDlvTestChdir(t *testing.T) { dlvbin, tmpdir := getDlvBin(t) defer os.RemoveAll(tmpdir) diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index b8434c4fab..bed000cafa 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -494,7 +494,6 @@ func (t *Target) SetEBPFTracepoint(fnName string) error { } // Start putting together the argument map. This will tell the eBPF program // all of the arguments we want to trace and how to find them. - var args []ebpf.UProbeArgMap fn, ok := t.BinInfo().LookupFunc[fnName] if !ok { return fmt.Errorf("could not find function %s", fnName) @@ -533,17 +532,22 @@ func (t *Target) SetEBPFTracepoint(fnName string) error { } _, l, _ := t.BinInfo().PCToLine(fn.Entry) + var args []ebpf.UProbeArgMap varEntries := reader.Variables(dwarfTree, fn.Entry, l, variablesFlags) for _, entry := range varEntries { - isret, _ := entry.Val(dwarf.AttrVarParam).(bool) - if isret { - continue - } _, dt, err := readVarEntry(entry.Tree, fn.cu.image) if err != nil { return err } - offset, pieces, _, err := t.BinInfo().Location(entry, dwarf.AttrLocation, fn.Entry, op.DwarfRegisters{}, nil) + + var addr uint64 + isret, _ := entry.Val(dwarf.AttrVarParam).(bool) + if isret { + addr = fn.End + } else { + addr = fn.Entry + } + origOffset, pieces, _, err := t.BinInfo().Location(entry, dwarf.AttrLocation, addr, op.DwarfRegisters{}, nil) if err != nil { return err } @@ -553,12 +557,39 @@ func (t *Target) SetEBPFTracepoint(fnName string) error { paramPieces = append(paramPieces, int(piece.Val)) } } - offset += int64(t.BinInfo().Arch.PtrSize()) - args = append(args, ebpf.UProbeArgMap{Offset: offset, Size: dt.Size(), Kind: dt.Common().ReflectKind, Pieces: paramPieces, InReg: len(pieces) > 0}) + offset := origOffset + int64(t.BinInfo().Arch.PtrSize()) + if isret { + offset = origOffset + } + args = append(args, ebpf.UProbeArgMap{ + Offset: offset, + Size: dt.Size(), + Kind: dt.Common().ReflectKind, + Pieces: paramPieces, + InReg: len(pieces) > 0, + Ret: isret, + }) } // Finally, set the uprobe on the function. t.proc.SetUProbe(fnName, goidOffset, args) + + // Next we want to set the uretprobe on the function. + // We have to be careful here, just as with stack watchpoints, we need to + // ensure that we watch the runtime to ensure that whenever it decides to grow and copy + // a stack frame, we will be notified. + err = t.setStackResizeSentinel(nil, true, func(th Thread) bool { + if th.Breakpoint().Name == "copystack-entry" { + t.proc.DisableURetProbes() + } else { + t.proc.EnableURetProbes() + } + return false // we don't want this showing up for users. + }) + if err != nil { + return err + } + return nil } diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index e28e04e4a2..392d9f9381 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -281,6 +281,14 @@ func (dbp *process) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProb // StartCallInjection notifies the backend that we are about to inject a function call. func (p *process) StartCallInjection() (func(), error) { return func() {}, nil } +func (dbp *process) EnableURetProbes() error { + panic("not implemented") +} + +func (dbp *process) DisableURetProbes() error { + panic("not implemented") +} + // ReadMemory will return memory from the core file at the specified location and put the // read memory into `data`, returning the length read, and returning an error if // the length read is shorter than the length of the `data` buffer. diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index b2173fcf28..04f3788d25 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -381,6 +381,14 @@ func (dbp *gdbProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UP panic("not implemented") } +func (dbp *gdbProcess) EnableURetProbes() error { + panic("not implemented") +} + +func (dbp *gdbProcess) DisableURetProbes() error { + panic("not implemented") +} + // unusedPort returns an unused tcp port // This is a hack and subject to a race condition with other running // programs, but most (all?) OS will cycle through all ephemeral ports diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index e5797e264e..55782dbda3 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -47,6 +47,8 @@ type ProcessInternal interface { SupportsBPF() bool SetUProbe(string, int64, []ebpf.UProbeArgMap) error + EnableURetProbes() error + DisableURetProbes() error // DumpProcessNotes returns ELF core notes describing the process and its threads. // Implementing this method is optional. diff --git a/pkg/proc/internal/ebpf/context.go b/pkg/proc/internal/ebpf/context.go index c457dbc283..695b8105c6 100644 --- a/pkg/proc/internal/ebpf/context.go +++ b/pkg/proc/internal/ebpf/context.go @@ -13,6 +13,7 @@ type UProbeArgMap struct { Kind reflect.Kind // Kind of variable. Pieces []int // Pieces of the variables as stored in registers. InReg bool // True if this param is contained in a register. + Ret bool // True if this param is a return value. } type RawUProbeParam struct { @@ -26,7 +27,8 @@ type RawUProbeParam struct { } type RawUProbeParams struct { - FnAddr int - GoroutineID int - InputParams []*RawUProbeParam + FnAddr int + GoroutineID int + InputParams []*RawUProbeParam + ReturnParams []*RawUProbeParam } diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go index 2936f2ca75..6754471fca 100644 --- a/pkg/proc/internal/ebpf/helpers.go +++ b/pkg/proc/internal/ebpf/helpers.go @@ -26,6 +26,17 @@ var TraceProbeBytes []byte const FakeAddressBase = 0xbeed000000000000 +type Uretprobe struct { + Link *bpf.BPFLink + Pid int + Path string + Offset uint32 +} + +func (u *Uretprobe) Destroy() error { + return u.Link.Destroy() +} + type EBPFContext struct { bpfModule *bpf.Module bpfProg *bpf.BPFProg @@ -33,6 +44,11 @@ type EBPFContext struct { bpfRingBuf *bpf.RingBuffer bpfArgMap *bpf.BPFMap + uretprobes []Uretprobe + clearedURetProbes []Uretprobe + clearURetProbesOnce sync.Once + u sync.Mutex + parsedBpfEvents []RawUProbeParams m sync.Mutex } @@ -43,6 +59,31 @@ func (ctx *EBPFContext) Close() { } } +func (ctx *EBPFContext) GetURetProbes() []Uretprobe { + ctx.u.Lock() + defer ctx.u.Unlock() + return ctx.uretprobes +} + +func (ctx *EBPFContext) ClearURetProbes() { + ctx.u.Lock() + defer ctx.u.Unlock() + // We wrap this in a sync.Once because we want this operation to only + // happen once per hit of the runtime.copystack function. We seem to hit that + // breakpoint multiple times, but we only want to clear the list of uretprobes + // once. + ctx.clearURetProbesOnce.Do(func() { + ctx.clearedURetProbes = ctx.uretprobes + ctx.uretprobes = make([]Uretprobe, 0) + }) +} + +func (ctx *EBPFContext) GetClearedURetProbes() []Uretprobe { + ctx.u.Lock() + defer ctx.u.Unlock() + return ctx.clearedURetProbes +} + func (ctx *EBPFContext) AttachUprobe(pid int, name string, offset uint32) error { if ctx.bpfProg == nil { return errors.New("no eBPF program loaded") @@ -51,6 +92,23 @@ func (ctx *EBPFContext) AttachUprobe(pid int, name string, offset uint32) error return err } +func (ctx *EBPFContext) AttachURetprobe(pid int, name string, offset uint32) error { + ctx.u.Lock() + defer ctx.u.Unlock() + if ctx.bpfProg == nil { + return errors.New("no eBPF program loaded") + } + link, err := ctx.bpfProg.AttachURetprobe(pid, name, offset) + ctx.uretprobes = append(ctx.uretprobes, Uretprobe{Link: link, Pid: pid, Path: name, Offset: offset}) + + // If we're attaching a uretprobe we can consider the list of uretprobes to be + // active again, which means they can once again be cleared. Let's set up another + // sync.Once to do this. + ctx.clearURetProbesOnce = sync.Once{} + + return err +} + func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64) error { if ctx.bpfArgMap == nil { return errors.New("eBPF map not loaded") @@ -82,7 +140,7 @@ func LoadEBPFTracingProgram() (*EBPFContext, error) { var ctx EBPFContext var err error - ctx.bpfModule, err = bpf.NewModuleFromBuffer(TraceProbeBytes, "trace.o") + ctx.bpfModule, err = bpf.NewModuleFromBuffer(TraceProbeBytes, "trace_probe/trace.o") if err != nil { return nil, err } @@ -114,7 +172,7 @@ func LoadEBPFTracingProgram() (*EBPFContext, error) { return } - parsed := ParseFunctionParameterList(b) + parsed := parseFunctionParameterList(b) ctx.m.Lock() ctx.parsedBpfEvents = append(ctx.parsedBpfEvents, parsed) @@ -125,7 +183,7 @@ func LoadEBPFTracingProgram() (*EBPFContext, error) { return &ctx, nil } -func ParseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { +func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { params := (*C.function_parameter_list_t)(unsafe.Pointer(&rawParamBytes[0])) defer runtime.KeepAlive(params) // Ensure the param is not garbage collected. @@ -134,10 +192,10 @@ func ParseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { rawParams.FnAddr = int(params.fn_addr) rawParams.GoroutineID = int(params.goroutine_id) - for i := 0; i < int(params.n_parameters); i++ { + parseParam := func(param C.function_parameter_t) *RawUProbeParam { iparam := &RawUProbeParam{} data := make([]byte, 0x60) - ret := params.params[i] + ret := param iparam.Kind = reflect.Kind(ret.kind) val := C.GoBytes(unsafe.Pointer(&ret.val), C.int(ret.size)) @@ -161,8 +219,14 @@ func ParseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { iparam.Base = FakeAddressBase + 0x30 iparam.Len = int64(strLen) } + return iparam + } - rawParams.InputParams = append(rawParams.InputParams, iparam) + for i := 0; i < int(params.n_parameters); i++ { + rawParams.InputParams = append(rawParams.InputParams, parseParam(params.params[i])) + } + for i := 0; i < int(params.n_ret_parameters); i++ { + rawParams.ReturnParams = append(rawParams.ReturnParams, parseParam(params.ret_params[i])) } return rawParams @@ -171,12 +235,13 @@ func ParseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeArgMap) C.function_parameter_list_t { var params C.function_parameter_list_t params.goid_offset = C.uint(goidOffset) - params.n_parameters = C.uint(len(args)) params.fn_addr = C.uint(entry) - for i, arg := range args { + params.n_parameters = C.uint(0) + params.n_ret_parameters = C.uint(0) + for _, arg := range args { var param C.function_parameter_t param.size = C.uint(arg.Size) - param.offset = C.uint(arg.Offset) + param.offset = C.int(arg.Offset) param.kind = C.uint(arg.Kind) if arg.InReg { param.in_reg = true @@ -188,7 +253,15 @@ func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeAr param.reg_nums[i] = C.int(arg.Pieces[i]) } } - params.params[i] = param + if !arg.Ret { + params.params[params.n_parameters] = param + params.n_parameters++ + } else { + offset := arg.Offset + param.offset = C.int(offset) + params.ret_params[params.n_ret_parameters] = param + params.n_ret_parameters++ + } } return params } diff --git a/pkg/proc/internal/ebpf/helpers_disabled.go b/pkg/proc/internal/ebpf/helpers_disabled.go index 8ea909509b..b59bc51291 100644 --- a/pkg/proc/internal/ebpf/helpers_disabled.go +++ b/pkg/proc/internal/ebpf/helpers_disabled.go @@ -7,9 +7,31 @@ import ( "errors" ) +type Uretprobe struct { + Link interface{} + Pid int + Path string + Offset uint32 +} + +func (u *Uretprobe) Destroy() error { + return nil +} + type EBPFContext struct { } +func (ctx *EBPFContext) GetURetProbes() []Uretprobe { + return nil +} + +func (ctx *EBPFContext) ClearURetProbes() { +} + +func (ctx *EBPFContext) GetClearedURetProbes() []Uretprobe { + return nil +} + func (ctx *EBPFContext) Close() { } @@ -18,6 +40,10 @@ func (ctx *EBPFContext) AttachUprobe(pid int, name string, offset uint32) error return errors.New("eBPF is disabled") } +func (ctx *EBPFContext) AttachURetprobe(pid int, name string, offset uint32) error { + return errors.New("eBPF is disabled") +} + func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64) error { return errors.New("eBPF is disabled") } @@ -33,7 +59,3 @@ func SymbolToOffset(file, symbol string) (uint32, error) { func LoadEBPFTracingProgram() (*EBPFContext, error) { return nil, errors.New("eBPF disabled") } - -func ParseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { - return RawUProbeParams{} -} diff --git a/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h b/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h index fec16670f8..46b1ab44b9 100644 --- a/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h +++ b/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h @@ -8,7 +8,7 @@ typedef struct function_parameter { unsigned int size; // Offset from stack pointer. This should only be set from the Go side. - unsigned int offset; + int offset; // If true, the parameter is passed in a register. bool in_reg; @@ -20,7 +20,7 @@ typedef struct function_parameter { int reg_nums[6]; // The following are filled in by the eBPF program. - unsigned int daddr; // Data address. + size_t daddr; // Data address. char val[0x30]; // Value of the parameter. char deref_val[0x30]; // Dereference value of the parameter. } function_parameter_t; @@ -33,6 +33,10 @@ typedef struct function_parameter_list { int goroutine_id; unsigned int fn_addr; + unsigned int n_parameters; // number of parameters. function_parameter_t params[6]; // list of parameters. + + unsigned int n_ret_parameters; // number of return parameters. + function_parameter_t ret_params[6]; // list of return parameters. } function_parameter_list_t; diff --git a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c index 505981c2fc..93b14cec3e 100644 --- a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c +++ b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c @@ -124,9 +124,9 @@ int parse_param(struct pt_regs *ctx, function_parameter_t *param) { // a slice we will need some further processing below. int ret = 0; if (param->in_reg) { - parse_param_registers(ctx, param); + ret = parse_param_registers(ctx, param); } else { - parse_param_stack(ctx, param); + ret = parse_param_stack(ctx, param); } if (ret != 0) { return ret; @@ -176,11 +176,47 @@ int get_goroutine_id(function_parameter_list_t *parsed_args) { return 1; } +__always_inline +void parse_params(struct pt_regs *ctx, function_parameter_list_t *args, function_parameter_list_t *parsed_args, bool ret) { + // Since we cannot loop in eBPF programs let's take adavantage of the + // fact that in C switch cases will pass through automatically. + if (!ret) { + switch (args->n_parameters) { + case 6: + parse_param(ctx, &parsed_args->params[5]); + case 5: + parse_param(ctx, &parsed_args->params[4]); + case 4: + parse_param(ctx, &parsed_args->params[3]); + case 3: + parse_param(ctx, &parsed_args->params[2]); + case 2: + parse_param(ctx, &parsed_args->params[1]); + case 1: + parse_param(ctx, &parsed_args->params[0]); + } + } else { + switch (args->n_ret_parameters) { + case 6: + parse_param(ctx, &parsed_args->ret_params[5]); + case 5: + parse_param(ctx, &parsed_args->ret_params[4]); + case 4: + parse_param(ctx, &parsed_args->ret_params[3]); + case 3: + parse_param(ctx, &parsed_args->ret_params[2]); + case 2: + parse_param(ctx, &parsed_args->ret_params[1]); + case 1: + parse_param(ctx, &parsed_args->ret_params[0]); + } + } +} + SEC("uprobe/dlv_trace") int uprobe__dlv_trace(struct pt_regs *ctx) { function_parameter_list_t *args; function_parameter_list_t *parsed_args; - function_parameter_t param; uint64_t key = ctx->ip; args = bpf_map_lookup_elem(&arg_map, &key); @@ -192,28 +228,43 @@ int uprobe__dlv_trace(struct pt_regs *ctx) { if (!parsed_args) { return 1; } - memcpy(parsed_args, args, sizeof(function_parameter_list_t)); + + // Initialize the parsed_args struct. + parsed_args->goid_offset = args->goid_offset; + parsed_args->g_addr_offset = args->g_addr_offset; + parsed_args->goroutine_id = args->goroutine_id; + parsed_args->fn_addr = args->fn_addr; + parsed_args->n_parameters = args->n_parameters; + parsed_args->n_ret_parameters = args->n_ret_parameters; + memcpy(parsed_args->params, args->params, sizeof(args->params)); + memcpy(parsed_args->ret_params, args->ret_params, sizeof(args->ret_params)); if (!get_goroutine_id(parsed_args)) { bpf_ringbuf_discard(parsed_args, 0); return 1; } - // Since we cannot loop in eBPF programs let's take adavantage of the - // fact that in C switch cases will pass through automatically. - switch (args->n_parameters) { - case 6: - parse_param(ctx, &parsed_args->params[5]); - case 5: - parse_param(ctx, &parsed_args->params[4]); - case 4: - parse_param(ctx, &parsed_args->params[3]); - case 3: - parse_param(ctx, &parsed_args->params[2]); - case 2: - parse_param(ctx, &parsed_args->params[1]); - case 1: - parse_param(ctx, &parsed_args->params[0]); + if (args->fn_addr == ctx->ip) { + // In uprobe at function entry. + + // We're in the entry point of the function at the uprobe we set. + // Get our return address and ensure that we set the parameter information + // at that address as well for the uretprobe. + size_t ret_addr; + bpf_probe_read_user(&ret_addr, sizeof(size_t), (void*)(ctx->sp)); + bpf_map_update_elem(&arg_map, &ret_addr, args, 0); + + // Parse input parameters. + parse_params(ctx, args, parsed_args, false); + } else { + // In uretprobe at function return address. + // Note we are not at the RET instruction, + // we are actually at the return address of + // the function we are tracing in the calling + // function. + + // Parse output parameters. + parse_params(ctx, args, parsed_args, true); } bpf_ringbuf_submit(parsed_args, 0); diff --git a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.h b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.h index b46d2025ea..971db59fa4 100644 --- a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.h +++ b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.h @@ -16,6 +16,7 @@ struct { __uint(max_entries, BPF_MAX_VAR_SIZ); } heap SEC(".maps"); +// Map which uses instruction address as key and function parameter info as the value. struct { __uint(max_entries, 42); __uint(type, BPF_MAP_TYPE_HASH); diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 98734246ba..b47de73be4 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -708,12 +708,17 @@ func (dbp *nativeProcess) EntryPoint() (uint64, error) { func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error { // Lazily load and initialize the BPF program upon request to set a uprobe. if dbp.os.ebpf == nil { - dbp.os.ebpf, _ = ebpf.LoadEBPFTracingProgram() + var err error + dbp.os.ebpf, err = ebpf.LoadEBPFTracingProgram() + if err != nil { + return err + } } - // We only allow up to 6 args for a BPF probe. + // We only allow up to 12 args for a BPF probe. + // 6 inputs + 6 outputs. // Return early if we have more. - if len(args) > 6 { + if len(args) > 12 { return errors.New("too many arguments in traced function, max is 6") } @@ -733,7 +738,32 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf if err != nil { return err } - return dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, offset) + err = dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, offset) + if err != nil { + return err + } + return dbp.os.ebpf.AttachURetprobe(dbp.Pid(), debugname, offset) +} + +func (dbp *nativeProcess) DisableURetProbes() error { + for _, ret := range dbp.os.ebpf.GetURetProbes() { + err := ret.Destroy() + if err != nil { + return err + } + } + dbp.os.ebpf.ClearURetProbes() + return nil +} + +func (dbp *nativeProcess) EnableURetProbes() error { + for _, ret := range dbp.os.ebpf.GetClearedURetProbes() { + err := dbp.os.ebpf.AttachURetprobe(ret.Pid, ret.Path, ret.Offset) + if err != nil { + return err + } + } + return nil } func killProcess(pid int) error { diff --git a/pkg/proc/stackwatch.go b/pkg/proc/stackwatch.go index 2b33f70ff9..5e77a402d6 100644 --- a/pkg/proc/stackwatch.go +++ b/pkg/proc/stackwatch.go @@ -95,11 +95,26 @@ func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoi } // Stack Resize Sentinel + return t.setStackResizeSentinel(watchpoint, false, func(th Thread) bool { + adjustStackWatchpoint(t, th, watchpoint) + return false // we never want this breakpoint to be shown to the user + }) +} +func (t *Target) setStackResizeSentinel(watchpoint *Breakpoint, breakOnEntry bool, callback func(Thread) bool) error { fn := t.BinInfo().LookupFunc["runtime.copystack"] if fn == nil { return errors.New("could not find runtime.copystack") } + if breakOnEntry { + bp, err := t.SetBreakpoint(fn.Entry, StackResizeBreakpoint, nil) + if err != nil { + return err + } + bp.Name = "copystack-entry" + brklt := bp.Breaklets[len(bp.Breaklets)-1] + brklt.callback = callback + } text, err := Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End) if err != nil { return err @@ -116,18 +131,15 @@ func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoi if retpc == 0 { return errors.New("could not find return instruction in runtime.copystack") } - rszbp, err := t.SetBreakpoint(retpc, StackResizeBreakpoint, sameGCond) + + rszbp, err := t.SetBreakpoint(retpc, StackResizeBreakpoint, nil) if err != nil { return err } rszbreaklet := rszbp.Breaklets[len(rszbp.Breaklets)-1] rszbreaklet.watchpoint = watchpoint - rszbreaklet.callback = func(th Thread) bool { - adjustStackWatchpoint(t, th, watchpoint) - return false // we never want this breakpoint to be shown to the user - } - + rszbreaklet.callback = callback return nil } diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 33485d8797..02f23428a3 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -10,6 +10,7 @@ import ( "github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/goversion" + "github.com/go-delve/delve/pkg/proc/internal/ebpf" ) var ( @@ -401,35 +402,49 @@ func (t *Target) CurrentThread() Thread { } type UProbeTraceResult struct { - FnAddr int - GoroutineID int - InputParams []*Variable + FnAddr int + GoroutineID int + InputParams []*Variable + ReturnParams []*Variable } func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult { var results []*UProbeTraceResult tracepoints := t.proc.GetBufferedTracepoints() + convertInputParamToVariable := func(ip *ebpf.RawUProbeParam) *Variable { + v := &Variable{} + v.RealType = ip.RealType + v.Len = ip.Len + v.Base = ip.Base + v.Addr = ip.Addr + v.Kind = ip.Kind + + cachedMem := CreateLoadedCachedMemory(ip.Data) + compMem, _ := CreateCompositeMemory(cachedMem, t.BinInfo().Arch, op.DwarfRegisters{}, ip.Pieces) + v.mem = compMem + + return v + } for _, tp := range tracepoints { r := &UProbeTraceResult{} r.FnAddr = tp.FnAddr r.GoroutineID = tp.GoroutineID for _, ip := range tp.InputParams { - v := &Variable{} - v.RealType = ip.RealType - v.Len = ip.Len - v.Base = ip.Base - v.Addr = ip.Addr - v.Kind = ip.Kind - - cachedMem := CreateLoadedCachedMemory(ip.Data) - compMem, _ := CreateCompositeMemory(cachedMem, t.BinInfo().Arch, op.DwarfRegisters{}, ip.Pieces) - v.mem = compMem + v := convertInputParamToVariable(ip) // Load the value here so that we don't have to export // loadValue outside of proc. v.loadValue(loadFullValue) r.InputParams = append(r.InputParams, v) } + for _, ip := range tp.ReturnParams { + v := convertInputParamToVariable(ip) + + // Load the value here so that we don't have to export + // loadValue outside of proc. + v.loadValue(loadFullValue) + r.ReturnParams = append(r.ReturnParams, v) + } results = append(results, r) } return results diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 2562d6726c..eee82b8e28 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -2157,6 +2157,9 @@ func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult { for _, p := range trace.InputParams { results[i].InputParams = append(results[i].InputParams, *api.ConvertVar(p)) } + for _, p := range trace.ReturnParams { + results[i].ReturnParams = append(results[i].ReturnParams, *api.ConvertVar(p)) + } } return results } From af2f0867e4ea8d23f915f28bb4a962bbbc34681a Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 12 Oct 2021 15:40:58 -0700 Subject: [PATCH 03/17] pkg/proc: replace uretprobes w/ uprobes I thought we could cleverly enable / disable uretprobes anytime Go needs to scan the stack, but it seems the kernel doesn't do the cleanup I thought it would in terms of restoring addresses on the stack. Instead, just simply use uprobes all around and avoid all this nonsense entirely. --- _fixtures/databpstack2.go | 14 +-- cmd/dlv/dlv_test.go | 6 +- pkg/proc/bininfo.go | 6 +- pkg/proc/breakpoints.go | 20 ----- pkg/proc/eval.go | 4 +- pkg/proc/gdbserial/gdbserver.go | 8 -- pkg/proc/interface.go | 2 - pkg/proc/internal/ebpf/helpers.go | 54 +----------- pkg/proc/internal/ebpf/helpers_disabled.go | 2 +- .../ebpf/trace_probe/function_vals.bpf.h | 1 + .../internal/ebpf/trace_probe/trace.bpf.c | 2 +- pkg/proc/native/proc_linux.go | 86 ++++++++++++++----- pkg/proc/stackwatch.go | 2 +- .../aquasecurity/libbpfgo/libbpfgo.go | 8 ++ 14 files changed, 94 insertions(+), 121 deletions(-) diff --git a/_fixtures/databpstack2.go b/_fixtures/databpstack2.go index dab14466de..9fc483900e 100644 --- a/_fixtures/databpstack2.go +++ b/_fixtures/databpstack2.go @@ -4,20 +4,14 @@ import ( "fmt" ) -func f(n int) { - w := 0 - g(n, &w) // Position 0 -} - -func g(cnt int, p *int) int { +func g(cnt int) int { if cnt == 0 { - *p = 10 - return *p // Position 1 + return 10 } - return g(cnt-1, p) + return g(cnt - 1) } func main() { - f(1000) + fmt.Println(g(1000)) fmt.Printf("done\n") // Position 2 } diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 0a9f759da6..2cb9d3d4e2 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -911,7 +911,7 @@ func TestTraceEBPFRet(t *testing.T) { dlvbin, tmpdir := getDlvBinEBPF(t) defer os.RemoveAll(tmpdir) - expected := "> (1) main.g(1000, 0xdeadbeef) => 10\n" + expected := "> (1) main.g(1000) => \"10\"\n" fixtures := protest.FindFixturesDir() cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "databpstack2.go"), "main.g") @@ -921,10 +921,10 @@ func TestTraceEBPFRet(t *testing.T) { assertNoError(cmd.Start(), t, "running trace") - output, err := bufio.NewReader(rdr).ReadString('\n') + output, err := ioutil.ReadAll(rdr) assertNoError(err, t, "ReadAll") - if !strings.Contains(output, expected) { + if !strings.Contains(string(output), expected) { t.Fatalf("expected:\n%s\ngot:\n%s", expected, output) } cmd.Wait() diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 6e40601a97..a337c9edae 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -701,7 +701,7 @@ func (bi *BinaryInfo) PCToImage(pc uint64) *Image { type Image struct { Path string StaticBase uint64 - addr uint64 + Addr uint64 index int // index of this object in BinaryInfo.SharedObjects @@ -750,13 +750,13 @@ func (bi *BinaryInfo) AddImage(path string, addr uint64) error { return nil } for _, image := range bi.Images { - if image.Path == path && image.addr == addr { + if image.Path == path && image.Addr == addr { return nil } } // Actually add the image. - image := &Image{Path: path, addr: addr, typeCache: make(map[dwarf.Offset]godwarf.Type)} + image := &Image{Path: path, Addr: addr, typeCache: make(map[dwarf.Offset]godwarf.Type)} image.dwarfTreeCache, _ = simplelru.NewLRU(dwarfTreeCacheSize, nil) // add Image regardless of error so that we don't attempt to re-add it every time we stop diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index bed000cafa..e34fe5f40e 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -558,9 +558,6 @@ func (t *Target) SetEBPFTracepoint(fnName string) error { } } offset := origOffset + int64(t.BinInfo().Arch.PtrSize()) - if isret { - offset = origOffset - } args = append(args, ebpf.UProbeArgMap{ Offset: offset, Size: dt.Size(), @@ -573,23 +570,6 @@ func (t *Target) SetEBPFTracepoint(fnName string) error { // Finally, set the uprobe on the function. t.proc.SetUProbe(fnName, goidOffset, args) - - // Next we want to set the uretprobe on the function. - // We have to be careful here, just as with stack watchpoints, we need to - // ensure that we watch the runtime to ensure that whenever it decides to grow and copy - // a stack frame, we will be notified. - err = t.setStackResizeSentinel(nil, true, func(th Thread) bool { - if th.Breakpoint().Name == "copystack-entry" { - t.proc.DisableURetProbes() - } else { - t.proc.EnableURetProbes() - } - return false // we don't want this showing up for users. - }) - if err != nil { - return err - } - return nil } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index eed01c2cee..00297bdbb7 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -511,10 +511,10 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { pkgvars := make([]packageVar, len(scope.BinInfo.packageVars)) copy(pkgvars, scope.BinInfo.packageVars) sort.Slice(pkgvars, func(i, j int) bool { - if pkgvars[i].cu.image.addr == pkgvars[j].cu.image.addr { + if pkgvars[i].cu.image.Addr == pkgvars[j].cu.image.Addr { return pkgvars[i].offset < pkgvars[j].offset } - return pkgvars[i].cu.image.addr < pkgvars[j].cu.image.addr + return pkgvars[i].cu.image.Addr < pkgvars[j].cu.image.Addr }) vars := make([]*Variable, 0, len(scope.BinInfo.packageVars)) for _, pkgvar := range pkgvars { diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 04f3788d25..b2173fcf28 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -381,14 +381,6 @@ func (dbp *gdbProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UP panic("not implemented") } -func (dbp *gdbProcess) EnableURetProbes() error { - panic("not implemented") -} - -func (dbp *gdbProcess) DisableURetProbes() error { - panic("not implemented") -} - // unusedPort returns an unused tcp port // This is a hack and subject to a race condition with other running // programs, but most (all?) OS will cycle through all ephemeral ports diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index 55782dbda3..e5797e264e 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -47,8 +47,6 @@ type ProcessInternal interface { SupportsBPF() bool SetUProbe(string, int64, []ebpf.UProbeArgMap) error - EnableURetProbes() error - DisableURetProbes() error // DumpProcessNotes returns ELF core notes describing the process and its threads. // Implementing this method is optional. diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go index 6754471fca..75e61cd81d 100644 --- a/pkg/proc/internal/ebpf/helpers.go +++ b/pkg/proc/internal/ebpf/helpers.go @@ -44,11 +44,6 @@ type EBPFContext struct { bpfRingBuf *bpf.RingBuffer bpfArgMap *bpf.BPFMap - uretprobes []Uretprobe - clearedURetProbes []Uretprobe - clearURetProbesOnce sync.Once - u sync.Mutex - parsedBpfEvents []RawUProbeParams m sync.Mutex } @@ -59,31 +54,6 @@ func (ctx *EBPFContext) Close() { } } -func (ctx *EBPFContext) GetURetProbes() []Uretprobe { - ctx.u.Lock() - defer ctx.u.Unlock() - return ctx.uretprobes -} - -func (ctx *EBPFContext) ClearURetProbes() { - ctx.u.Lock() - defer ctx.u.Unlock() - // We wrap this in a sync.Once because we want this operation to only - // happen once per hit of the runtime.copystack function. We seem to hit that - // breakpoint multiple times, but we only want to clear the list of uretprobes - // once. - ctx.clearURetProbesOnce.Do(func() { - ctx.clearedURetProbes = ctx.uretprobes - ctx.uretprobes = make([]Uretprobe, 0) - }) -} - -func (ctx *EBPFContext) GetClearedURetProbes() []Uretprobe { - ctx.u.Lock() - defer ctx.u.Unlock() - return ctx.clearedURetProbes -} - func (ctx *EBPFContext) AttachUprobe(pid int, name string, offset uint32) error { if ctx.bpfProg == nil { return errors.New("no eBPF program loaded") @@ -92,28 +62,11 @@ func (ctx *EBPFContext) AttachUprobe(pid int, name string, offset uint32) error return err } -func (ctx *EBPFContext) AttachURetprobe(pid int, name string, offset uint32) error { - ctx.u.Lock() - defer ctx.u.Unlock() - if ctx.bpfProg == nil { - return errors.New("no eBPF program loaded") - } - link, err := ctx.bpfProg.AttachURetprobe(pid, name, offset) - ctx.uretprobes = append(ctx.uretprobes, Uretprobe{Link: link, Pid: pid, Path: name, Offset: offset}) - - // If we're attaching a uretprobe we can consider the list of uretprobes to be - // active again, which means they can once again be cleared. Let's set up another - // sync.Once to do this. - ctx.clearURetProbesOnce = sync.Once{} - - return err -} - -func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64) error { +func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64, isret bool) error { if ctx.bpfArgMap == nil { return errors.New("eBPF map not loaded") } - params := createFunctionParameterList(key, goidOffset, args) + params := createFunctionParameterList(key, goidOffset, args, isret) params.g_addr_offset = C.longlong(gAddrOffset) return ctx.bpfArgMap.Update(unsafe.Pointer(&key), unsafe.Pointer(¶ms)) } @@ -232,10 +185,11 @@ func parseFunctionParameterList(rawParamBytes []byte) RawUProbeParams { return rawParams } -func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeArgMap) C.function_parameter_list_t { +func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeArgMap, isret bool) C.function_parameter_list_t { var params C.function_parameter_list_t params.goid_offset = C.uint(goidOffset) params.fn_addr = C.uint(entry) + params.is_ret = C.bool(isret) params.n_parameters = C.uint(0) params.n_ret_parameters = C.uint(0) for _, arg := range args { diff --git a/pkg/proc/internal/ebpf/helpers_disabled.go b/pkg/proc/internal/ebpf/helpers_disabled.go index b59bc51291..66035e04a9 100644 --- a/pkg/proc/internal/ebpf/helpers_disabled.go +++ b/pkg/proc/internal/ebpf/helpers_disabled.go @@ -44,7 +44,7 @@ func (ctx *EBPFContext) AttachURetprobe(pid int, name string, offset uint32) err return errors.New("eBPF is disabled") } -func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64) error { +func (ctx *EBPFContext) UpdateArgMap(key uint64, goidOffset int64, args []UProbeArgMap, gAddrOffset uint64, isret bool) error { return errors.New("eBPF is disabled") } diff --git a/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h b/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h index 46b1ab44b9..0df7b9b7c8 100644 --- a/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h +++ b/pkg/proc/internal/ebpf/trace_probe/function_vals.bpf.h @@ -33,6 +33,7 @@ typedef struct function_parameter_list { int goroutine_id; unsigned int fn_addr; + bool is_ret; unsigned int n_parameters; // number of parameters. function_parameter_t params[6]; // list of parameters. diff --git a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c index 93b14cec3e..29085c7414 100644 --- a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c +++ b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c @@ -244,7 +244,7 @@ int uprobe__dlv_trace(struct pt_regs *ctx) { return 1; } - if (args->fn_addr == ctx->ip) { + if (!args->is_ret) { // In uprobe at function entry. // We're in the entry point of the function at the uprobe we set. diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index b47de73be4..4bf5c95b9b 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -3,6 +3,7 @@ package native import ( "bufio" "bytes" + "debug/elf" "errors" "fmt" "io/ioutil" @@ -719,7 +720,7 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf // 6 inputs + 6 outputs. // Return early if we have more. if len(args) > 12 { - return errors.New("too many arguments in traced function, max is 6") + return errors.New("too many arguments in traced function, max is 12 input+return") } fn, ok := dbp.bi.LookupFunc[fnName] @@ -728,7 +729,7 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf } key := fn.Entry - err := dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, dbp.BinInfo().GStructOffset()) + err := dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, dbp.BinInfo().GStructOffset(), false) if err != nil { return err } @@ -738,32 +739,77 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf if err != nil { return err } - err = dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, offset) + + // First attach a uprobe at all return addresses. We do this instead of using a uretprobe + // for two reasons: + // 1. uretprobes do not play well with Go + // 2. uretprobes seem to not restore the function return addr on the stack when removed, destroying any + // kind of workaround we could come up with. + // TODO(derekparker): this whole thing could likely be optimized a bit. + f, err := elf.Open(dbp.BinInfo().Images[0].Path) if err != nil { - return err + return fmt.Errorf("could not open elf file to resolve symbol offset: %w", err) + } + syms, err := f.Symbols() + if err != nil { + return fmt.Errorf("could not open symbol section to resolve symbol offset: %w", err) } - return dbp.os.ebpf.AttachURetprobe(dbp.Pid(), debugname, offset) -} -func (dbp *nativeProcess) DisableURetProbes() error { - for _, ret := range dbp.os.ebpf.GetURetProbes() { - err := ret.Destroy() - if err != nil { - return err + sectionsToSearchForSymbol := []*elf.Section{} + + for i := range f.Sections { + if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { + sectionsToSearchForSymbol = append(sectionsToSearchForSymbol, f.Sections[i]) } } - dbp.os.ebpf.ClearURetProbes() - return nil -} -func (dbp *nativeProcess) EnableURetProbes() error { - for _, ret := range dbp.os.ebpf.GetClearedURetProbes() { - err := dbp.os.ebpf.AttachURetprobe(ret.Pid, ret.Path, ret.Offset) - if err != nil { - return err + var executableSection *elf.Section + + for j := range syms { + if syms[j].Name == fnName { + // Find what section the symbol is in by checking the executable section's + // addr space. + for m := range sectionsToSearchForSymbol { + if syms[j].Value > sectionsToSearchForSymbol[m].Addr && + syms[j].Value < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size { + executableSection = sectionsToSearchForSymbol[m] + } + } + + if executableSection == nil { + return errors.New("could not find symbol in executable sections of binary") + } + var regs proc.Registers + mem := dbp.Memory() + regs, _ = dbp.memthread.Registers() + instructions, err := proc.Disassemble(mem, regs, &proc.BreakpointMap{}, dbp.BinInfo(), fn.Entry, fn.End) + if err != nil { + return err + } + + var addrs []uint64 + for _, instruction := range instructions { + if instruction.IsRet() { + addrs = append(addrs, instruction.Loc.PC) + } + } + addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...) + for _, addr := range addrs { + err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, dbp.BinInfo().GStructOffset(), true) + if err != nil { + return err + } + off := uint32(addr - executableSection.Addr + executableSection.Offset) + err = dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, off) + if err != nil { + return err + } + break + } } } - return nil + + return dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, offset) } func killProcess(pid int) error { diff --git a/pkg/proc/stackwatch.go b/pkg/proc/stackwatch.go index 5e77a402d6..58e4d71e7a 100644 --- a/pkg/proc/stackwatch.go +++ b/pkg/proc/stackwatch.go @@ -180,7 +180,7 @@ func watchpointOutOfScope(t *Target, watchpoint *Breakpoint) { // adjustStackWatchpoint is called when the goroutine of watchpoint resizes // its stack. It is used as a breaklet callback function. -// Its responsibility is to move the watchpoint to a its new address. +// Its responsibility is to move the watchpoint to its new address. func adjustStackWatchpoint(t *Target, th Thread, watchpoint *Breakpoint) { g, _ := GetG(th) if g == nil { diff --git a/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go b/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go index cb525c85ed..a8e02d37fb 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go +++ b/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go @@ -251,6 +251,14 @@ type BPFLink struct { eventName string } +func (l *BPFLink) Detach() error { + ret := C.bpf_link__detach(l.link) + if ret < 0 { + return syscall.Errno(-ret) + } + return nil +} + func (l *BPFLink) Destroy() error { ret := C.bpf_link__destroy(l.link) if ret < 0 { From ce0300b875b75e8486fe0ff3fcbad4178e79dcaf Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 12 Oct 2021 16:10:46 -0700 Subject: [PATCH 04/17] cmd/dlv: update ebpf trace test --- cmd/dlv/dlv_test.go | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 2cb9d3d4e2..7320ec599a 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -871,7 +871,7 @@ func TestTraceEBPF(t *testing.T) { dlvbin, tmpdir := getDlvBinEBPF(t) defer os.RemoveAll(tmpdir) - expected := []byte("> (1) main.foo(99, 9801)\n") + expected := []byte("> (1) main.foo(99, 9801)\n=> \"9900\"") fixtures := protest.FindFixturesDir() cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "issue573.go"), "foo") @@ -890,46 +890,6 @@ func TestTraceEBPF(t *testing.T) { cmd.Wait() } -func TestTraceEBPFRet(t *testing.T) { - if os.Getenv("CI") == "true" { - t.Skip("cannot run test in CI, requires kernel compiled with btf support") - } - if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { - t.Skip("not implemented on non linux/amd64 systems") - } - if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) { - t.Skip("requires at least Go 1.16 to run test") - } - usr, err := user.Current() - if err != nil { - t.Fatal(err) - } - if usr.Uid != "0" { - t.Skip("test must be run as root") - } - - dlvbin, tmpdir := getDlvBinEBPF(t) - defer os.RemoveAll(tmpdir) - - expected := "> (1) main.g(1000) => \"10\"\n" - - fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "databpstack2.go"), "main.g") - rdr, err := cmd.StderrPipe() - assertNoError(err, t, "stderr pipe") - defer rdr.Close() - - assertNoError(cmd.Start(), t, "running trace") - - output, err := ioutil.ReadAll(rdr) - assertNoError(err, t, "ReadAll") - - if !strings.Contains(string(output), expected) { - t.Fatalf("expected:\n%s\ngot:\n%s", expected, output) - } - cmd.Wait() -} - func TestDlvTestChdir(t *testing.T) { dlvbin, tmpdir := getDlvBin(t) defer os.RemoveAll(tmpdir) From acb419b4d9d2f48d6ee7e5d6ab8fe7f559368ec7 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 12 Oct 2021 16:16:55 -0700 Subject: [PATCH 05/17] *: update staticcheck.txt --- _scripts/staticcheck-out.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_scripts/staticcheck-out.txt b/_scripts/staticcheck-out.txt index e69de29bb2..c1f2954deb 100644 --- a/_scripts/staticcheck-out.txt +++ b/_scripts/staticcheck-out.txt @@ -0,0 +1,3 @@ +localtests/issue2593/main.go:23:6: func issue2593_call is unused (U1000) +pkg/proc/native/proc_linux.go:807:5: the surrounding loop is unconditionally terminated (SA4004) +vendor/github.com/aquasecurity/libbpfgo/libbpf_cb.go:4:2: could not import C (cgo preprocessing failed) (compile) From 4ec953cfa5ff544b616de51925634ea139737080 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 12 Oct 2021 16:24:38 -0700 Subject: [PATCH 06/17] pkg/proc: remove unconditional loop termination --- pkg/proc/native/proc_linux.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 4bf5c95b9b..25e35085b3 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -804,7 +804,6 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf if err != nil { return err } - break } } } From e8212b7f5312382a94a30a9a152f8267b0a94e7d Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 12 Oct 2021 16:25:06 -0700 Subject: [PATCH 07/17] *: update staticcheck again --- _scripts/staticcheck-out.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/_scripts/staticcheck-out.txt b/_scripts/staticcheck-out.txt index c1f2954deb..e69de29bb2 100644 --- a/_scripts/staticcheck-out.txt +++ b/_scripts/staticcheck-out.txt @@ -1,3 +0,0 @@ -localtests/issue2593/main.go:23:6: func issue2593_call is unused (U1000) -pkg/proc/native/proc_linux.go:807:5: the surrounding loop is unconditionally terminated (SA4004) -vendor/github.com/aquasecurity/libbpfgo/libbpf_cb.go:4:2: could not import C (cgo preprocessing failed) (compile) From 960de0e74e0049cf4e6142bcb028c6cc233f962d Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 08:56:36 -0700 Subject: [PATCH 08/17] pkg/proc: misc cleanup --- _fixtures/databpstack2.go | 17 ----------------- pkg/proc/bininfo.go | 6 +++--- pkg/proc/eval.go | 4 ++-- 3 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 _fixtures/databpstack2.go diff --git a/_fixtures/databpstack2.go b/_fixtures/databpstack2.go deleted file mode 100644 index 9fc483900e..0000000000 --- a/_fixtures/databpstack2.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "fmt" -) - -func g(cnt int) int { - if cnt == 0 { - return 10 - } - return g(cnt - 1) -} - -func main() { - fmt.Println(g(1000)) - fmt.Printf("done\n") // Position 2 -} diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index a337c9edae..6e40601a97 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -701,7 +701,7 @@ func (bi *BinaryInfo) PCToImage(pc uint64) *Image { type Image struct { Path string StaticBase uint64 - Addr uint64 + addr uint64 index int // index of this object in BinaryInfo.SharedObjects @@ -750,13 +750,13 @@ func (bi *BinaryInfo) AddImage(path string, addr uint64) error { return nil } for _, image := range bi.Images { - if image.Path == path && image.Addr == addr { + if image.Path == path && image.addr == addr { return nil } } // Actually add the image. - image := &Image{Path: path, Addr: addr, typeCache: make(map[dwarf.Offset]godwarf.Type)} + image := &Image{Path: path, addr: addr, typeCache: make(map[dwarf.Offset]godwarf.Type)} image.dwarfTreeCache, _ = simplelru.NewLRU(dwarfTreeCacheSize, nil) // add Image regardless of error so that we don't attempt to re-add it every time we stop diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 00297bdbb7..eed01c2cee 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -511,10 +511,10 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { pkgvars := make([]packageVar, len(scope.BinInfo.packageVars)) copy(pkgvars, scope.BinInfo.packageVars) sort.Slice(pkgvars, func(i, j int) bool { - if pkgvars[i].cu.image.Addr == pkgvars[j].cu.image.Addr { + if pkgvars[i].cu.image.addr == pkgvars[j].cu.image.addr { return pkgvars[i].offset < pkgvars[j].offset } - return pkgvars[i].cu.image.Addr < pkgvars[j].cu.image.Addr + return pkgvars[i].cu.image.addr < pkgvars[j].cu.image.addr }) vars := make([]*Variable, 0, len(scope.BinInfo.packageVars)) for _, pkgvar := range pkgvars { From 097212ba29685f6f397c7a28f2edf0eefa6a7776 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 08:59:19 -0700 Subject: [PATCH 09/17] revert libbpfgo upgrade, not needed --- go.mod | 2 +- go.sum | 4 - pkg/proc/internal/ebpf/helpers.go | 4 - .../aquasecurity/libbpfgo/.gitignore | 8 +- .../aquasecurity/libbpfgo/.gitmodules | 7 +- .../github.com/aquasecurity/libbpfgo/Makefile | 186 +------- .../aquasecurity/libbpfgo/Readme.md | 81 +--- .../libbpfgo/helpers/argumentParsers.go | 36 +- .../aquasecurity/libbpfgo/helpers/common.go | 75 ---- .../aquasecurity/libbpfgo/helpers/elf.go | 16 +- .../libbpfgo/helpers/kernel_config.go | 399 ------------------ .../libbpfgo/helpers/kernel_features.go | 228 ++++++++++ .../aquasecurity/libbpfgo/helpers/osinfo.go | 221 ---------- .../aquasecurity/libbpfgo/helpers/rwArray.go | 1 - .../libbpfgo/helpers/tracelisten.go | 9 +- .../aquasecurity/libbpfgo/libbpfgo.go | 193 +++------ vendor/modules.txt | 2 +- 17 files changed, 338 insertions(+), 1134 deletions(-) delete mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/common.go delete mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go create mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go delete mode 100644 vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go diff --git a/go.mod b/go.mod index 47e2f7faf1..4ba1ded6fe 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-delve/delve go 1.16 require ( - github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c + github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac github.com/cosiner/argv v0.1.0 github.com/creack/pty v1.1.9 github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 diff --git a/go.sum b/go.sum index 2b5fc96495..ab2954078c 100644 --- a/go.sum +++ b/go.sum @@ -18,10 +18,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac h1:oehMMAySC3p8eSwcwQ8lTXxeCkkPll+AwNesUNowbJ8= github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw= -github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0 h1:WekP69tdNlVfWdlXUqxGEwbeLCMfNcURBsq0uS5dPSI= -github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw= -github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c h1:MZdKpQb3jqE0q6zWS2nt1/ZAvKAML3PvSOeifi1UQ9w= -github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go index 75e61cd81d..47c1bfc5ba 100644 --- a/pkg/proc/internal/ebpf/helpers.go +++ b/pkg/proc/internal/ebpf/helpers.go @@ -33,10 +33,6 @@ type Uretprobe struct { Offset uint32 } -func (u *Uretprobe) Destroy() error { - return u.Link.Destroy() -} - type EBPFContext struct { bpfModule *bpf.Module bpfProg *bpf.BPFProg diff --git a/vendor/github.com/aquasecurity/libbpfgo/.gitignore b/vendor/github.com/aquasecurity/libbpfgo/.gitignore index c592b1a78f..67c39a63ae 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/.gitignore +++ b/vendor/github.com/aquasecurity/libbpfgo/.gitignore @@ -1,7 +1 @@ -output* -selftest/*/*.o -selftest/*/*.skel.h -selftest/*/*-static -selftest/*/*-dynamic -selftest/uprobe/ctest -selftest/uprobe/gotest +selftest/dist diff --git a/vendor/github.com/aquasecurity/libbpfgo/.gitmodules b/vendor/github.com/aquasecurity/libbpfgo/.gitmodules index 1706b4a8fc..a04deb79a3 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/.gitmodules +++ b/vendor/github.com/aquasecurity/libbpfgo/.gitmodules @@ -1,3 +1,4 @@ -[submodule "libbpf"] - path = libbpf - url = https://github.com/libbpf/libbpf.git + +[submodule "selftest/libbpf-module"] + path = selftest/libbpf-module + url = https://github.com/libbpf/libbpf diff --git a/vendor/github.com/aquasecurity/libbpfgo/Makefile b/vendor/github.com/aquasecurity/libbpfgo/Makefile index 08abb51b86..d6c26b598e 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/Makefile +++ b/vendor/github.com/aquasecurity/libbpfgo/Makefile @@ -1,171 +1,29 @@ -BASEDIR = $(abspath ./) +TARGET_BPF := test/test.bpf.o +VMLINUX_H = test/vmlinux.h -OUTPUT = ./output -SELFTEST = ./selftest +GO_SRC := $(shell find . -type f -name '*.go') +BPF_SRC := $(shell find . -type f -name '*.bpf.c') +PWD := $(shell pwd) -CC = gcc -CLANG = clang +LIBBPF_HEADERS := /usr/include/bpf +LIBBPF := "-lbpf" -ARCH := $(shell uname -m) -ARCH := $(subst x86_64,amd64,$(ARCH)) +.PHONY: all +all: test -BTFFILE = /sys/kernel/btf/vmlinux -BPFTOOL = $(shell which bpftool || /bin/false) -GIT = $(shell which git || /bin/false) -VMLINUXH = $(OUTPUT)/vmlinux.h +$(VMLINUX_H): + bpftool btf dump file /sys/kernel/btf/vmlinux format c > test/vmlinux.h -# libbpf +go_env := CC=gcc CGO_CFLAGS="-I $(LIBBPF_HEADERS)" CGO_LDFLAGS="$(LIBBPF)" +.PHONY: test +test: $(TARGET_BPF) $(GO_SRC) + $(go_env) go test -ldflags '-extldflags "-static"' . -LIBBPF_SRC = $(abspath ./libbpf/src) -LIBBPF_OBJ = $(abspath ./$(OUTPUT)/libbpf.a) -LIBBPF_OBJDIR = $(abspath ./$(OUTPUT)/libbpf) -LIBBPF_DESTDIR = $(abspath ./$(OUTPUT)) +$(TARGET_BPF): $(BPF_SRC) $(VMLINUX_H) + clang \ + -g -O2 -c -target bpf \ + -o $@ $< -CFLAGS = -g -O2 -Wall -fpie -LDFLAGS = - -# golang - -CGO_CFLAGS_STATIC = "-I$(abspath $(OUTPUT))" -CGO_LDFLAGS_STATIC = "-lelf -lz $(LIBBPF_OBJ)" -CGO_EXTLDFLAGS_STATIC = '-w -extldflags "-static"' - -CGO_CFGLAGS_DYN = "-I. -I/usr/include/" -CGO_LDFLAGS_DYN = "-lelf -lz -lbpf" - -# default == shared lib from OS package - -all: libbpfgo-dynamic -test: libbpfgo-dynamic-test - -# libbpfgo test object - -libbpfgo-test-bpf-static: libbpfgo-static # needed for serialization - $(MAKE) -C $(SELFTEST)/build - -libbpfgo-test-bpf-dynamic: libbpfgo-dynamic # needed for serialization - $(MAKE) -C $(SELFTEST)/build - -libbpfgo-test-bpf-clean: - $(MAKE) -C $(SELFTEST)/build clean - -# libbpf: shared - -libbpfgo-dynamic: $(OUTPUT)/libbpf - CC=$(CLANG) \ - CGO_CFLAGS=$(CGO_CFLAGS_DYN) \ - CGO_LDFLAGS=$(CGO_LDFLAGS_DYN) \ - go build . - -libbpfgo-dynamic-test: libbpfgo-test-bpf-dynamic - CC=$(CLANG) \ - CGO_CFLAGS=$(CGO_CFLAGS_DYN) \ - CGO_LDFLAGS=$(CGO_LDFLAGS_DYN) \ - sudo -E go test . - -# libbpf: static - -libbpfgo-static: $(VMLINUXH) | $(LIBBPF_OBJ) - CC=$(CLANG) \ - CGO_CFLAGS=$(CGO_CFLAGS_STATIC) \ - CGO_LDFLAGS=$(CGO_LDFLAGS_STATIC) \ - GOOS=linux GOARCH=$(ARCH) \ - go build \ - -tags netgo -ldflags $(CGO_EXTLDFLAGS_STATIC) \ - . - -libbpfgo-static-test: libbpfgo-test-bpf-static - CC=$(CLANG) \ - CGO_CFLAGS=$(CGO_CFLAGS_STATIC) \ - CGO_LDFLAGS=$(CGO_LDFLAGS_STATIC) \ - GOOS=linux GOARCH=$(ARCH) \ - sudo -E -- go test \ - -tags netgo -ldflags $(CGO_EXTLDFLAGS_STATIC) \ - . - -# vmlinux header file - -.PHONY: vmlinuxh -vmlinuxh: $(VMLINUXH) - -$(VMLINUXH): $(OUTPUT) - @if [ ! -f $(BTFFILE) ]; then \ - echo "ERROR: kernel does not seem to support BTF"; \ - exit 1; \ - fi - @if [ ! -f $(VMLINUXH) ]; then \ - echo "INFO: generating $(VMLINUXH) from $(BTFFILE)"; \ - $(BPFTOOL) btf dump file $(BTFFILE) format c > $(VMLINUXH); \ - fi - -# static libbpf generation for the git submodule - -.PHONY: libbpf-static -libbpf-static: $(LIBBPF_OBJ) - -$(LIBBPF_OBJ): $(LIBBPF_SRC) $(wildcard $(LIBBPF_SRC)/*.[ch]) | $(OUTPUT)/libbpf - CC="$(CC)" CFLAGS="$(CFLAGS)" LD_FLAGS="$(LDFLAGS)" \ - $(MAKE) -C $(LIBBPF_SRC) \ - BUILD_STATIC_ONLY=1 \ - OBJDIR=$(LIBBPF_OBJDIR) \ - DESTDIR=$(LIBBPF_DESTDIR) \ - INCLUDEDIR= LIBDIR= UAPIDIR= install - -$(LIBBPF_SRC): -ifeq ($(wildcard $@), ) - echo "INFO: updating submodule 'libbpf'" - $(GIT) submodule update --init --recursive -endif - -# selftests - -SELFTESTS = $(shell find $(SELFTEST) -mindepth 1 -maxdepth 1 -type d ! -name 'common' ! -name 'build') - -define FOREACH - SELFTESTERR=0; \ - for DIR in $(SELFTESTS); do \ - echo "INFO: entering $$DIR..."; \ - $(MAKE) -j8 -C $$DIR $(1) || SELFTESTERR=1; \ - done; \ - if [ $$SELFTESTERR -eq 1 ]; then \ - exit 1; \ - fi -endef - -.PHONY: selftest -.PHONY: selftest-static -.PHONY: selftest-dynamic -.PHONY: selftest-run -.PHONY: selftest-static-run -.PHONY: selftest-dynamic-run -.PHONY: selftest-clean - -selftest: selftest-static - -selftest-static: - $(call FOREACH, main-static) -selftest-dynamic: - $(call FOREACH, main-dynamic) - -selftest-run: selftest-static-run - -selftest-static-run: - $(call FOREACH, run-static) -selftest-dynamic-run: - $(call FOREACH, run-dynamic) - -selftest-clean: - $(call FOREACH, clean) - -# output - -$(OUTPUT): - mkdir -p $(OUTPUT) - -$(OUTPUT)/libbpf: - mkdir -p $(OUTPUT)/libbpf - -# cleanup - -clean: selftest-clean libbpfgo-test-bpf-clean - rm -rf $(OUTPUT) +.PHONY: clean +clean: + rm $(TARGET_BPF) $(VMLINUX_H) diff --git a/vendor/github.com/aquasecurity/libbpfgo/Readme.md b/vendor/github.com/aquasecurity/libbpfgo/Readme.md index 8d6df5f90c..068d2020c6 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/Readme.md +++ b/vendor/github.com/aquasecurity/libbpfgo/Readme.md @@ -2,67 +2,18 @@ ----- +___ -* [Installing](#installing) -* [Building](#building) -* [Concepts](#concepts) -* [Example](#example) -* [Releases](#releases) -* [Learn more](#learn-more) +libbpfgo is a Go library for working with Linux's [eBPF](https://ebpf.io/). It was created for [Tracee](https://github.com/aquasecurity/tracee), our open source Runtime Security and eBPF tracing tools written in Go. If you are interested in eBPF and it's applications, check out Tracee on Github: [https://github.com/aquasecurity/tracee](https://github.com/aquasecurity/tracee). - -libbpfgo is a Go library for Linux's [eBPF](https://ebpf.io/) project. It was created for [Tracee](https://github.com/aquasecurity/tracee), our open source Runtime Security, and eBPF tracing tool, written in Go. If you are interested in eBPF and its applications, check out Tracee at Github: [https://github.com/aquasecurity/tracee](https://github.com/aquasecurity/tracee). - -libbpfgo is built around [libbpf](https://github.com/libbpf/libbpf) - the standard library for interacting with eBPF programs from userspace - which is a C library maintained in Linux upstream. We have created libbpfgo as a thin Go wrapper around the libbpf project. +libbpfgo is built around libbpf - the standard library for interacting with eBPF from userspace, which is a C library maintained in Linux upstream. We have created libbpfgo as a thin Go wrapper around libbpf. ## Installing -libbpfgo uses CGO to interop with libbpf and will expect to be linked with libbpf at run or link time. Simply importing libbpfgo is not enough to get started, and you will need to fulfill the required dependency in one of the following ways: - -1. Install libbpf as a shared object in the system. Libbpf may already be packaged for your distribution and, if not, you can build and install from source. More info [here](https://github.com/libbpf/libbpf). -1. Embed libbpf into your Go project as a vendored dependency. This means that the libbpf code is statically linked into the resulting binary, and there are no runtime dependencies. [Tracee](https://github.com/aquasecurity/tracee) takes this approach. - -## Building - -Currently you will find the following GNU Makefile rules: - -| Makefile Rule | Description | -|--------------------------|-----------------------------------| -| all | builds libbpfgo (dynamic) | -| clean | cleans entire tree | -| selftest | builds all selftests (static) | -| selftest-run | runs all selftests (static) | - -* libbpf dynamically linked (libbpf from OS) - -| Makefile Rule | Description | -|--------------------------|-----------------------------------| -| libbpfgo-dynamic | builds dynamic libbpfgo (libbpf) | -| libbpfgo-dynamic-test | 'go test' with dynamic libbpfgo | -| selftest-dynamic | build tests with dynamic libbpfgo | -| selftest-dynamic-run | run tests using dynamic libbpfgo | - -* statically compiled (libbpf submodule) +libbpfgo is using CGO to interop with libbpf and will expect to be linked with libbpf at run or link time. Simply importing libbpfgo is not enough to get started, and you will need to fulfill the required dependency in one of the following ways: -| Makefile Rule | Description | -|--------------------------|-----------------------------------| -| libbpfgo-static | builds static libbpfgo (libbpf) | -| libbpfgo-static-test | 'go test' with static libbpfgo | -| selftest-static | build tests with static libbpfgo | -| selftest-static-run | run tests using static libbpfgo | - -* examples - -``` -$ make libbpfgo-static => libbpfgo statically linked with libbpf -$ make -C selftest/perfbuffers => single selftest build (static libbpf) -$ make -C selftest/perfbuffers run-dynamic => single selftest run (dynamic libbpf) -$ make selftest-static-run => will build & run all static selftests -``` - -> Note 01: dynamic builds need your OS to have a *recent enough* libbpf package (and its headers) installed. Sometimes, recent features might require the use of backported OS packages in order for your OS to contain latest *libbpf* features (sometimes required by libbpfgo). -> Note 02: static builds need `git submodule init` first. Make sure to sync the *libbpf* git submodule before trying to statically compile or test the *libbpfgo* repository. +1. Install the libbpf as a shared object in the system. Libbpf may already be packaged for you distribution, if not, you can build and install from source. More info [here](https://github.com/libbpf/libbpf). +1. Embed libbpf into your Go project as a vendored dependency. This means that the libbpf code is statically linked into the resulting binary, and there are no runtime dependencies. [Tracee](https://github.com/aquasecurity/tracee) takes this approach and you can take example from it's [Makefile](https://github.com/aquasecurity/tracee/blob/f8df7da6a27f729610992b6bd52e89d510fcf384/tracee-ebpf/Makefile#L62). ## Concepts @@ -96,23 +47,15 @@ rb.Start() e := <-eventsChannel ``` -## Releases - -libbpfgo does not yet have a regular schedule for cutting releases. There has not yet been a major release but API backwards compatibility will be maintained for all releases with the same major release number. Milestones are created when preparing for release. +Please check our github milestones for an idea of the project roadmap. The general goal is to fully implement/expose libbpf's API in Go as seamlessly as possible. -- __Major releases__ are cut when backwards compatibility is broken or major milestones are completed, such as reaching parity with libbpf's API. -- __Minor releases__ are cut to incorporate new support for libbpf APIs. -- __Patch releases__ are cut to incorporate important individual or groupings of bug fixes. -- __libbpf support numbering__ indicates the _minimum_ required libbpf version that must be linked in order to ensure libbpfgo compatibility. For example, `v0.2.1-libbpf-0.4.0` means that version 0.2.1 of libbpfgo requires v0.4.0 or newer of libbpf. -*Note*: some distributions might have local changes to their libbpf package and their version might include backports and/or fixes differently than upstream versions. In those cases we recommend that libbpfgo is used statically compiled. +## Learn more +- Blost post on [how to Build eBPF Programs with libbpfgo](https://blog.aquasec.com/libbpf-ebpf-programs) -## Learn more +- The [selftests](./selftest) are small programs that use libbpfgo to verify functionality, they're good examples to look at for usage. -Please check our github milestones for an idea of the project roadmap. The general goal is to fully implement/expose libbpf's API in Go as seamlessly as possible. +- [tracee-ebpf](https://github.com/aquasecurity/tracee/tree/main/tracee-ebpf) is a robust consumer of this package. -- [How to Build eBPF Programs with libbpfgo](https://blog.aquasec.com/libbpf-ebpf-programs). -- [selftests](./selftest) are small program using libbpfgo and might be good usage examples. -- [tracee-ebpf](https://github.com/aquasecurity/tracee/tree/main/tracee-ebpf) is a robust consumer of this project. -- Feel free to ask questions by creating a new [Discussion](https://github.com/aquasecurity/libbpfgo/discussions), we'd love to help. +- Feel free to ask questions by creating a new [Discussion](https://github.com/aquasecurity/libbpfgo/discussions) and we'd love to help. diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go index ebd0e52063..1967297aed 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/argumentParsers.go @@ -95,7 +95,6 @@ func ParseMemProt(prot uint32) string { f = append(f, "PROT_EXEC") } } - return strings.Join(f, "|") } @@ -105,7 +104,7 @@ func ParseMemProt(prot uint32) string { func ParseOpenFlags(flags uint32) string { var f []string - // access mode + //access mode switch { case flags&01 == 01: f = append(f, "O_WRONLY") @@ -185,7 +184,6 @@ func ParseAccessMode(mode uint32) string { f = append(f, "X_OK") } } - return strings.Join(f, "|") } @@ -202,7 +200,6 @@ func ParseExecFlags(flags uint32) string { if len(f) == 0 { f = append(f, "0") } - return strings.Join(f, "|") } @@ -285,7 +282,6 @@ func ParseCloneFlags(flags uint64) string { if len(f) == 0 { f = append(f, "0") } - return strings.Join(f, "|") } @@ -301,9 +297,7 @@ func ParseSocketType(st uint32) string { 6: "SOCK_DCCP", 10: "SOCK_PACKET", } - var f []string - if stName, ok := socketTypes[st&0xf]; ok { f = append(f, stName) } else { @@ -315,7 +309,6 @@ func ParseSocketType(st uint32) string { if st&002000000 == 002000000 { f = append(f, "SOCK_CLOEXEC") } - return strings.Join(f, "|") } @@ -369,15 +362,12 @@ func ParseSocketDomain(sd uint32) string { 43: "AF_SMC", 44: "AF_XDP", } - var res string - if sdName, ok := socketDomains[sd]; ok { res = sdName } else { res = strconv.Itoa(int(sd)) } - return res } @@ -385,23 +375,19 @@ func ParseSocketDomain(sd uint32) string { func ParseUint32IP(in uint32) string { ip := make(net.IP, net.IPv4len) binary.BigEndian.PutUint32(ip, in) - return ip.String() } -// Parse16BytesSliceIP parses the IP address encoded as 16 bytes long -// PrintBytesSliceIP. It would be more correct to accept a [16]byte instead of -// variable lenth slice, but that would case unnecessary memory copying and -// type conversions. +// Parse16BytesSliceIP parses the IP address encoded as 16 bytes long PrintBytesSliceIP +// It would be more correct to accept a [16]byte instead of variable lenth slice, but that would case unnecessary memory copying and type conversions func Parse16BytesSliceIP(in []byte) string { ip := net.IP(in) - return ip.String() } -// ParseCapability parses the `capability` bitmask argument of the -// `cap_capable` function include/uapi/linux/capability.h -func ParseCapability(c int32) string { +// ParseCapability parses the `capability` bitmask argument of the `cap_capable` function +// include/uapi/linux/capability.h +func ParseCapability(cap int32) string { var capabilities = map[int32]string{ 0: "CAP_CHOWN", 1: "CAP_DAC_OVERRIDE", @@ -442,15 +428,12 @@ func ParseCapability(c int32) string { 36: "CAP_BLOCK_SUSPEND", 37: "CAP_AUDIT_READ", } - var res string - - if capName, ok := capabilities[c]; ok { + if capName, ok := capabilities[cap]; ok { res = capName } else { - res = strconv.Itoa(int(c)) + res = strconv.Itoa(int(cap)) } - return res } @@ -518,7 +501,6 @@ func ParsePrctlOption(op int32) string { } else { res = strconv.Itoa(int(op)) } - return res } @@ -567,7 +549,6 @@ func ParsePtraceRequest(req int64) string { } else { res = strconv.Itoa(int(req)) } - return res } @@ -618,6 +599,5 @@ func ParseBPFCmd(cmd int32) string { } else { res = strconv.Itoa(int(cmd)) } - return res } diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/common.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/common.go deleted file mode 100644 index a6db47b544..0000000000 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/common.go +++ /dev/null @@ -1,75 +0,0 @@ -package helpers - -import ( - "fmt" - "os" - "strconv" - "strings" - "syscall" -) - -func checkEnvPath(env string) (string, error) { - filePath, _ := os.LookupEnv(env) - if filePath != "" { - _, err := os.Stat(filePath) - if err != nil { - return "", fmt.Errorf("could not open %s %s", env, filePath) - } - return filePath, nil - } - return "", nil -} - -// UnameRelease gets the version string of the current running kernel -func UnameRelease() (string, error) { - var uname syscall.Utsname - if err := syscall.Uname(&uname); err != nil { - return "", fmt.Errorf("could not get utsname") - } - - var buf [65]byte - for i, b := range uname.Release { - buf[i] = byte(b) - } - - ver := string(buf[:]) - ver = strings.Trim(ver, "\x00") - - return ver, nil -} - -// CompareKernelRelease will compare two given kernel version/release -// strings and return -1, 0 or 1 if given version is less, equal or bigger, -// respectively, than the given one -// -// Examples of $(uname -r): -// -// 5.11.0-31-generic (ubuntu) -// 4.18.0-305.12.1.el8_4.x86_64 (alma) -// 4.18.0-338.el8.x86_64 (stream8) -// 4.18.0-305.7.1.el8_4.centos.x86_64 (centos) -// 4.18.0-305.7.1.el8_4.centos.plus.x86_64 (centos + plus repo) -// 5.13.13-arch1-1 (archlinux) -// -func CompareKernelRelease(base, given string) int { - b := strings.Split(base, "-") // [base]-xxx - b = strings.Split(b[0], ".") // [major][minor][patch] - - g := strings.Split(given, "-") - g = strings.Split(g[0], ".") - - for n := 0; n <= 2; n++ { - i, _ := strconv.Atoi(g[n]) - j, _ := strconv.Atoi(b[n]) - - if i > j { - return 1 // given is bigger - } else if i < j { - return -1 // given is less - } else { - continue // equal - } - } - - return 0 // equal -} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go index 09e25f7607..bfe2b0e0b9 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/elf.go @@ -9,14 +9,15 @@ import ( // SymbolToOffset attempts to resolve a 'symbol' name in the binary found at // 'path' to an offset. The offset can be used for attaching a u(ret)probe func SymbolToOffset(path, symbol string) (uint32, error) { + f, err := elf.Open(path) if err != nil { - return 0, fmt.Errorf("could not open elf file to resolve symbol offset: %w", err) + return 0, fmt.Errorf("could not open elf file to resolve symbol offset: %v", err) } syms, err := f.Symbols() if err != nil { - return 0, fmt.Errorf("could not open symbol section to resolve symbol offset: %w", err) + return 0, fmt.Errorf("could not open symbol section to resolve symbol offset: %v", err) } sectionsToSearchForSymbol := []*elf.Section{} @@ -28,14 +29,14 @@ func SymbolToOffset(path, symbol string) (uint32, error) { } var executableSection *elf.Section + for i := range syms { + if syms[i].Name == symbol { - for j := range syms { - if syms[j].Name == symbol { // Find what section the symbol is in by checking the executable section's // addr space. for m := range sectionsToSearchForSymbol { - if syms[j].Value > sectionsToSearchForSymbol[m].Addr && - syms[j].Value < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size { + if syms[i].Value > sectionsToSearchForSymbol[m].Addr && + syms[i].Value < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size { executableSection = sectionsToSearchForSymbol[m] } } @@ -44,9 +45,8 @@ func SymbolToOffset(path, symbol string) (uint32, error) { return 0, errors.New("could not find symbol in executable sections of binary") } - return uint32(syms[j].Value - executableSection.Addr + executableSection.Offset), nil + return uint32(syms[i].Value - executableSection.Addr + executableSection.Offset), nil } } - return 0, fmt.Errorf("symbol not found") } diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go deleted file mode 100644 index c93eda51e5..0000000000 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_config.go +++ /dev/null @@ -1,399 +0,0 @@ -package helpers - -import ( - "bufio" - "compress/gzip" - "fmt" - "io" - "os" - "path/filepath" - "strings" -) - -// KernelConfigOption is an abstraction of the key in key=value syntax of the kernel config file -type KernelConfigOption uint32 - -// KernelConfigOptionValue is an abstraction of the value in key=value syntax of kernel config file -type KernelConfigOptionValue uint8 - -const ( - UNDEFINED KernelConfigOptionValue = iota - BUILTIN - MODULE - STRING - ANY -) - -func (k KernelConfigOption) String() string { - return kernelConfigKeyIDToString[k] -} - -func (k KernelConfigOptionValue) String() string { - switch k { - case UNDEFINED: - return "UNDEFINED" - case BUILTIN: - return "BUILTIN" - case MODULE: - return "MODULE" - case STRING: - return "STRING" - case ANY: - return "ANY" - } - - return "" -} - -// These constants are a limited number of the total kernel config options, -// but are provided because they are most relevant for BPF development. - -const ( - CONFIG_BPF KernelConfigOption = iota + 1 - CONFIG_BPF_SYSCALL - CONFIG_HAVE_EBPF_JIT - CONFIG_BPF_JIT - CONFIG_BPF_JIT_ALWAYS_ON - CONFIG_CGROUPS - CONFIG_CGROUP_BPF - CONFIG_CGROUP_NET_CLASSID - CONFIG_SOCK_CGROUP_DATA - CONFIG_BPF_EVENTS - CONFIG_KPROBE_EVENTS - CONFIG_UPROBE_EVENTS - CONFIG_TRACING - CONFIG_FTRACE_SYSCALLS - CONFIG_FUNCTION_ERROR_INJECTION - CONFIG_BPF_KPROBE_OVERRIDE - CONFIG_NET - CONFIG_XDP_SOCKETS - CONFIG_LWTUNNEL_BPF - CONFIG_NET_ACT_BPF - CONFIG_NET_CLS_BPF - CONFIG_NET_CLS_ACT - CONFIG_NET_SCH_INGRESS - CONFIG_XFRM - CONFIG_IP_ROUTE_CLASSID - CONFIG_IPV6_SEG6_BPF - CONFIG_BPF_LIRC_MODE2 - CONFIG_BPF_STREAM_PARSER - CONFIG_NETFILTER_XT_MATCH_BPF - CONFIG_BPFILTER - CONFIG_BPFILTER_UMH - CONFIG_TEST_BPF - CONFIG_HZ - CONFIG_DEBUG_INFO_BTF - CONFIG_DEBUG_INFO_BTF_MODULES - CONFIG_BPF_LSM - CONFIG_BPF_PRELOAD - CONFIG_BPF_PRELOAD_UMD - CUSTOM_OPTION_START KernelConfigOption = 1000 -) - -var kernelConfigKeyStringToID = map[string]KernelConfigOption{ - "CONFIG_BPF": CONFIG_BPF, - "CONFIG_BPF_SYSCALL": CONFIG_BPF_SYSCALL, - "CONFIG_HAVE_EBPF_JIT": CONFIG_HAVE_EBPF_JIT, - "CONFIG_BPF_JIT": CONFIG_BPF_JIT, - "CONFIG_BPF_JIT_ALWAYS_ON": CONFIG_BPF_JIT_ALWAYS_ON, - "CONFIG_CGROUPS": CONFIG_CGROUPS, - "CONFIG_CGROUP_BPF": CONFIG_CGROUP_BPF, - "CONFIG_CGROUP_NET_CLASSID": CONFIG_CGROUP_NET_CLASSID, - "CONFIG_SOCK_CGROUP_DATA": CONFIG_SOCK_CGROUP_DATA, - "CONFIG_BPF_EVENTS": CONFIG_BPF_EVENTS, - "CONFIG_KPROBE_EVENTS": CONFIG_KPROBE_EVENTS, - "CONFIG_UPROBE_EVENTS": CONFIG_UPROBE_EVENTS, - "CONFIG_TRACING": CONFIG_TRACING, - "CONFIG_FTRACE_SYSCALLS": CONFIG_FTRACE_SYSCALLS, - "CONFIG_FUNCTION_ERROR_INJECTION": CONFIG_FUNCTION_ERROR_INJECTION, - "CONFIG_BPF_KPROBE_OVERRIDE": CONFIG_BPF_KPROBE_OVERRIDE, - "CONFIG_NET": CONFIG_NET, - "CONFIG_XDP_SOCKETS": CONFIG_XDP_SOCKETS, - "CONFIG_LWTUNNEL_BPF": CONFIG_LWTUNNEL_BPF, - "CONFIG_NET_ACT_BPF": CONFIG_NET_ACT_BPF, - "CONFIG_NET_CLS_BPF": CONFIG_NET_CLS_BPF, - "CONFIG_NET_CLS_ACT": CONFIG_NET_CLS_ACT, - "CONFIG_NET_SCH_INGRESS": CONFIG_NET_SCH_INGRESS, - "CONFIG_XFRM": CONFIG_XFRM, - "CONFIG_IP_ROUTE_CLASSID": CONFIG_IP_ROUTE_CLASSID, - "CONFIG_IPV6_SEG6_BPF": CONFIG_IPV6_SEG6_BPF, - "CONFIG_BPF_LIRC_MODE2": CONFIG_BPF_LIRC_MODE2, - "CONFIG_BPF_STREAM_PARSER": CONFIG_BPF_STREAM_PARSER, - "CONFIG_NETFILTER_XT_MATCH_BPF": CONFIG_NETFILTER_XT_MATCH_BPF, - "CONFIG_BPFILTER": CONFIG_BPFILTER, - "CONFIG_BPFILTER_UMH": CONFIG_BPFILTER_UMH, - "CONFIG_TEST_BPF": CONFIG_TEST_BPF, - "CONFIG_HZ": CONFIG_HZ, - "CONFIG_DEBUG_INFO_BTF": CONFIG_DEBUG_INFO_BTF, - "CONFIG_DEBUG_INFO_BTF_MODULES": CONFIG_DEBUG_INFO_BTF_MODULES, - "CONFIG_BPF_LSM": CONFIG_BPF_LSM, - "CONFIG_BPF_PRELOAD": CONFIG_BPF_PRELOAD, - "CONFIG_BPF_PRELOAD_UMD": CONFIG_BPF_PRELOAD_UMD, -} - -var kernelConfigKeyIDToString = map[KernelConfigOption]string{ - CONFIG_BPF: "CONFIG_BPF", - CONFIG_BPF_SYSCALL: "CONFIG_BPF_SYSCALL", - CONFIG_HAVE_EBPF_JIT: "CONFIG_HAVE_EBPF_JIT", - CONFIG_BPF_JIT: "CONFIG_BPF_JIT", - CONFIG_BPF_JIT_ALWAYS_ON: "CONFIG_BPF_JIT_ALWAYS_ON", - CONFIG_CGROUPS: "CONFIG_CGROUPS", - CONFIG_CGROUP_BPF: "CONFIG_CGROUP_BPF", - CONFIG_CGROUP_NET_CLASSID: "CONFIG_CGROUP_NET_CLASSID", - CONFIG_SOCK_CGROUP_DATA: "CONFIG_SOCK_CGROUP_DATA", - CONFIG_BPF_EVENTS: "CONFIG_BPF_EVENTS", - CONFIG_KPROBE_EVENTS: "CONFIG_KPROBE_EVENTS", - CONFIG_UPROBE_EVENTS: "CONFIG_UPROBE_EVENTS", - CONFIG_TRACING: "CONFIG_TRACING", - CONFIG_FTRACE_SYSCALLS: "CONFIG_FTRACE_SYSCALLS", - CONFIG_FUNCTION_ERROR_INJECTION: "CONFIG_FUNCTION_ERROR_INJECTION", - CONFIG_BPF_KPROBE_OVERRIDE: "CONFIG_BPF_KPROBE_OVERRIDE", - CONFIG_NET: "CONFIG_NET", - CONFIG_XDP_SOCKETS: "CONFIG_XDP_SOCKETS", - CONFIG_LWTUNNEL_BPF: "CONFIG_LWTUNNEL_BPF", - CONFIG_NET_ACT_BPF: "CONFIG_NET_ACT_BPF", - CONFIG_NET_CLS_BPF: "CONFIG_NET_CLS_BPF", - CONFIG_NET_CLS_ACT: "CONFIG_NET_CLS_ACT", - CONFIG_NET_SCH_INGRESS: "CONFIG_NET_SCH_INGRESS", - CONFIG_XFRM: "CONFIG_XFRM", - CONFIG_IP_ROUTE_CLASSID: "CONFIG_IP_ROUTE_CLASSID", - CONFIG_IPV6_SEG6_BPF: "CONFIG_IPV6_SEG6_BPF", - CONFIG_BPF_LIRC_MODE2: "CONFIG_BPF_LIRC_MODE2", - CONFIG_BPF_STREAM_PARSER: "CONFIG_BPF_STREAM_PARSER", - CONFIG_NETFILTER_XT_MATCH_BPF: "CONFIG_NETFILTER_XT_MATCH_BPF", - CONFIG_BPFILTER: "CONFIG_BPFILTER", - CONFIG_BPFILTER_UMH: "CONFIG_BPFILTER_UMH", - CONFIG_TEST_BPF: "CONFIG_TEST_BPF", - CONFIG_HZ: "CONFIG_HZ", - CONFIG_DEBUG_INFO_BTF: "CONFIG_DEBUG_INFO_BTF", - CONFIG_DEBUG_INFO_BTF_MODULES: "CONFIG_DEBUG_INFO_BTF_MODULES", - CONFIG_BPF_LSM: "CONFIG_BPF_LSM", - CONFIG_BPF_PRELOAD: "CONFIG_BPF_PRELOAD", - CONFIG_BPF_PRELOAD_UMD: "CONFIG_BPF_PRELOAD_UMD", -} - -// KernelConfig is a set of kernel configuration options (currently for running OS only) -type KernelConfig struct { - configs map[KernelConfigOption]interface{} // predominantly KernelConfigOptionValue, sometimes string - needed map[KernelConfigOption]interface{} - kConfigFilePath string -} - -// InitKernelConfig inits external KernelConfig object -func InitKernelConfig() (*KernelConfig, error) { - config := KernelConfig{} - - // special case: user provided kconfig file (it MUST exist) - - osKConfigFilePath, err := checkEnvPath("LIBBPFGO_KCONFIG_FILE") // override /proc/config.gz or /boot/config-$(uname -r) if needed (containers) - if err != nil { - return &config, err - } - if len(osKConfigFilePath) > 2 { - if _, err := os.Stat(osKConfigFilePath); err != nil { - return &config, err - } - config.kConfigFilePath = osKConfigFilePath - if err := config.initKernelConfig(osKConfigFilePath); err != nil { - return &config, err - } - - return &config, nil - } - - // fastpath: check config.gz in procfs first - - configGZ := "/proc/config.gz" - if _, err1 := os.Stat(configGZ); err1 == nil { - config.kConfigFilePath = configGZ - if err2 := config.initKernelConfig(configGZ); err2 != nil { - return &config, err2 - } - - return &config, nil - } // ignore if /proc/config.gz does not exist - - // slowerpath: /boot/$(uname -r) - - releaseVersion, err := UnameRelease() - if err != nil { - return &config, err - } - - releaseFilePath := fmt.Sprintf("/boot/config-%s", releaseVersion) - config.kConfigFilePath = releaseFilePath - err = config.initKernelConfig(releaseFilePath) - - return &config, err -} - -// GetKernelConfigFilePath gives the kconfig file chosen by InitKernelConfig during initialization -func (k *KernelConfig) GetKernelConfigFilePath() string { - return k.kConfigFilePath -} - -// AddCustomKernelConfig allows user to extend list of possible existing kconfigs to be parsed from kConfigFilePath -func (k *KernelConfig) AddCustomKernelConfig(key KernelConfigOption, value string) error { - if key < CUSTOM_OPTION_START { - return fmt.Errorf("KConfig key index must be bigger than %d (CUSTOM_OPTION_START)\n", CUSTOM_OPTION_START) - } - - // extend initial list of kconfig options: add other possible existing ones - kernelConfigKeyIDToString[key] = value - kernelConfigKeyStringToID[value] = key - - return nil -} - -// LoadKernelConfig will (re)read kconfig file (likely after AddCustomKernelConfig was called) -func (k *KernelConfig) LoadKernelConfig() error { - return k.initKernelConfig(k.kConfigFilePath) -} - -// initKernelConfig inits internal KernelConfig data by calling appropriate readConfigFromXXX function -func (k *KernelConfig) initKernelConfig(configFilePath string) error { - if _, err := os.Stat(configFilePath); err != nil { - return fmt.Errorf("could not read %v: %w", configFilePath, err) - } - - if strings.Compare(filepath.Ext(configFilePath), ".gz") == 0 { - return k.readConfigFromProcConfigGZ(configFilePath) - } - - return k.readConfigFromBootConfigRelease(configFilePath) // assume it is a txt file by default -} - -// readConfigFromBootConfigRelease prepares io.Reader (/boot/config-$(uname -r)) for readConfigFromScanner -func (k *KernelConfig) readConfigFromBootConfigRelease(filePath string) error { - file, _ := os.Open(filePath) // already checked - k.readConfigFromScanner(file) - file.Close() - - return nil -} - -// readConfigFromProcConfigGZ prepares gziped io.Reader (/proc/config.gz) for readConfigFromScanner -func (k *KernelConfig) readConfigFromProcConfigGZ(filePath string) error { - file, _ := os.Open(filePath) // already checked - zreader, _ := gzip.NewReader(file) - k.readConfigFromScanner(zreader) - zreader.Close() - file.Close() - - return nil -} - -// readConfigFromScanner reads all existing KernelConfigOption's and KernelConfigOptionValue's from given io.Reader -func (k *KernelConfig) readConfigFromScanner(reader io.Reader) { - - if k.configs == nil { - k.configs = make(map[KernelConfigOption]interface{}) - } - if k.needed == nil { - k.needed = make(map[KernelConfigOption]interface{}) - } - - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - kv := strings.Split(scanner.Text(), "=") - if len(kv) != 2 { - continue - } - - configKeyID := kernelConfigKeyStringToID[kv[0]] - if configKeyID == 0 { - continue - } - if strings.Compare(kv[1], "m") == 0 { - k.configs[configKeyID] = MODULE - } else if strings.Compare(kv[1], "y") == 0 { - k.configs[configKeyID] = BUILTIN - } else { - k.configs[configKeyID] = kv[1] - } - } -} - -// GetValue will return a KernelConfigOptionValue for a given KernelConfigOption when this is a BUILTIN or a MODULE -func (k *KernelConfig) GetValue(option KernelConfigOption) KernelConfigOptionValue { - value, ok := k.configs[KernelConfigOption(option)].(KernelConfigOptionValue) - if ok { - return value - } - - return UNDEFINED // not an error as the config option might not exist in kconfig file -} - -// GetValueString will return a KernelConfigOptionValue for a given KernelConfigOption when this is actually a string -func (k *KernelConfig) GetValueString(option KernelConfigOption) (string, error) { - value, ok := k.configs[option].(string) - if ok { - return value, nil - } - - return "", fmt.Errorf("given option's value (%s) is not a string", option) -} - -// Exists will return true if a given KernelConfigOption was found in provided KernelConfig -// and it will return false if the KernelConfigOption is not set (# XXXXX is not set) -// -// Examples: -// kernelConfig.Exists(helpers.CONFIG_BPF) -// kernelConfig.Exists(helpers.CONFIG_BPF_PRELOAD) -// kernelConfig.Exists(helpers.CONFIG_HZ) -// -func (k *KernelConfig) Exists(option KernelConfigOption) bool { - if _, ok := k.configs[option]; ok { - return true - } - - return false -} - -// ExistsValue will return true if a given KernelConfigOption was found in provided KernelConfig -// AND its value is the same as the one provided by KernelConfigOptionValue -func (k *KernelConfig) ExistsValue(option KernelConfigOption, value interface{}) bool { - if cfg, ok := k.configs[option]; ok { - switch cfg.(type) { - case KernelConfigOptionValue: - if value == ANY { - return true - } else if k.configs[option].(KernelConfigOptionValue) == value { - return true - } - case string: - if strings.Compare(k.configs[option].(string), value.(string)) == 0 { - return true - } - } - } - - return false -} - -// CheckMissing returns an array of KernelConfigOption's that were added to KernelConfig as needed but couldn't be -// found. It returns an empty array if nothing is missing. -func (k *KernelConfig) CheckMissing() []KernelConfigOption { - missing := make([]KernelConfigOption, 0) - - for key, value := range k.needed { - if !k.ExistsValue(key, value) { - missing = append(missing, key) - } - } - - return missing -} - -// AddNeeded adds a KernelConfigOption and its value, if needed, as required for further checks with CheckMissing -// -// Examples: -// kernelConfig.AddNeeded(helpers.CONFIG_BPF, helpers.ANY) -// kernelConfig.AddNeeded(helpers.CONFIG_BPF_PRELOAD, helpers.ANY) -// kernelConfig.AddNeeded(helpers.CONFIG_HZ, "250") -// -func (k *KernelConfig) AddNeeded(option KernelConfigOption, value interface{}) { - if _, ok := kernelConfigKeyIDToString[option]; ok { - k.needed[option] = value - } -} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go new file mode 100644 index 0000000000..f771160820 --- /dev/null +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/kernel_features.go @@ -0,0 +1,228 @@ +package helpers + +import ( + "bufio" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "os" + "strings" + + "golang.org/x/sys/unix" +) + +// These constants are a limited number of the total kernel config options, +// but are provided because they are most relevant for BPF +// development. +const ( + CONFIG_BPF uint32 = iota + 1 + CONFIG_BPF_SYSCALL + CONFIG_HAVE_EBPF_JIT + CONFIG_BPF_JIT + CONFIG_BPF_JIT_ALWAYS_ON + CONFIG_CGROUPS + CONFIG_CGROUP_BPF + CONFIG_CGROUP_NET_CLASSID + CONFIG_SOCK_CGROUP_DATA + CONFIG_BPF_EVENTS + CONFIG_KPROBE_EVENTS + CONFIG_UPROBE_EVENTS + CONFIG_TRACING + CONFIG_FTRACE_SYSCALLS + CONFIG_FUNCTION_ERROR_INJECTION + CONFIG_BPF_KPROBE_OVERRIDE + CONFIG_NET + CONFIG_XDP_SOCKETS + CONFIG_LWTUNNEL_BPF + CONFIG_NET_ACT_BPF + CONFIG_NET_CLS_BPF + CONFIG_NET_CLS_ACT + CONFIG_NET_SCH_INGRESS + CONFIG_XFRM + CONFIG_IP_ROUTE_CLASSID + CONFIG_IPV6_SEG6_BPF + CONFIG_BPF_LIRC_MODE2 + CONFIG_BPF_STREAM_PARSER + CONFIG_NETFILTER_XT_MATCH_BPF + CONFIG_BPFILTER + CONFIG_BPFILTER_UMH + CONFIG_TEST_BPF + CONFIG_HZ + CONFIG_DEBUG_INFO_BTF + CONFIG_DEBUG_INFO_BTF_MODULES + CONFIG_BPF_LSM + CONFIG_BPF_PRELOAD + CONFIG_BPF_PRELOAD_UMD +) + +var KernelConfigKeyStringToID map[string]uint32 = map[string]uint32{ + "CONFIG_BPF": CONFIG_BPF, + "CONFIG_BPF_SYSCALL": CONFIG_BPF_SYSCALL, + "CONFIG_HAVE_EBPF_JIT": CONFIG_HAVE_EBPF_JIT, + "CONFIG_BPF_JIT": CONFIG_BPF_JIT, + "CONFIG_BPF_JIT_ALWAYS_ON": CONFIG_BPF_JIT_ALWAYS_ON, + "CONFIG_CGROUPS": CONFIG_CGROUPS, + "CONFIG_CGROUP_BPF": CONFIG_CGROUP_BPF, + "CONFIG_CGROUP_NET_CLASSID": CONFIG_CGROUP_NET_CLASSID, + "CONFIG_SOCK_CGROUP_DATA": CONFIG_SOCK_CGROUP_DATA, + "CONFIG_BPF_EVENTS": CONFIG_BPF_EVENTS, + "CONFIG_KPROBE_EVENTS": CONFIG_KPROBE_EVENTS, + "CONFIG_UPROBE_EVENTS": CONFIG_UPROBE_EVENTS, + "CONFIG_TRACING": CONFIG_TRACING, + "CONFIG_FTRACE_SYSCALLS": CONFIG_FTRACE_SYSCALLS, + "CONFIG_FUNCTION_ERROR_INJECTION": CONFIG_FUNCTION_ERROR_INJECTION, + "CONFIG_BPF_KPROBE_OVERRIDE": CONFIG_BPF_KPROBE_OVERRIDE, + "CONFIG_NET": CONFIG_NET, + "CONFIG_XDP_SOCKETS": CONFIG_XDP_SOCKETS, + "CONFIG_LWTUNNEL_BPF": CONFIG_LWTUNNEL_BPF, + "CONFIG_NET_ACT_BPF": CONFIG_NET_ACT_BPF, + "CONFIG_NET_CLS_BPF": CONFIG_NET_CLS_BPF, + "CONFIG_NET_CLS_ACT": CONFIG_NET_CLS_ACT, + "CONFIG_NET_SCH_INGRESS": CONFIG_NET_SCH_INGRESS, + "CONFIG_XFRM": CONFIG_XFRM, + "CONFIG_IP_ROUTE_CLASSID": CONFIG_IP_ROUTE_CLASSID, + "CONFIG_IPV6_SEG6_BPF": CONFIG_IPV6_SEG6_BPF, + "CONFIG_BPF_LIRC_MODE2": CONFIG_BPF_LIRC_MODE2, + "CONFIG_BPF_STREAM_PARSER": CONFIG_BPF_STREAM_PARSER, + "CONFIG_NETFILTER_XT_MATCH_BPF": CONFIG_NETFILTER_XT_MATCH_BPF, + "CONFIG_BPFILTER": CONFIG_BPFILTER, + "CONFIG_BPFILTER_UMH": CONFIG_BPFILTER_UMH, + "CONFIG_TEST_BPF": CONFIG_TEST_BPF, + "CONFIG_HZ": CONFIG_HZ, + "CONFIG_DEBUG_INFO_BTF": CONFIG_DEBUG_INFO_BTF, + "CONFIG_DEBUG_INFO_BTF_MODULES": CONFIG_DEBUG_INFO_BTF_MODULES, + "CONFIG_BPF_LSM": CONFIG_BPF_LSM, + "CONFIG_BPF_PRELOAD": CONFIG_BPF_PRELOAD, + "CONFIG_BPF_PRELOAD_UMD": CONFIG_BPF_PRELOAD_UMD, +} + +var KernelConfigKeyIDToString map[uint32]string = map[uint32]string{ + CONFIG_BPF: "CONFIG_BPF", + CONFIG_BPF_SYSCALL: "CONFIG_BPF_SYSCALL", + CONFIG_HAVE_EBPF_JIT: "CONFIG_HAVE_EBPF_JIT", + CONFIG_BPF_JIT: "CONFIG_BPF_JIT", + CONFIG_BPF_JIT_ALWAYS_ON: "CONFIG_BPF_JIT_ALWAYS_ON", + CONFIG_CGROUPS: "CONFIG_CGROUPS", + CONFIG_CGROUP_BPF: "CONFIG_CGROUP_BPF", + CONFIG_CGROUP_NET_CLASSID: "CONFIG_CGROUP_NET_CLASSID", + CONFIG_SOCK_CGROUP_DATA: "CONFIG_SOCK_CGROUP_DATA", + CONFIG_BPF_EVENTS: "CONFIG_BPF_EVENTS", + CONFIG_KPROBE_EVENTS: "CONFIG_KPROBE_EVENTS", + CONFIG_UPROBE_EVENTS: "CONFIG_UPROBE_EVENTS", + CONFIG_TRACING: "CONFIG_TRACING", + CONFIG_FTRACE_SYSCALLS: "CONFIG_FTRACE_SYSCALLS", + CONFIG_FUNCTION_ERROR_INJECTION: "CONFIG_FUNCTION_ERROR_INJECTION", + CONFIG_BPF_KPROBE_OVERRIDE: "CONFIG_BPF_KPROBE_OVERRIDE", + CONFIG_NET: "CONFIG_NET", + CONFIG_XDP_SOCKETS: "CONFIG_XDP_SOCKETS", + CONFIG_LWTUNNEL_BPF: "CONFIG_LWTUNNEL_BPF", + CONFIG_NET_ACT_BPF: "CONFIG_NET_ACT_BPF", + CONFIG_NET_CLS_BPF: "CONFIG_NET_CLS_BPF", + CONFIG_NET_CLS_ACT: "CONFIG_NET_CLS_ACT", + CONFIG_NET_SCH_INGRESS: "CONFIG_NET_SCH_INGRESS", + CONFIG_XFRM: "CONFIG_XFRM", + CONFIG_IP_ROUTE_CLASSID: "CONFIG_IP_ROUTE_CLASSID", + CONFIG_IPV6_SEG6_BPF: "CONFIG_IPV6_SEG6_BPF", + CONFIG_BPF_LIRC_MODE2: "CONFIG_BPF_LIRC_MODE2", + CONFIG_BPF_STREAM_PARSER: "CONFIG_BPF_STREAM_PARSER", + CONFIG_NETFILTER_XT_MATCH_BPF: "CONFIG_NETFILTER_XT_MATCH_BPF", + CONFIG_BPFILTER: "CONFIG_BPFILTER", + CONFIG_BPFILTER_UMH: "CONFIG_BPFILTER_UMH", + CONFIG_TEST_BPF: "CONFIG_TEST_BPF", + CONFIG_HZ: "CONFIG_HZ", + CONFIG_DEBUG_INFO_BTF: "CONFIG_DEBUG_INFO_BTF", + CONFIG_DEBUG_INFO_BTF_MODULES: "CONFIG_DEBUG_INFO_BTF_MODULES", + CONFIG_BPF_LSM: "CONFIG_BPF_LSM", + CONFIG_BPF_PRELOAD: "CONFIG_BPF_PRELOAD", + CONFIG_BPF_PRELOAD_UMD: "CONFIG_BPF_PRELOAD_UMD", +} + +type KernelConfig map[uint32]string + +// InitKernelConfig populates the passed KernelConfig +// by attempting to read the kernel config into it from: +// /proc/config-$(uname -r) +// or +// /boot/config.gz +func (k KernelConfig) InitKernelConfig() error { + + x := unix.Utsname{} + err := unix.Uname(&x) + if err != nil { + return fmt.Errorf("could not determine uname release: %v", err) + } + + bootConfigPath := fmt.Sprintf("/boot/config-%s", bytes.Trim(x.Release[:], "\x00")) + + err = k.getBootConfigByPath(bootConfigPath) + if err == nil { + return nil + } + + err2 := k.getProcGZConfigByPath("/proc/config.gz") + if err != nil { + return fmt.Errorf("%v %v", err, err2) + } + + return nil +} + +// GetKernelConfigValue retrieves a value from the kernel config +// If the config value does not exist an error will be returned +func (k KernelConfig) GetKernelConfigValue(key uint32) (string, error) { + v, exists := k[key] + if !exists { + return "", errors.New("kernel config value does not exist, it's possible this option is not present in your kernel version or the KernelConfig has not been initialized") + } + return v, nil +} + +func (k KernelConfig) getBootConfigByPath(bootConfigPath string) error { + + configFile, err := os.Open(bootConfigPath) + if err != nil { + return fmt.Errorf("could not open %s: %v", bootConfigPath, err) + } + + k.readConfigFromScanner(configFile) + + return nil +} + +func (k KernelConfig) getProcGZConfigByPath(procConfigPath string) error { + + configFile, err := os.Open(procConfigPath) + if err != nil { + return fmt.Errorf("could not open %s: %v", procConfigPath, err) + } + + return k.getProcGZConfig(configFile) +} + +func (k KernelConfig) getProcGZConfig(reader io.Reader) error { + zreader, err := gzip.NewReader(reader) + if err != nil { + return err + } + + k.readConfigFromScanner(zreader) + return nil +} + +func (k KernelConfig) readConfigFromScanner(reader io.Reader) { + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + kv := strings.Split(scanner.Text(), "=") + if len(kv) != 2 { + continue + } + configKeyID := KernelConfigKeyStringToID[kv[0]] + if configKeyID == 0 { + continue + } + k[configKeyID] = kv[1] + } +} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go deleted file mode 100644 index 51ddeff0d7..0000000000 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/osinfo.go +++ /dev/null @@ -1,221 +0,0 @@ -package helpers - -import ( - "bufio" - "fmt" - "os" - "strings" -) - -type OSReleaseID uint32 - -func (o OSReleaseID) String() string { - return osReleaseIDToString[o] -} - -const ( - UBUNTU OSReleaseID = iota + 1 - FEDORA - ARCH - DEBIAN - CENTOS - STREAM - ALMA -) - -// stringToOSReleaseID is a map of supported distributions -var stringToOSReleaseID = map[string]OSReleaseID{ - "ubuntu": UBUNTU, - "fedora": FEDORA, - "arch": ARCH, - "debian": DEBIAN, - "centos": CENTOS, - "stream": STREAM, - "alma": ALMA, -} - -// osReleaseIDToString is a map of supported distributions -var osReleaseIDToString = map[OSReleaseID]string{ - UBUNTU: "ubuntu", - FEDORA: "fedora", - ARCH: "arch", - DEBIAN: "debian", - CENTOS: "centos", - STREAM: "stream", - ALMA: "alma", -} - -const ( - OS_NAME OSReleaseField = iota + 0 - OS_ID - OS_ID_LIKE - OS_PRETTY_NAME - OS_VARIANT - OS_VARIANT_ID - OS_VERSION - OS_VERSION_ID - OS_VERSION_CODENAME - OS_BUILD_ID - OS_IMAGE_ID - OS_IMAGE_VERSION - OS_KERNEL_RELEASE // not part of default os-release, but we can use it here to facilitate things -) - -type OSReleaseField uint32 - -func (o OSReleaseField) String() string { - return osReleaseFieldToString[o] -} - -// stringToOSReleaseField is a map of os-release file fields -var stringToOSReleaseField = map[string]OSReleaseField{ - "NAME": OS_NAME, - "ID": OS_ID, - "ID_LIKE": OS_ID_LIKE, - "PRETTY_NAME": OS_PRETTY_NAME, - "VARIANT": OS_VARIANT, - "VARIANT_ID": OS_VARIANT_ID, - "VERSION": OS_VERSION, - "VERSION_ID": OS_VERSION_ID, - "VERSION_CODENAME": OS_VERSION_CODENAME, - "BUILD_ID": OS_BUILD_ID, - "IMAGE_ID": OS_IMAGE_ID, - "IMAGE_VERSION": OS_IMAGE_VERSION, - "KERNEL_RELEASE": OS_KERNEL_RELEASE, -} - -// osReleaseFieldToString is a map of os-release file fields -var osReleaseFieldToString = map[OSReleaseField]string{ - OS_NAME: "NAME", - OS_ID: "ID", - OS_ID_LIKE: "ID_LIKE", - OS_PRETTY_NAME: "PRETTY_NAME", - OS_VARIANT: "VARIANT", - OS_VARIANT_ID: "VARIANT_ID", - OS_VERSION: "VERSION", - OS_VERSION_ID: "VERSION_ID", - OS_VERSION_CODENAME: "VERSION_CODENAME", - OS_BUILD_ID: "BUILD_ID", - OS_IMAGE_ID: "IMAGE_ID", - OS_IMAGE_VERSION: "IMAGE_VERSION", - OS_KERNEL_RELEASE: "KERNEL_RELEASE", -} - -// OSBTFEnabled checks if kernel has embedded BTF vmlinux file -func OSBTFEnabled() bool { - _, err := os.Stat("/sys/kernel/btf/vmlinux") // TODO: accept a KernelConfig param and check for CONFIG_DEBUG_INFO_BTF=y, or similar - - return err == nil -} - -// GetOSInfo creates a OSInfo object and runs discoverOSDistro() on its creation -func GetOSInfo() (*OSInfo, error) { - info := OSInfo{} - var err error - - if info.osReleaseFieldValues == nil { - info.osReleaseFieldValues = make(map[OSReleaseField]string) - } - - info.osReleaseFieldValues[OS_KERNEL_RELEASE], err = UnameRelease() - if err != nil { - return &info, fmt.Errorf("could not determine uname release: %w", err) - } - - info.osReleaseFilePath, err = checkEnvPath("LIBBPFGO_OSRELEASE_FILE") // useful if users wants to mount host os-release in a container - if err != nil { - return &info, err - } else if info.osReleaseFilePath == "" { - info.osReleaseFilePath = "/etc/os-release" - } - - if err = info.discoverOSDistro(); err != nil { - return &info, err - } - - return &info, nil -} - -// OSInfo object contains all OS relevant information -// -// OSRelease is relevant to examples such as: -// 1) OSInfo.OSReleaseInfo[helpers.OS_KERNEL_RELEASE]) => will provide $(uname -r) string -// 2) if OSInfo.GetReleaseID() == helpers.UBUNTU => {} will allow to run code in specific distribution -// -type OSInfo struct { - osReleaseFieldValues map[OSReleaseField]string - osReleaseID OSReleaseID - osReleaseFilePath string -} - -// GetOSReleaseFieldValue provides access to internal OSInfo OSReleaseField's -func (btfi *OSInfo) GetOSReleaseFieldValue(value OSReleaseField) string { - return btfi.osReleaseFieldValues[value] -} - -// GetOSReleaseFilePath provides the path for the used os-release file as it might -// not necessarily be /etc/os-release, depending on the environment variable -func (btfi *OSInfo) GetOSReleaseFilePath() string { - return btfi.osReleaseFilePath -} - -// GetOSReleaseFilePath provides the ID of current Linux distribution -func (btfi *OSInfo) GetOSReleaseID() OSReleaseID { - return btfi.osReleaseID -} - -// GetOSReleaseAllFieldValues allows user to dump, as strings, the existing OSReleaseField's and its values -func (btfi *OSInfo) GetOSReleaseAllFieldValues() map[OSReleaseField]string { - summary := make(map[OSReleaseField]string) - - for k, v := range btfi.osReleaseFieldValues { - summary[k] = v // create a copy so consumer can read internal data (e.g. debugging) - } - - return summary -} - -// CompareOSBaseKernelRelease will compare a given kernel version/release string -// to the current running version and return -1, 0 or 1 if given version is less, -// equal or bigger, respectively, than running one. Example: -// -// OSInfo.CompareOSBaseKernelRelease("5.11.0")) -// -func (btfi *OSInfo) CompareOSBaseKernelRelease(version string) int { - return CompareKernelRelease(btfi.osReleaseFieldValues[OS_KERNEL_RELEASE], version) -} - -// discoverOSDistro discover running Linux distribution information by reading UTS and -// the /etc/os-releases file (https://man7.org/linux/man-pages/man5/os-release.5.html) -func (btfi *OSInfo) discoverOSDistro() error { - var err error - - if btfi.osReleaseFilePath == "" { - return fmt.Errorf("should specify os-release filepath") - } - - file, err := os.Open(btfi.osReleaseFilePath) - if err != nil { - return err - } - - defer file.Close() - scanner := bufio.NewScanner(file) - - for scanner.Scan() { - val := strings.Split(scanner.Text(), "=") - if len(val) != 2 { - continue - } - keyID := stringToOSReleaseField[val[0]] - if keyID == 0 { // could not find KEY= from os-release in consts - continue - } - btfi.osReleaseFieldValues[keyID] = val[1] - if keyID == OS_ID { - btfi.osReleaseID = stringToOSReleaseID[strings.ToLower(val[1])] - } - } - - return nil -} diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go index c9b9f3ef45..3b2beab36d 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/rwArray.go @@ -39,7 +39,6 @@ func (a *RWArray) Put(v interface{}) int { if !a.slots[i].used { a.slots[i].value = v a.slots[i].used = true - return i } } diff --git a/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go b/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go index a74874a5d6..ebdab4ceb8 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go +++ b/vendor/github.com/aquasecurity/libbpfgo/helpers/tracelisten.go @@ -15,20 +15,19 @@ import ( func TracePipeListen() error { f, err := os.Open("/sys/kernel/debug/tracing/trace_pipe") if err != nil { - return fmt.Errorf("failed to open trace pipe: %w", err) + return fmt.Errorf("failed to open trace pipe: %v", err) } defer f.Close() r := bufio.NewReader(f) b := make([]byte, 1024) - for { - l, err := r.Read(b) + len, err := r.Read(b) if err != nil { - return fmt.Errorf("failed to read from trace pipe: %w", err) + return fmt.Errorf("failed to read from trace pipe: %v", err) } - s := string(b[:l]) + s := string(b[:len]) fmt.Println(s) } } diff --git a/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go b/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go index a8e02d37fb..d37d32a056 100644 --- a/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go +++ b/vendor/github.com/aquasecurity/libbpfgo/libbpfgo.go @@ -70,7 +70,7 @@ struct perf_buffer * init_perf_buf(int map_fd, int page_cnt, uintptr_t ctx) { pb_opts.lost_cb = perfLostCallback; pb_opts.ctx = (void*)ctx; pb = perf_buffer__new(map_fd, page_cnt, &pb_opts); - if (libbpf_get_error(pb)) { + if (pb < 0) { fprintf(stderr, "Failed to initialize perf buffer!\n"); return NULL; } @@ -197,6 +197,7 @@ import ( "fmt" "net" "path/filepath" + "strings" "sync" "syscall" "unsafe" @@ -251,22 +252,6 @@ type BPFLink struct { eventName string } -func (l *BPFLink) Detach() error { - ret := C.bpf_link__detach(l.link) - if ret < 0 { - return syscall.Errno(-ret) - } - return nil -} - -func (l *BPFLink) Destroy() error { - ret := C.bpf_link__destroy(l.link) - if ret < 0 { - return syscall.Errno(-ret) - } - return nil -} - type PerfBuffer struct { pb *C.struct_perf_buffer bpfMap *BPFMap @@ -311,49 +296,14 @@ func errptrError(ptr unsafe.Pointer, format string, args ...interface{}) error { return fmt.Errorf(format+": %v", args...) } -type NewModuleArgs struct { - KConfigFilePath string - BTFObjPath string - BPFObjName string - BPFObjPath string - BPFObjBuff []byte -} - -func NewModuleFromFile(bpfObjPath string) (*Module, error) { - - return NewModuleFromFileArgs(NewModuleArgs{ - BPFObjPath: bpfObjPath, - }) -} - -func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { +func NewModuleFromFile(bpfObjFile string) (*Module, error) { C.set_print_fn() - if err := bumpMemlockRlimit(); err != nil { - return nil, err - } - opts := C.struct_bpf_object_open_opts{} - opts.sz = C.sizeof_struct_bpf_object_open_opts - - bpfFile := C.CString(args.BPFObjPath) - defer C.free(unsafe.Pointer(bpfFile)) - - // instruct libbpf to use user provided kernel BTF file - if args.BTFObjPath != "" { - btfFile := C.CString(args.BTFObjPath) - opts.btf_custom_path = btfFile - defer C.free(unsafe.Pointer(btfFile)) - } - - // instruct libbpf to use user provided KConfigFile - if args.KConfigFilePath != "" { - kConfigFile := C.CString(args.KConfigFilePath) - opts.kconfig = kConfigFile - defer C.free(unsafe.Pointer(kConfigFile)) - } - - obj := C.bpf_object__open_file(bpfFile, &opts) + bumpMemlockRlimit() + cs := C.CString(bpfObjFile) + obj := C.bpf_object__open(cs) + C.free(unsafe.Pointer(cs)) if C.IS_ERR_OR_NULL(unsafe.Pointer(obj)) { - return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s", args.BPFObjPath) + return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s", bpfObjFile) } return &Module{ @@ -362,46 +312,18 @@ func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { } func NewModuleFromBuffer(bpfObjBuff []byte, bpfObjName string) (*Module, error) { - - return NewModuleFromBufferArgs(NewModuleArgs{ - BPFObjBuff: bpfObjBuff, - BPFObjName: bpfObjName, - }) -} - -func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) { C.set_print_fn() - if err := bumpMemlockRlimit(); err != nil { - return nil, err - } - if args.BTFObjPath == "" { - args.BTFObjPath = "/sys/kernel/btf/vmlinux" - } - btfFile := C.CString(args.BTFObjPath) - bpfName := C.CString(args.BPFObjName) - bpfBuff := unsafe.Pointer(C.CBytes(args.BPFObjBuff)) - bpfBuffSize := C.size_t(len(args.BPFObjBuff)) - - opts := C.struct_bpf_object_open_opts{} - opts.object_name = bpfName - opts.sz = C.sizeof_struct_bpf_object_open_opts - opts.btf_custom_path = btfFile // instruct libbpf to use user provided kernel BTF file - - if len(args.KConfigFilePath) > 2 { - kConfigFile := C.CString(args.KConfigFilePath) - opts.kconfig = kConfigFile // instruct libbpf to use user provided KConfigFile - defer C.free(unsafe.Pointer(kConfigFile)) - } - - obj := C.bpf_object__open_mem(bpfBuff, bpfBuffSize, &opts) + bumpMemlockRlimit() + name := C.CString(bpfObjName) + buffSize := C.size_t(len(bpfObjBuff)) + buffPtr := unsafe.Pointer(C.CBytes(bpfObjBuff)) + obj := C.bpf_object__open_buffer(buffPtr, buffSize, name) + C.free(unsafe.Pointer(name)) + C.free(unsafe.Pointer(buffPtr)) if C.IS_ERR_OR_NULL(unsafe.Pointer(obj)) { - return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s: %v", args.BPFObjName, args.BPFObjBuff[:20]) + return nil, errptrError(unsafe.Pointer(obj), "failed to open BPF object %s: %v", bpfObjName, bpfObjBuff[:20]) } - C.free(bpfBuff) - C.free(unsafe.Pointer(bpfName)) - C.free(unsafe.Pointer(btfFile)) - return &Module{ obj: obj, }, nil @@ -456,9 +378,11 @@ func (m *Module) GetMap(mapName string) (*BPFMap, error) { } func (b *BPFMap) Pin(pinPath string) error { + cs := C.CString(b.name) path := C.CString(pinPath) - errC := C.bpf_map__pin(b.bpfMap, path) - C.free(unsafe.Pointer(path)) + bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) + errC := C.bpf_map__pin(bpfMap, path) + C.free(unsafe.Pointer(cs)) if errC != 0 { return fmt.Errorf("failed to pin map %s to path %s", b.name, pinPath) } @@ -466,9 +390,11 @@ func (b *BPFMap) Pin(pinPath string) error { } func (b *BPFMap) Unpin(pinPath string) error { + cs := C.CString(b.name) path := C.CString(pinPath) - errC := C.bpf_map__unpin(b.bpfMap, path) - C.free(unsafe.Pointer(path)) + bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) + errC := C.bpf_map__unpin(bpfMap, path) + C.free(unsafe.Pointer(cs)) if errC != 0 { return fmt.Errorf("failed to unpin map %s from path %s", b.name, pinPath) } @@ -476,9 +402,11 @@ func (b *BPFMap) Unpin(pinPath string) error { } func (b *BPFMap) SetPinPath(pinPath string) error { + cs := C.CString(b.name) path := C.CString(pinPath) - errC := C.bpf_map__set_pin_path(b.bpfMap, path) - C.free(unsafe.Pointer(path)) + bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) + errC := C.bpf_map__set_pin_path(bpfMap, path) + C.free(unsafe.Pointer(cs)) if errC != 0 { return fmt.Errorf("failed to set pin for map %s to path %s", b.name, pinPath) } @@ -491,7 +419,10 @@ func (b *BPFMap) SetPinPath(pinPath string) error { // Note: for ring buffer and perf buffer, maxEntries is the // capacity in bytes. func (b *BPFMap) Resize(maxEntries uint32) error { - errC := C.bpf_map__set_max_entries(b.bpfMap, C.uint(maxEntries)) + cs := C.CString(b.name) + bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) + errC := C.bpf_map__resize(bpfMap, C.uint(maxEntries)) + C.free(unsafe.Pointer(cs)) if errC != 0 { return fmt.Errorf("failed to resize map %s to %v", b.name, maxEntries) } @@ -502,35 +433,13 @@ func (b *BPFMap) Resize(maxEntries uint32) error { // Note: for ring buffer and perf buffer, maxEntries is the // capacity in bytes. func (b *BPFMap) GetMaxEntries() uint32 { - maxEntries := C.bpf_map__max_entries(b.bpfMap) + cs := C.CString(b.name) + bpfMap := C.bpf_object__find_map_by_name(b.module.obj, cs) + maxEntries := C.bpf_map__max_entries(bpfMap) + C.free(unsafe.Pointer(cs)) return uint32(maxEntries) } -func (b *BPFMap) GetFd() int { - return int(b.fd) -} - -func (b *BPFMap) GetName() string { - return b.name -} - -func (b *BPFMap) GetModule() *Module { - return b.module -} - -func (b *BPFMap) GetPinPath() string { - pinPathGo := C.GoString(C.bpf_map__get_pin_path(b.bpfMap)) - return pinPathGo -} - -func (b *BPFMap) IsPinned() bool { - isPinned := C.bpf_map__is_pinned(b.bpfMap) - if isPinned == C.bool(true) { - return true - } - return false -} - func GetUnsafePointer(data interface{}) (unsafe.Pointer, error) { var dataPtr unsafe.Pointer switch k := data.(type) { @@ -690,16 +599,8 @@ func (m *Module) GetProgram(progName string) (*BPFProg, error) { }, nil } -func (p *BPFProg) GetFd() int { - return int(C.bpf_program__fd(p.prog)) -} - -func (p *BPFProg) GetModule() *Module { - return p.module -} - -func (p *BPFProg) GetName() string { - return p.name +func (p *BPFProg) GetFd() C.int { + return C.bpf_program__fd(p.prog) } // BPFProgType is an enum as defined in https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/bpf.h @@ -760,21 +661,25 @@ func (p *BPFProg) SetTracepoint() error { return nil } -func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { - tpCategory := C.CString(category) - tpName := C.CString(name) +func (p *BPFProg) AttachTracepoint(tp string) (*BPFLink, error) { + tpEvent := strings.Split(tp, ":") + if len(tpEvent) != 2 { + return nil, fmt.Errorf("tracepoint must be in 'category:name' format") + } + tpCategory := C.CString(tpEvent[0]) + tpName := C.CString(tpEvent[1]) link := C.bpf_program__attach_tracepoint(p.prog, tpCategory, tpName) C.free(unsafe.Pointer(tpCategory)) C.free(unsafe.Pointer(tpName)) if C.IS_ERR_OR_NULL(unsafe.Pointer(link)) { - return nil, errptrError(unsafe.Pointer(link), "failed to attach tracepoint %s to program %s", name, p.name) + return nil, errptrError(unsafe.Pointer(link), "failed to attach tracepoint %s to program %s", tp, p.name) } bpfLink := &BPFLink{ link: link, prog: p, linkType: Tracepoint, - eventName: name, + eventName: tp, } p.module.links = append(p.module.links, bpfLink) return bpfLink, nil @@ -896,7 +801,7 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse link := C.bpf_program__attach_uprobe(prog.prog, retCBool, pidCint, pathCString, offsetCsizet) C.free(unsafe.Pointer(pathCString)) if C.IS_ERR_OR_NULL(unsafe.Pointer(link)) { - return nil, errptrError(unsafe.Pointer(link), "failed to attach u(ret)probe to program %s:%d with pid %d, ", path, offset, pid) + return nil, errptrError(unsafe.Pointer(link), "failed to attach u(ret)probe to program %s:%d with pid %s, ", path, offset, pid) } upType := Uprobe @@ -908,7 +813,7 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse link: link, prog: prog, linkType: upType, - eventName: fmt.Sprintf("%s:%d:%d", path, pid, offset), + eventName: fmt.Sprintf("%s:%d:%s", path, pid, offset), } return bpfLink, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 997ad0ae97..28047986cf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0.0.20210928124427-df4987ad001c +# github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac ## explicit github.com/aquasecurity/libbpfgo github.com/aquasecurity/libbpfgo/helpers From 214ac2234755df5a7e0ef2d68ae766dbff04eca2 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 09:11:58 -0700 Subject: [PATCH 10/17] pkg/proc: be smarter about image selection --- pkg/proc/native/proc_linux.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 25e35085b3..81ceae9c48 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -746,7 +746,8 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf // 2. uretprobes seem to not restore the function return addr on the stack when removed, destroying any // kind of workaround we could come up with. // TODO(derekparker): this whole thing could likely be optimized a bit. - f, err := elf.Open(dbp.BinInfo().Images[0].Path) + img := dbp.BinInfo().PCToImage(fn.Entry) + f, err := elf.Open(img.Path) if err != nil { return fmt.Errorf("could not open elf file to resolve symbol offset: %w", err) } From a6f65bb6afd902971fca576b267456589dc2aab5 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 09:18:37 -0700 Subject: [PATCH 11/17] pkg/proc: revert changes to stackwatch --- pkg/proc/stackwatch.go | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/pkg/proc/stackwatch.go b/pkg/proc/stackwatch.go index 58e4d71e7a..2b33f70ff9 100644 --- a/pkg/proc/stackwatch.go +++ b/pkg/proc/stackwatch.go @@ -95,26 +95,11 @@ func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoi } // Stack Resize Sentinel - return t.setStackResizeSentinel(watchpoint, false, func(th Thread) bool { - adjustStackWatchpoint(t, th, watchpoint) - return false // we never want this breakpoint to be shown to the user - }) -} -func (t *Target) setStackResizeSentinel(watchpoint *Breakpoint, breakOnEntry bool, callback func(Thread) bool) error { fn := t.BinInfo().LookupFunc["runtime.copystack"] if fn == nil { return errors.New("could not find runtime.copystack") } - if breakOnEntry { - bp, err := t.SetBreakpoint(fn.Entry, StackResizeBreakpoint, nil) - if err != nil { - return err - } - bp.Name = "copystack-entry" - brklt := bp.Breaklets[len(bp.Breaklets)-1] - brklt.callback = callback - } text, err := Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End) if err != nil { return err @@ -131,15 +116,18 @@ func (t *Target) setStackResizeSentinel(watchpoint *Breakpoint, breakOnEntry boo if retpc == 0 { return errors.New("could not find return instruction in runtime.copystack") } - - rszbp, err := t.SetBreakpoint(retpc, StackResizeBreakpoint, nil) + rszbp, err := t.SetBreakpoint(retpc, StackResizeBreakpoint, sameGCond) if err != nil { return err } rszbreaklet := rszbp.Breaklets[len(rszbp.Breaklets)-1] rszbreaklet.watchpoint = watchpoint - rszbreaklet.callback = callback + rszbreaklet.callback = func(th Thread) bool { + adjustStackWatchpoint(t, th, watchpoint) + return false // we never want this breakpoint to be shown to the user + } + return nil } @@ -180,7 +168,7 @@ func watchpointOutOfScope(t *Target, watchpoint *Breakpoint) { // adjustStackWatchpoint is called when the goroutine of watchpoint resizes // its stack. It is used as a breaklet callback function. -// Its responsibility is to move the watchpoint to its new address. +// Its responsibility is to move the watchpoint to a its new address. func adjustStackWatchpoint(t *Target, th Thread, watchpoint *Breakpoint) { g, _ := GetG(th) if g == nil { From 7ae83d58f13c46f955744ece84a215fde3359e8f Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 09:25:19 -0700 Subject: [PATCH 12/17] pkg/proc: misc cleanups --- pkg/proc/breakpoints.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index e34fe5f40e..16019f2d5d 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -540,14 +540,7 @@ func (t *Target) SetEBPFTracepoint(fnName string) error { return err } - var addr uint64 - isret, _ := entry.Val(dwarf.AttrVarParam).(bool) - if isret { - addr = fn.End - } else { - addr = fn.Entry - } - origOffset, pieces, _, err := t.BinInfo().Location(entry, dwarf.AttrLocation, addr, op.DwarfRegisters{}, nil) + offset, pieces, _, err := t.BinInfo().Location(entry, dwarf.AttrLocation, fn.Entry, op.DwarfRegisters{}, nil) if err != nil { return err } @@ -557,7 +550,8 @@ func (t *Target) SetEBPFTracepoint(fnName string) error { paramPieces = append(paramPieces, int(piece.Val)) } } - offset := origOffset + int64(t.BinInfo().Arch.PtrSize()) + isret, _ := entry.Val(dwarf.AttrVarParam).(bool) + offset += int64(t.BinInfo().Arch.PtrSize()) args = append(args, ebpf.UProbeArgMap{ Offset: offset, Size: dt.Size(), From da184d21e2398f2a855d45f6506c3dabebc1b780 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 09:28:03 -0700 Subject: [PATCH 13/17] pkg/proc: more cleanup --- pkg/proc/internal/ebpf/helpers.go | 7 ------- pkg/proc/internal/ebpf/helpers_disabled.go | 22 ---------------------- 2 files changed, 29 deletions(-) diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go index 47c1bfc5ba..a9249fde0d 100644 --- a/pkg/proc/internal/ebpf/helpers.go +++ b/pkg/proc/internal/ebpf/helpers.go @@ -26,13 +26,6 @@ var TraceProbeBytes []byte const FakeAddressBase = 0xbeed000000000000 -type Uretprobe struct { - Link *bpf.BPFLink - Pid int - Path string - Offset uint32 -} - type EBPFContext struct { bpfModule *bpf.Module bpfProg *bpf.BPFProg diff --git a/pkg/proc/internal/ebpf/helpers_disabled.go b/pkg/proc/internal/ebpf/helpers_disabled.go index 66035e04a9..ca6fe2391b 100644 --- a/pkg/proc/internal/ebpf/helpers_disabled.go +++ b/pkg/proc/internal/ebpf/helpers_disabled.go @@ -7,31 +7,9 @@ import ( "errors" ) -type Uretprobe struct { - Link interface{} - Pid int - Path string - Offset uint32 -} - -func (u *Uretprobe) Destroy() error { - return nil -} - type EBPFContext struct { } -func (ctx *EBPFContext) GetURetProbes() []Uretprobe { - return nil -} - -func (ctx *EBPFContext) ClearURetProbes() { -} - -func (ctx *EBPFContext) GetClearedURetProbes() []Uretprobe { - return nil -} - func (ctx *EBPFContext) Close() { } From 5c9907e5f5c39d560f7ae4feadbd66612879c4f8 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 09:31:02 -0700 Subject: [PATCH 14/17] pkg/proc: fixup a comment --- pkg/proc/internal/ebpf/trace_probe/trace.bpf.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c index 29085c7414..fa0b3cb937 100644 --- a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c +++ b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c @@ -257,11 +257,7 @@ int uprobe__dlv_trace(struct pt_regs *ctx) { // Parse input parameters. parse_params(ctx, args, parsed_args, false); } else { - // In uretprobe at function return address. - // Note we are not at the RET instruction, - // we are actually at the return address of - // the function we are tracing in the calling - // function. + // We are now stopped at the RET instruction for this function. // Parse output parameters. parse_params(ctx, args, parsed_args, true); From 11ef680d9ee32395b28a6fc2e588210bdc8b29d6 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 13 Oct 2021 18:50:04 -0700 Subject: [PATCH 15/17] pkg/proc: a little more cleanup --- pkg/proc/internal/ebpf/helpers.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go index a9249fde0d..97dd1fb4b2 100644 --- a/pkg/proc/internal/ebpf/helpers.go +++ b/pkg/proc/internal/ebpf/helpers.go @@ -200,8 +200,6 @@ func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeAr params.params[params.n_parameters] = param params.n_parameters++ } else { - offset := arg.Offset - param.offset = C.int(offset) params.ret_params[params.n_ret_parameters] = param params.n_ret_parameters++ } From bcaaacaffeb13d383bd6676ebbef96857902d7ac Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Sun, 17 Oct 2021 14:13:59 -0700 Subject: [PATCH 16/17] feedback changes --- pkg/proc/internal/ebpf/helpers.go | 28 +++++++ pkg/proc/internal/ebpf/helpers_disabled.go | 5 ++ .../internal/ebpf/trace_probe/trace.bpf.c | 56 ++++---------- pkg/proc/native/proc_linux.go | 73 ++++++------------- pkg/proc/target.go | 12 +-- 5 files changed, 76 insertions(+), 98 deletions(-) diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go index 97dd1fb4b2..0a0c66d01b 100644 --- a/pkg/proc/internal/ebpf/helpers.go +++ b/pkg/proc/internal/ebpf/helpers.go @@ -6,6 +6,7 @@ package ebpf // #include "./trace_probe/function_vals.bpf.h" import "C" import ( + "debug/elf" _ "embed" "encoding/binary" "errors" @@ -206,3 +207,30 @@ func createFunctionParameterList(entry uint64, goidOffset int64, args []UProbeAr } return params } + +func AddressToOffset(f *elf.File, addr uint64) (uint32, error) { + sectionsToSearchForSymbol := []*elf.Section{} + + for i := range f.Sections { + if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { + sectionsToSearchForSymbol = append(sectionsToSearchForSymbol, f.Sections[i]) + } + } + + var executableSection *elf.Section + + // Find what section the symbol is in by checking the executable section's + // addr space. + for m := range sectionsToSearchForSymbol { + if addr > sectionsToSearchForSymbol[m].Addr && + addr < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size { + executableSection = sectionsToSearchForSymbol[m] + } + } + + if executableSection == nil { + return 0, errors.New("could not find symbol in executable sections of binary") + } + + return uint32(addr - executableSection.Addr + executableSection.Offset), nil +} diff --git a/pkg/proc/internal/ebpf/helpers_disabled.go b/pkg/proc/internal/ebpf/helpers_disabled.go index ca6fe2391b..1dc274ea3a 100644 --- a/pkg/proc/internal/ebpf/helpers_disabled.go +++ b/pkg/proc/internal/ebpf/helpers_disabled.go @@ -4,6 +4,7 @@ package ebpf import ( + "debug/elf" "errors" ) @@ -37,3 +38,7 @@ func SymbolToOffset(file, symbol string) (uint32, error) { func LoadEBPFTracingProgram() (*EBPFContext, error) { return nil, errors.New("eBPF disabled") } + +func AddressToOffset(f *elf.File, addr uint64) (uint32, error) { + return 0, errors.New("eBPF disabled") +} diff --git a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c index fa0b3cb937..2e6110e7c9 100644 --- a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c +++ b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c @@ -177,39 +177,22 @@ int get_goroutine_id(function_parameter_list_t *parsed_args) { } __always_inline -void parse_params(struct pt_regs *ctx, function_parameter_list_t *args, function_parameter_list_t *parsed_args, bool ret) { +void parse_params(struct pt_regs *ctx, unsigned int n_params, function_parameter_t params[6], bool ret) { // Since we cannot loop in eBPF programs let's take adavantage of the // fact that in C switch cases will pass through automatically. - if (!ret) { - switch (args->n_parameters) { - case 6: - parse_param(ctx, &parsed_args->params[5]); - case 5: - parse_param(ctx, &parsed_args->params[4]); - case 4: - parse_param(ctx, &parsed_args->params[3]); - case 3: - parse_param(ctx, &parsed_args->params[2]); - case 2: - parse_param(ctx, &parsed_args->params[1]); - case 1: - parse_param(ctx, &parsed_args->params[0]); - } - } else { - switch (args->n_ret_parameters) { - case 6: - parse_param(ctx, &parsed_args->ret_params[5]); - case 5: - parse_param(ctx, &parsed_args->ret_params[4]); - case 4: - parse_param(ctx, &parsed_args->ret_params[3]); - case 3: - parse_param(ctx, &parsed_args->ret_params[2]); - case 2: - parse_param(ctx, &parsed_args->ret_params[1]); - case 1: - parse_param(ctx, &parsed_args->ret_params[0]); - } + switch (n_params) { + case 6: + parse_param(ctx, ¶ms[5]); + case 5: + parse_param(ctx, ¶ms[4]); + case 4: + parse_param(ctx, ¶ms[3]); + case 3: + parse_param(ctx, ¶ms[2]); + case 2: + parse_param(ctx, ¶ms[1]); + case 1: + parse_param(ctx, ¶ms[0]); } } @@ -247,20 +230,13 @@ int uprobe__dlv_trace(struct pt_regs *ctx) { if (!args->is_ret) { // In uprobe at function entry. - // We're in the entry point of the function at the uprobe we set. - // Get our return address and ensure that we set the parameter information - // at that address as well for the uretprobe. - size_t ret_addr; - bpf_probe_read_user(&ret_addr, sizeof(size_t), (void*)(ctx->sp)); - bpf_map_update_elem(&arg_map, &ret_addr, args, 0); - // Parse input parameters. - parse_params(ctx, args, parsed_args, false); + parse_params(ctx, args->n_parameters, parsed_args->params, false); } else { // We are now stopped at the RET instruction for this function. // Parse output parameters. - parse_params(ctx, args, parsed_args, true); + parse_params(ctx, args->n_ret_parameters, parsed_args->ret_params, true); } bpf_ringbuf_submit(parsed_args, 0); diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 81ceae9c48..6b5613a2cf 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -751,61 +751,34 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf if err != nil { return fmt.Errorf("could not open elf file to resolve symbol offset: %w", err) } - syms, err := f.Symbols() + + var regs proc.Registers + mem := dbp.Memory() + regs, _ = dbp.memthread.Registers() + instructions, err := proc.Disassemble(mem, regs, &proc.BreakpointMap{}, dbp.BinInfo(), fn.Entry, fn.End) if err != nil { - return fmt.Errorf("could not open symbol section to resolve symbol offset: %w", err) + return err } - sectionsToSearchForSymbol := []*elf.Section{} - - for i := range f.Sections { - if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { - sectionsToSearchForSymbol = append(sectionsToSearchForSymbol, f.Sections[i]) + var addrs []uint64 + for _, instruction := range instructions { + if instruction.IsRet() { + addrs = append(addrs, instruction.Loc.PC) } } - - var executableSection *elf.Section - - for j := range syms { - if syms[j].Name == fnName { - // Find what section the symbol is in by checking the executable section's - // addr space. - for m := range sectionsToSearchForSymbol { - if syms[j].Value > sectionsToSearchForSymbol[m].Addr && - syms[j].Value < sectionsToSearchForSymbol[m].Addr+sectionsToSearchForSymbol[m].Size { - executableSection = sectionsToSearchForSymbol[m] - } - } - - if executableSection == nil { - return errors.New("could not find symbol in executable sections of binary") - } - var regs proc.Registers - mem := dbp.Memory() - regs, _ = dbp.memthread.Registers() - instructions, err := proc.Disassemble(mem, regs, &proc.BreakpointMap{}, dbp.BinInfo(), fn.Entry, fn.End) - if err != nil { - return err - } - - var addrs []uint64 - for _, instruction := range instructions { - if instruction.IsRet() { - addrs = append(addrs, instruction.Loc.PC) - } - } - addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...) - for _, addr := range addrs { - err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, dbp.BinInfo().GStructOffset(), true) - if err != nil { - return err - } - off := uint32(addr - executableSection.Addr + executableSection.Offset) - err = dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, off) - if err != nil { - return err - } - } + addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...) + for _, addr := range addrs { + err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, dbp.BinInfo().GStructOffset(), true) + if err != nil { + return err + } + off, err := ebpf.AddressToOffset(f, addr) + if err != nil { + return err + } + err = dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, off) + if err != nil { + return err } } diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 02f23428a3..dc503e1907 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -423,6 +423,10 @@ func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult { compMem, _ := CreateCompositeMemory(cachedMem, t.BinInfo().Arch, op.DwarfRegisters{}, ip.Pieces) v.mem = compMem + // Load the value here so that we don't have to export + // loadValue outside of proc. + v.loadValue(loadFullValue) + return v } for _, tp := range tracepoints { @@ -431,18 +435,10 @@ func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult { r.GoroutineID = tp.GoroutineID for _, ip := range tp.InputParams { v := convertInputParamToVariable(ip) - - // Load the value here so that we don't have to export - // loadValue outside of proc. - v.loadValue(loadFullValue) r.InputParams = append(r.InputParams, v) } for _, ip := range tp.ReturnParams { v := convertInputParamToVariable(ip) - - // Load the value here so that we don't have to export - // loadValue outside of proc. - v.loadValue(loadFullValue) r.ReturnParams = append(r.ReturnParams, v) } results = append(results, r) From 665d02ba5b1222023edbbf3e806510e7c8d5628c Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Mon, 25 Oct 2021 11:25:06 -0700 Subject: [PATCH 17/17] pkg/proc: remove unused ret param --- pkg/proc/internal/ebpf/trace_probe/trace.bpf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c index 2e6110e7c9..39fdff227e 100644 --- a/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c +++ b/pkg/proc/internal/ebpf/trace_probe/trace.bpf.c @@ -177,7 +177,7 @@ int get_goroutine_id(function_parameter_list_t *parsed_args) { } __always_inline -void parse_params(struct pt_regs *ctx, unsigned int n_params, function_parameter_t params[6], bool ret) { +void parse_params(struct pt_regs *ctx, unsigned int n_params, function_parameter_t params[6]) { // Since we cannot loop in eBPF programs let's take adavantage of the // fact that in C switch cases will pass through automatically. switch (n_params) { @@ -231,12 +231,12 @@ int uprobe__dlv_trace(struct pt_regs *ctx) { // In uprobe at function entry. // Parse input parameters. - parse_params(ctx, args->n_parameters, parsed_args->params, false); + parse_params(ctx, args->n_parameters, parsed_args->params); } else { // We are now stopped at the RET instruction for this function. // Parse output parameters. - parse_params(ctx, args->n_ret_parameters, parsed_args->ret_params, true); + parse_params(ctx, args->n_ret_parameters, parsed_args->ret_params); } bpf_ringbuf_submit(parsed_args, 0);