From 8a4081a30a4dbfaef33aec84485be1218e834b3c Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Fri, 10 Sep 2021 17:42:18 +0200 Subject: [PATCH 1/5] linux ebpf initial commit --- .gitignore | 2 + Makefile | 54 ++ cmd/fibratus/app/capture.go | 27 + cmd/fibratus/app/capture_linux.go | 25 + cmd/fibratus/app/capture_windows.go | 13 +- cmd/fibratus/app/config.go | 2 +- cmd/fibratus/app/control_service.go | 39 ++ cmd/fibratus/app/control_service_linux.go | 33 + cmd/fibratus/app/control_service_windows.go | 26 +- cmd/fibratus/app/docs.go | 10 +- cmd/fibratus/app/install_service_linux.go | 31 + cmd/fibratus/app/list.go | 11 +- cmd/fibratus/app/remove_service_linux.go | 31 + cmd/fibratus/app/replay.go | 27 + cmd/fibratus/app/replay_linux.go | 25 + cmd/fibratus/app/replay_windows.go | 13 +- cmd/fibratus/app/root.go | 17 +- cmd/fibratus/app/run_linux.go | 125 ++++ cmd/fibratus/app/run_windows.go | 13 +- cmd/fibratus/app/stats.go | 7 +- .../common/{init_windows.go => setup.go} | 23 +- cmd/fibratus/main_linux.go | 31 + go.mod | 3 +- go.sum | 7 + pkg/aggregator/aggregator.go | 6 +- pkg/api/listener.go | 43 +- pkg/api/listener_windows.go | 60 ++ pkg/api/server.go | 17 +- pkg/api/server_linux.go | 36 + pkg/api/server_windows.go | 13 +- pkg/config/alertsender.go | 2 +- pkg/config/config.go | 283 ++++++++ pkg/config/config_linux.go | 40 ++ pkg/config/config_windows.go | 236 +------ pkg/config/filters.go | 58 +- pkg/config/kstream_linux.go | 36 + pkg/config/kstream_test.go | 1 + pkg/config/{kstream.go => kstream_windows.go} | 2 - pkg/config/output.go | 12 +- pkg/config/schema_linux.go | 23 + pkg/config/schema_windows.go | 45 -- pkg/config/transformer.go | 5 +- pkg/ebpf/c/bpf/api.h | 92 +++ pkg/ebpf/c/kevent.h | 26 + pkg/ebpf/c/kprobe.c | 111 +++ pkg/ebpf/c/maps.h | 59 ++ pkg/ebpf/c/read.h | 0 pkg/ebpf/c/syscall.h | 22 + pkg/errors/errors_linux.go | 27 + pkg/filament/filament.go | 1 + pkg/filament/filament_test.go | 1 + pkg/filament/filament_unsupported.go | 1 + pkg/filament/kdict.go | 1 + pkg/filament/kdict_test.go | 1 + pkg/filament/table.go | 1 + pkg/filament/table_test.go | 1 + pkg/filter/accessor.go | 637 ----------------- pkg/filter/accessor_windows.go | 663 ++++++++++++++++++ pkg/filter/fields/fields.go | 56 +- pkg/filter/fields/fields_linux.go | 25 + pkg/filter/fields/fields_windows.go | 51 -- pkg/filter/filter_linux.go | 29 + pkg/filter/funcmap/funcmap.go | 101 +++ pkg/filter/funcmap/funcmap_linux.go | 24 + pkg/filter/funcmap/funcmap_windows.go | 77 -- pkg/fs/attrs.go | 1 + pkg/fs/dev.go | 1 + pkg/fs/dev_test.go | 1 + pkg/fs/file.go | 1 + pkg/fs/file_test.go | 1 + pkg/fs/types.go | 1 + pkg/handle/alpc.go | 1 + pkg/handle/key.go | 1 + pkg/handle/key_test.go | 1 + pkg/handle/mutant.go | 1 + pkg/handle/object.go | 1 + pkg/handle/object_test.go | 1 + pkg/handle/snapshotter.go | 1 + pkg/handle/snapshotter_mock.go | 1 + pkg/handle/snapshotter_test.go | 1 + pkg/handle/timeout.go | 1 + pkg/handle/timeout_test.go | 1 + pkg/handle/types.go | 1 + pkg/handle/types/marshaller.go | 1 + pkg/handle/types/marshaller_test.go | 1 + pkg/handle/types/types.go | 1 + pkg/kcap/header.go | 1 + pkg/kcap/reader.go | 1 + pkg/kcap/reader_unsupported.go | 1 + pkg/kcap/reader_windows.go | 1 + pkg/kcap/reader_windows_test.go | 1 + pkg/kcap/section/section.go | 4 + pkg/kcap/section/section_linux.go | 24 + pkg/kcap/section/section_windows.go | 3 - pkg/kcap/types_linux.go | 7 + pkg/kcap/version/version.go | 22 + pkg/kcap/version/version_linux.go | 19 + pkg/kcap/writer.go | 1 + pkg/kcap/writer_unsupported.go | 1 + pkg/kcap/writer_windows.go | 1 + pkg/kcap/writer_windows_test.go | 1 + pkg/kevent/formatter_linux.go | 42 ++ pkg/kevent/kevent.go | 22 +- pkg/kevent/kevent_linux.go | 76 ++ pkg/kevent/kevent_windows.go | 17 +- pkg/kevent/kparam_linux.go | 33 + pkg/kevent/kparams/canonicalize.go | 1 + pkg/kevent/kparams/canonicalize_test.go | 1 + pkg/kevent/kparams/fields.go | 28 + pkg/kevent/kparams/fields_windows.go | 6 - pkg/kevent/kparams/size.go | 1 + pkg/kevent/kparams/size_test.go | 1 + pkg/kevent/kparams/types.go | 80 +++ pkg/kevent/kparams/types_linux.go | 87 +++ pkg/kevent/kparams/types_windows.go | 63 -- pkg/kevent/ktypes/ktypes.go | 30 + pkg/kevent/ktypes/ktypes_linux.go | 48 ++ pkg/kevent/ktypes/ktypes_windows.go | 11 - pkg/kevent/ktypes/metainfo.go | 39 ++ pkg/kevent/ktypes/metainfo_linux.go | 59 ++ pkg/kevent/ktypes/metainfo_windows.go | 20 - pkg/kevent/marshaller_linux.go | 33 + ...ler_test.go => marshaller_windows_test.go} | 0 pkg/kevent/sequencer_linux.go | 24 + pkg/kstream/interceptors/chain_linux.go | 23 + pkg/kstream/kstream_rundownc.go | 1 + pkg/kstream/kstreamc.go | 52 ++ pkg/kstream/kstreamc_linux.go | 253 +++++++ pkg/kstream/kstreamc_windows.go | 24 - pkg/kstream/maps.go | 75 ++ pkg/pe/config.go | 1 + pkg/pe/entropy.go | 1 + pkg/pe/marshaller.go | 1 + pkg/pe/marshaller_test.go | 1 + pkg/pe/reader.go | 1 + pkg/pe/reader_test.go | 1 + pkg/pe/resource/types.go | 1 + pkg/pe/resources.go | 1 + pkg/pe/resources_test.go | 1 + pkg/pe/section.go | 1 + pkg/pe/section_test.go | 1 + pkg/pe/types.go | 1 + pkg/ps/peb.go | 1 + pkg/ps/peb_test.go | 1 + pkg/ps/types/types_linux.go | 23 + pkg/syscall/etw/etw.go | 1 + pkg/syscall/etw/types.go | 1 + pkg/syscall/file/file.go | 1 + pkg/syscall/file/types.go | 1 + pkg/syscall/handle/handle.go | 1 + pkg/syscall/object/alpc.go | 1 + pkg/syscall/object/event.go | 1 + pkg/syscall/object/mutant.go | 1 + pkg/syscall/object/object.go | 1 + pkg/syscall/object/types.go | 1 + pkg/syscall/process/process.go | 1 + pkg/syscall/process/types.go | 1 + pkg/syscall/registry/key.go | 1 + pkg/syscall/security/privileges.go | 1 + pkg/syscall/security/sid.go | 1 + pkg/syscall/sys/sys.go | 1 + pkg/syscall/tdh/tdh.go | 1 + pkg/syscall/tdh/types.go | 1 + pkg/syscall/thread/thread.go | 1 + pkg/syscall/utf16/string.go | 1 + pkg/syscall/winerrno/errors.go | 1 + pkg/util/filetime/filetime.go | 1 + pkg/util/hostname/hostname.go | 27 + pkg/util/hostname/hostname_linux.go | 35 + pkg/util/hostname/hostname_windows.go | 6 - pkg/util/ip/ip.go | 1 + pkg/util/ip/ip_test.go | 1 + pkg/util/limit/limit.go | 35 + pkg/util/log/logger.go | 20 +- pkg/util/rest/rest.go | 18 - pkg/util/rest/rest_linux.go | 26 + pkg/util/rest/rest_windows.go | 35 + .../{rest_test.go => rest_windows_test.go} | 0 pkg/util/service/service_linux.go | 21 + pkg/util/service/service_windows.go | 29 + pkg/util/term/fb.go | 1 + pkg/util/term/term.go | 1 + pkg/util/user/user_linux.go | 26 + pkg/yara/scanner.go | 1 + pkg/yara/scanner_test.go | 1 + pkg/yara/scanner_unsupported.go | 1 + pkg/yara/scanner_windows.go | 1 + 187 files changed, 3764 insertions(+), 1360 deletions(-) create mode 100644 Makefile create mode 100644 cmd/fibratus/app/capture.go create mode 100644 cmd/fibratus/app/capture_linux.go create mode 100644 cmd/fibratus/app/control_service.go create mode 100644 cmd/fibratus/app/control_service_linux.go create mode 100644 cmd/fibratus/app/install_service_linux.go create mode 100644 cmd/fibratus/app/remove_service_linux.go create mode 100644 cmd/fibratus/app/replay.go create mode 100644 cmd/fibratus/app/replay_linux.go create mode 100644 cmd/fibratus/app/run_linux.go rename cmd/fibratus/common/{init_windows.go => setup.go} (64%) create mode 100644 cmd/fibratus/main_linux.go create mode 100644 pkg/api/listener_windows.go create mode 100644 pkg/api/server_linux.go create mode 100644 pkg/config/config.go create mode 100644 pkg/config/config_linux.go create mode 100644 pkg/config/kstream_linux.go rename pkg/config/{kstream.go => kstream_windows.go} (99%) create mode 100644 pkg/config/schema_linux.go create mode 100644 pkg/ebpf/c/bpf/api.h create mode 100644 pkg/ebpf/c/kevent.h create mode 100644 pkg/ebpf/c/kprobe.c create mode 100644 pkg/ebpf/c/maps.h create mode 100644 pkg/ebpf/c/read.h create mode 100644 pkg/ebpf/c/syscall.h create mode 100644 pkg/errors/errors_linux.go create mode 100644 pkg/filter/accessor_windows.go create mode 100644 pkg/filter/fields/fields_linux.go create mode 100644 pkg/filter/filter_linux.go create mode 100644 pkg/filter/funcmap/funcmap.go create mode 100644 pkg/filter/funcmap/funcmap_linux.go create mode 100644 pkg/kcap/section/section_linux.go create mode 100644 pkg/kcap/version/version.go create mode 100644 pkg/kcap/version/version_linux.go create mode 100644 pkg/kevent/formatter_linux.go create mode 100644 pkg/kevent/kevent_linux.go create mode 100644 pkg/kevent/kparam_linux.go create mode 100644 pkg/kevent/kparams/fields.go create mode 100644 pkg/kevent/kparams/types.go create mode 100644 pkg/kevent/kparams/types_linux.go create mode 100644 pkg/kevent/ktypes/ktypes.go create mode 100644 pkg/kevent/ktypes/ktypes_linux.go create mode 100644 pkg/kevent/ktypes/metainfo.go create mode 100644 pkg/kevent/ktypes/metainfo_linux.go create mode 100644 pkg/kevent/marshaller_linux.go rename pkg/kevent/{marshaller_test.go => marshaller_windows_test.go} (100%) create mode 100644 pkg/kevent/sequencer_linux.go create mode 100644 pkg/kstream/interceptors/chain_linux.go create mode 100644 pkg/kstream/kstreamc.go create mode 100644 pkg/kstream/maps.go create mode 100644 pkg/ps/types/types_linux.go create mode 100644 pkg/util/hostname/hostname.go create mode 100644 pkg/util/hostname/hostname_linux.go create mode 100644 pkg/util/limit/limit.go create mode 100644 pkg/util/rest/rest_linux.go create mode 100644 pkg/util/rest/rest_windows.go rename pkg/util/rest/{rest_test.go => rest_windows_test.go} (100%) create mode 100644 pkg/util/service/service_linux.go create mode 100644 pkg/util/service/service_windows.go create mode 100644 pkg/util/user/user_linux.go diff --git a/.gitignore b/.gitignore index ac6fa8f37..5459c20aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ cmd/fibratus/fibratus.exe +cmd/fibratus/fibratus cmd/fibratus/fibratus.syso +pkg/kstream/kprobe.o build/msi/fibratus-* build/msi/components.wxs diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..4c7067ddc --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +.DEFAULT_GOAL := build + +SHELL=bash -o pipefail -e +UNAME=$(shell uname -r) +# Override this if the target linux headers path differs +LINUX_HEADERS ?= /lib/modules/$(UNAME) + +CLANG ?= clang +LLC ?= llc +GO ?= go +GOFMT ?= gofmt + +# Specifies a list of build flags +TAGS ?= "" + +# Compiler flags for building the kprobe object +CFLAGS = -I $(LINUX_HEADERS)/build/arch/x86/include \ + -I $(LINUX_HEADERS)/build/arch/x86/include/generated/uapi \ + -I $(LINUX_HEADERS)/build/arch/x86/include/generated \ + -I $(LINUX_HEADERS)/build/include \ + -I $(LINUX_HEADERS)/build/arch/x86/include/uapi \ + -I $(LINUX_HEADERS)/build/include/uapi \ + -include $(LINUX_HEADERS)/build/include/linux/kconfig.h \ + -I $(LINUX_HEADERS)/build/include/generated/uapi \ + -D__KERNEL__ -D__ASM_SYSREG_H \ + -DKBUILD_MODNAME='"kprobe"' \ + -Wunused \ + -Wall \ + -Wno-compare-distinct-pointer-types \ + -fno-stack-protector \ + -Wno-pointer-sign \ + -O2 -S -emit-llvm + +.PHONY: build +build: + $(GO) build -tags $(TAGS) -o ./cmd/fibratus/fibratus ./cmd/fibratus/ + +.PHONY: fmt +fmt: + $(GOFMT) -e -s -l -w pkg cmd + +KPROBE_PROG := pkg/kstream/kprobe.o +pkg/kstream/kprobe.o: pkg/ebpf/c/kprobe.c + $(CLANG) $(CFLAGS) -c $< -o - | $(LLC) -march=bpf -mcpu=$(CPU) -filetype=obj -o $@ + +.PHONY: kprobe +kprobe: $(KPROBE_PROG) + +.PHONY: clean +clean: + rm -f cmd/fibratus/fibratus + rm -f pkg/kstream/kprobe.o + + diff --git a/cmd/fibratus/app/capture.go b/cmd/fibratus/app/capture.go new file mode 100644 index 000000000..9c2890ba3 --- /dev/null +++ b/cmd/fibratus/app/capture.go @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +var captureCmd = &cobra.Command{ + Use: "capture [filter]", + Short: "Capture kernel event stream to the kcap file", + RunE: capture, +} diff --git a/cmd/fibratus/app/capture_linux.go b/cmd/fibratus/app/capture_linux.go new file mode 100644 index 000000000..f3a6a610b --- /dev/null +++ b/cmd/fibratus/app/capture_linux.go @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +func capture(cmd *cobra.Command, args []string) error { + return nil +} diff --git a/cmd/fibratus/app/capture_windows.go b/cmd/fibratus/app/capture_windows.go index 465483a46..9f7ff4794 100644 --- a/cmd/fibratus/app/capture_windows.go +++ b/cmd/fibratus/app/capture_windows.go @@ -27,6 +27,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/kcap" "github.com/rabbitstack/fibratus/pkg/kstream" "github.com/rabbitstack/fibratus/pkg/ps" + "github.com/rabbitstack/fibratus/pkg/syscall/security" "github.com/rabbitstack/fibratus/pkg/util/multierror" "github.com/rabbitstack/fibratus/pkg/util/spinner" log "github.com/sirupsen/logrus" @@ -51,10 +52,15 @@ func init() { func capture(cmd *cobra.Command, args []string) error { // initialize config and logger - if err := common.Init(captureConfig, true); err != nil { + if err := common.SetupConfigAndLogger(captureConfig); err != nil { return err } + // inject the debug privilege if enabled + if captureConfig.DebugPrivilege { + security.SetDebugPrivilege() + } + // set up the signals stopCh := common.Signals() @@ -131,9 +137,6 @@ func capture(cmd *cobra.Command, args []string) error { if err := writer.Close(); err != nil { return err } - if err := api.CloseServer(); err != nil { - return err - } - return nil + return api.CloseServer() } diff --git a/cmd/fibratus/app/config.go b/cmd/fibratus/app/config.go index e5faab8a0..63be9e1d8 100644 --- a/cmd/fibratus/app/config.go +++ b/cmd/fibratus/app/config.go @@ -44,7 +44,7 @@ func init() { } func printConfig(cmd *cobra.Command, args []string) error { - if err := common.Init(c, false); err != nil { + if err := common.SetupConfigAndLogger(c); err != nil { return err } body, err := rest.Get(rest.WithTransport(c.API.Transport), rest.WithURI("config")) diff --git a/cmd/fibratus/app/control_service.go b/cmd/fibratus/app/control_service.go new file mode 100644 index 000000000..b9c5c71ec --- /dev/null +++ b/cmd/fibratus/app/control_service.go @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +var startSvcCmd = &cobra.Command{ + Use: "start-service", + RunE: startService, + Short: "Start fibratus service", +} + +var stopSvcCmd = &cobra.Command{ + Use: "stop-service", + RunE: stopService, + Short: "Stop fibratus service", +} + +var restartSvcCmd = &cobra.Command{ + Use: "restart-service", + RunE: restartService, + Short: "Restart fibratus service", +} diff --git a/cmd/fibratus/app/control_service_linux.go b/cmd/fibratus/app/control_service_linux.go new file mode 100644 index 000000000..57ebf8ea5 --- /dev/null +++ b/cmd/fibratus/app/control_service_linux.go @@ -0,0 +1,33 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +func startService(cmd *cobra.Command, args []string) error { + return nil +} + +func stopService(cmd *cobra.Command, args []string) error { + return nil +} + +func restartService(cmd *cobra.Command, args []string) error { + return nil +} diff --git a/cmd/fibratus/app/control_service_windows.go b/cmd/fibratus/app/control_service_windows.go index 69c9bd39e..930d938a4 100644 --- a/cmd/fibratus/app/control_service_windows.go +++ b/cmd/fibratus/app/control_service_windows.go @@ -27,6 +27,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/handle" "github.com/rabbitstack/fibratus/pkg/kstream" "github.com/rabbitstack/fibratus/pkg/ps" + "github.com/rabbitstack/fibratus/pkg/syscall/security" "github.com/spf13/cobra" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" @@ -36,24 +37,6 @@ import ( "time" ) -var startSvcCmd = &cobra.Command{ - Use: "start-service", - RunE: startService, - Short: "Start fibratus service", -} - -var stopSvcCmd = &cobra.Command{ - Use: "stop-service", - RunE: stopService, - Short: "Stop fibratus service", -} - -var restartSvcCmd = &cobra.Command{ - Use: "restart-service", - RunE: restartService, - Short: "Restart fibratus service", -} - var ( // windows service command config svcConfig = config.NewWithOpts(config.WithRun()) @@ -213,10 +196,15 @@ loop: func (s *fsvc) run() error { // initialize config and logger - if err := common.Init(svcConfig, true); err != nil { + if err := common.SetupConfigAndLogger(svcConfig); err != nil { return err } + // inject the debug privilege if enabled + if svcConfig.DebugPrivilege { + security.SetDebugPrivilege() + } + ctrl = kstream.NewKtraceController(svcConfig.Kstream) err := ctrl.StartKtrace() if err != nil { diff --git a/cmd/fibratus/app/docs.go b/cmd/fibratus/app/docs.go index db95375ce..b2b44ed3b 100644 --- a/cmd/fibratus/app/docs.go +++ b/cmd/fibratus/app/docs.go @@ -19,14 +19,20 @@ package app import ( - "github.com/spf13/cobra" "os/exec" + "runtime" + + "github.com/spf13/cobra" ) var docsCmd = &cobra.Command{ Use: "docs", Short: "Open Fibratus docs in the web browser", RunE: func(cmd *cobra.Command, args []string) error { - return exec.Command("rundll32", "url.dll,FileProtocolHandler", "https://www.fibratus.io").Start() + if runtime.GOOS == "windows" { + return exec.Command("rundll32", "url.dll,FileProtocolHandler", "https://www.fibratus.io").Start() + } else { + return exec.Command("xdg-open", "https://www.fibratus.io").Start() + } }, } diff --git a/cmd/fibratus/app/install_service_linux.go b/cmd/fibratus/app/install_service_linux.go new file mode 100644 index 000000000..009d2324f --- /dev/null +++ b/cmd/fibratus/app/install_service_linux.go @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +var installSvcCmd = &cobra.Command{ + Use: "install-service", + Short: "Install fibratus as systemd service", + RunE: installService, +} + +func installService(cmd *cobra.Command, args []string) error { + return nil +} diff --git a/cmd/fibratus/app/list.go b/cmd/fibratus/app/list.go index a88976cac..d154c7b6f 100644 --- a/cmd/fibratus/app/list.go +++ b/cmd/fibratus/app/list.go @@ -21,16 +21,17 @@ package app import ( "bufio" "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "github.com/jedib0t/go-pretty/v6/table" "github.com/rabbitstack/fibratus/cmd/fibratus/common" "github.com/rabbitstack/fibratus/pkg/config" "github.com/rabbitstack/fibratus/pkg/filter/fields" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" "github.com/spf13/cobra" - "io/ioutil" - "os" - "path/filepath" - "strings" ) var listCmd = &cobra.Command{ @@ -70,7 +71,7 @@ func init() { // listFilaments renders a table with all available filaments. func listFilaments(cmd *cobra.Command, args []string) error { - if err := common.Init(listConfig, false); err != nil { + if err := common.SetupConfigAndLogger(listConfig); err != nil { return err } diff --git a/cmd/fibratus/app/remove_service_linux.go b/cmd/fibratus/app/remove_service_linux.go new file mode 100644 index 000000000..bcc680ba1 --- /dev/null +++ b/cmd/fibratus/app/remove_service_linux.go @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +var removeSvcCmd = &cobra.Command{ + Use: "remove-service", + Short: "Remove fibratus systemd service", + RunE: removeService, +} + +func removeService(cmd *cobra.Command, args []string) error { + return nil +} diff --git a/cmd/fibratus/app/replay.go b/cmd/fibratus/app/replay.go new file mode 100644 index 000000000..fc710b7a7 --- /dev/null +++ b/cmd/fibratus/app/replay.go @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +var replayCmd = &cobra.Command{ + Use: "replay", + Short: "Replay kernel event flow from the kcap file", + RunE: replay, +} diff --git a/cmd/fibratus/app/replay_linux.go b/cmd/fibratus/app/replay_linux.go new file mode 100644 index 000000000..59a481b79 --- /dev/null +++ b/cmd/fibratus/app/replay_linux.go @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import "github.com/spf13/cobra" + +func replay(cmd *cobra.Command, args []string) error { + return nil +} diff --git a/cmd/fibratus/app/replay_windows.go b/cmd/fibratus/app/replay_windows.go index cfbf8de45..7e9b3ea90 100644 --- a/cmd/fibratus/app/replay_windows.go +++ b/cmd/fibratus/app/replay_windows.go @@ -30,12 +30,6 @@ import ( "github.com/spf13/cobra" ) -var replayCmd = &cobra.Command{ - Use: "replay", - Short: "Replay kernel event flow from the kcap file", - RunE: replay, -} - var ( // replay command config replayConfig = config.NewWithOpts(config.WithReplay()) @@ -47,7 +41,7 @@ func init() { func replay(cmd *cobra.Command, args []string) error { // initialize config and logger - if err := common.Init(replayConfig, false); err != nil { + if err := common.SetupConfigAndLogger(replayConfig); err != nil { return err } @@ -133,9 +127,6 @@ func replay(cmd *cobra.Command, args []string) error { return err } } - if err := api.CloseServer(); err != nil { - return err - } - return nil + return api.CloseServer() } diff --git a/cmd/fibratus/app/root.go b/cmd/fibratus/app/root.go index c33091ffa..cde5ccee6 100644 --- a/cmd/fibratus/app/root.go +++ b/cmd/fibratus/app/root.go @@ -20,8 +20,9 @@ package app import ( "errors" - "github.com/spf13/cobra" "runtime" + + "github.com/spf13/cobra" ) // RootCmd is the entrance to Fibratus CLI @@ -29,19 +30,19 @@ var RootCmd = &cobra.Command{ Use: "fibratus", Short: "Modern tool for the kernel observability and exploration", Long: ` - Fibratus is a tool for exploration and tracing of the Windows kernel. + Fibratus is a tool for exploration and tracing of the Windows and Linux kernels. It lets you trap system-wide events such as process life-cycle, file system I/O, - registry modifications or network requests among many other observability signals. - In a nutshell, Fibratus allows for gaining deep operational visibility into the Windows - kernel but also processes running on top of it. + or network requests among many other observability signals. + In a nutshell, Fibratus allows for gaining deep operational visibility into the kernel + but also processes running on top of it. `, SilenceUsage: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if runtime.GOOS != "windows" { - return errors.New("fibratus can only be run on Windows operating systems") + if runtime.GOOS != "windows" && runtime.GOOS != "linux" { + return errors.New("fibratus can only be run on Windows and Linux operating systems") } if runtime.GOARCH == "386" { - return errors.New("fibratus can't be run on 32-bits Windows operating systems") + return errors.New("fibratus can't be run on 32-bits operating systems") } return nil }, diff --git a/cmd/fibratus/app/run_linux.go b/cmd/fibratus/app/run_linux.go new file mode 100644 index 000000000..a5236ab85 --- /dev/null +++ b/cmd/fibratus/app/run_linux.go @@ -0,0 +1,125 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app + +import ( + "os" + + "github.com/rabbitstack/fibratus/cmd/fibratus/common" + "github.com/rabbitstack/fibratus/pkg/aggregator" + "github.com/rabbitstack/fibratus/pkg/api" + "github.com/rabbitstack/fibratus/pkg/config" + kerrors "github.com/rabbitstack/fibratus/pkg/errors" + "github.com/rabbitstack/fibratus/pkg/filter" + "github.com/rabbitstack/fibratus/pkg/kstream" + "github.com/rabbitstack/fibratus/pkg/util/user" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var runCmd = &cobra.Command{ + Use: "run [filter]", + Short: "Bootstrap fibratus or a filament", + Aliases: []string{"start"}, + RunE: run, + Example: ` + # Run without the filter + fibratus run + + # Run with the filter that drops all but events produced by the dnsmasq process + fibratus run ps.name = 'dnsmasq' + `, +} + +var ( + // the run command config + cfg = config.NewWithOpts(config.WithRun()) +) + +func init() { + // initialize flags + cfg.MustViperize(runCmd) +} + +func run(cmd *cobra.Command, args []string) error { + if !user.IsRoot() { + return kerrors.ErrNotRoot + } + // initialize config and logger + if err := common.SetupConfigAndLogger(cfg); err != nil { + return err + } + // set up the signals + stopCh := common.Signals() + + // try to load the kprobe by reading the embedded + // bytecode and parsing all of the ELF objects that + // build up the kprobe program + kstreamc, err := kstream.NewConsumer() + if err != nil { + return err + } + // build the filter from the CLI argument. If we got a valid expression the filter + // is linked to the kernel stream consumer so it can drop any events that don't match + // the filter criteria + kfilter, err := filter.NewFromCLI(args, cfg) + if err != nil { + return err + } + if kfilter != nil { + kstreamc.SetFilter(kfilter) + } + log.Infof("bootstrapping with pid %d", os.Getpid()) + + // attach raw tracepoint and poll perf buffer + if err := kstreamc.OpenKstream(); err != nil { + return err + } + + // setup the aggregator that forwards events to outputs + agg, err := aggregator.NewBuffered( + kstreamc.Events(), + kstreamc.Errors(), + cfg.Aggregator, + cfg.Output, + cfg.Transformers, + cfg.Alertsenders, + ) + if err != nil { + return err + } + defer func() { + if err := agg.Stop(); err != nil { + log.Error(err) + } + }() + + // start the HTTP server + if err := api.StartServer(cfg); err != nil { + return err + } + + <-stopCh + + if err := kstreamc.CloseKstream(); err != nil { + return err + } + + return api.CloseServer() +} diff --git a/cmd/fibratus/app/run_windows.go b/cmd/fibratus/app/run_windows.go index b52989dbc..aa1e1819f 100644 --- a/cmd/fibratus/app/run_windows.go +++ b/cmd/fibratus/app/run_windows.go @@ -29,6 +29,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/handle" "github.com/rabbitstack/fibratus/pkg/kstream" "github.com/rabbitstack/fibratus/pkg/ps" + "github.com/rabbitstack/fibratus/pkg/syscall/security" "github.com/rabbitstack/fibratus/pkg/util/multierror" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -67,10 +68,15 @@ func init() { func run(cmd *cobra.Command, args []string) error { // initialize config and logger - if err := common.Init(cfg, true); err != nil { + if err := common.SetupConfigAndLogger(cfg); err != nil { return err } + // inject the debug privilege if enabled + if cfg.DebugPrivilege { + security.SetDebugPrivilege() + } + // set up the signals stopCh := common.Signals() @@ -175,9 +181,6 @@ func run(cmd *cobra.Command, args []string) error { if err := handle.CloseTimeout(); err != nil { return err } - if err := api.CloseServer(); err != nil { - return err - } - return nil + return api.CloseServer() } diff --git a/cmd/fibratus/app/stats.go b/cmd/fibratus/app/stats.go index 25f19f654..74783b24f 100644 --- a/cmd/fibratus/app/stats.go +++ b/cmd/fibratus/app/stats.go @@ -20,14 +20,15 @@ package app import ( "encoding/json" + "os" + "reflect" + "github.com/jedib0t/go-pretty/v6/table" "github.com/rabbitstack/fibratus/cmd/fibratus/common" "github.com/rabbitstack/fibratus/pkg/config" kerrors "github.com/rabbitstack/fibratus/pkg/errors" "github.com/rabbitstack/fibratus/pkg/util/rest" "github.com/spf13/cobra" - "os" - "reflect" ) var statsCmd = &cobra.Command{ @@ -114,7 +115,7 @@ type Stats struct { } func stats(cmd *cobra.Command, args []string) error { - if err := common.Init(statsConfig, false); err != nil { + if err := common.SetupConfigAndLogger(statsConfig); err != nil { return err } diff --git a/cmd/fibratus/common/init_windows.go b/cmd/fibratus/common/setup.go similarity index 64% rename from cmd/fibratus/common/init_windows.go rename to cmd/fibratus/common/setup.go index 45f177451..8b7a3b2ba 100644 --- a/cmd/fibratus/common/init_windows.go +++ b/cmd/fibratus/common/setup.go @@ -20,29 +20,24 @@ package common import ( "github.com/rabbitstack/fibratus/pkg/config" - "github.com/rabbitstack/fibratus/pkg/syscall/security" "github.com/rabbitstack/fibratus/pkg/util/log" ) -// Init initializes and validates the configuration -// as given by the commands. This function will also set up -// the logger and adjust the process token with the debug -// privilege if required. -func Init(c *config.Config, debugPrivilege bool) error { - if err := c.TryLoadFile(c.File()); err != nil { +// SetupConfigAndLogger initializes and validates the configuration +// along with the logger. The configuration setup process consists of +// loading the config from the file, initializing internal state, and +// finally validating the config options. +func SetupConfigAndLogger(c *config.Config) error { + if err := c.TryLoadFile(c.GetConfigFile()); err != nil { return err } // initialize and validate the config if err := c.Init(); err != nil { return err } - if err := c.Validate(); err != nil { - return err - } - // inject the debug privilege if enabled - if c.DebugPrivilege && debugPrivilege { - security.SetDebugPrivilege() - } + // if err := c.Validate(); err != nil { + // return err + // } if err := log.InitFromConfig(c.Log); err != nil { return err } diff --git a/cmd/fibratus/main_linux.go b/cmd/fibratus/main_linux.go new file mode 100644 index 000000000..86ff331a3 --- /dev/null +++ b/cmd/fibratus/main_linux.go @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "os" + + "github.com/rabbitstack/fibratus/cmd/fibratus/app" +) + +func main() { + if err := app.RootCmd.Execute(); err != nil { + os.Exit(-1) + } +} diff --git a/go.mod b/go.mod index caeaa5060..3a1f7ef0e 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.2 github.com/Microsoft/go-winio v0.4.14 github.com/briandowns/spinner v1.12.0 + github.com/cilium/ebpf v0.6.2 // indirect github.com/dustin/go-humanize v1.0.0 github.com/hashicorp/go-version v1.2.1 github.com/hillu/go-yara/v4 v4.0.6 @@ -26,7 +27,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/gozstd v1.11.0 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.5 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df diff --git a/go.sum b/go.sum index 281ac5e21..b413efe92 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/briandowns/spinner v1.12.0 h1:72O0PzqGJb6G3KgrcIOtL/JAGGZ5ptOMCn9cUHmqsmw= github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cilium/ebpf v0.6.2 h1:iHsfF/t4aW4heW2YKfeHrVPGdtYTL4C4KocpM8KTSnI= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -36,6 +38,7 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= @@ -58,6 +61,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -94,6 +98,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -239,6 +244,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= diff --git a/pkg/aggregator/aggregator.go b/pkg/aggregator/aggregator.go index 70c830074..26cda56f6 100644 --- a/pkg/aggregator/aggregator.go +++ b/pkg/aggregator/aggregator.go @@ -21,20 +21,24 @@ package aggregator import ( "errors" "expvar" + "time" + "github.com/rabbitstack/fibratus/pkg/aggregator/transformers" "github.com/rabbitstack/fibratus/pkg/alertsender" "github.com/rabbitstack/fibratus/pkg/kevent" "github.com/rabbitstack/fibratus/pkg/outputs" log "github.com/sirupsen/logrus" - "time" + // initialize outputs _ "github.com/rabbitstack/fibratus/pkg/outputs/amqp" _ "github.com/rabbitstack/fibratus/pkg/outputs/console" _ "github.com/rabbitstack/fibratus/pkg/outputs/elasticsearch" _ "github.com/rabbitstack/fibratus/pkg/outputs/null" + // initialize alert senders _ "github.com/rabbitstack/fibratus/pkg/alertsender/mail" _ "github.com/rabbitstack/fibratus/pkg/alertsender/slack" + // initialize transformers _ "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/remove" _ "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/rename" diff --git a/pkg/api/listener.go b/pkg/api/listener.go index 52d17d18a..50e27e4a4 100644 --- a/pkg/api/listener.go +++ b/pkg/api/listener.go @@ -1,5 +1,3 @@ -// +build windows - /* * Copyright 2019-2020 by Nedim Sabic Sabic * https://www.fibratus.io @@ -20,48 +18,11 @@ package api -import ( - "context" - "fmt" - "github.com/Microsoft/go-winio" - "net" - "strings" -) +import "net" -// MakePipeListener produces a new listener for receiving requests over a named pipe. -func MakePipeListener(pipePath, descriptor string) (net.Listener, error) { - npipe := transformPipePath(pipePath) - l, err := winio.ListenPipe(npipe, &winio.PipeConfig{SecurityDescriptor: descriptor}) - if err != nil { - return nil, fmt.Errorf("fail to listen on the %q pipe: %v", pipePath, err) - } - return l, nil -} +var listener net.Listener // makeTCPListener produces a new listener for receiving requests over TCP. func makeTCPListener(addr string) (net.Listener, error) { return net.Listen("tcp", addr) } - -// DialPipe creates a dialer to be used with the http.Client to connect to a named pipe. -func DialPipe(pipePath string) func(context.Context, string, string) (net.Conn, error) { - npipe := transformPipePath(pipePath) - return func(ctx context.Context, _, _ string) (net.Conn, error) { - return winio.DialPipeContext(ctx, npipe) - } -} - -// transformPipePath takes an input type name defined as a URI like `npipe:///hello` and transform it into -// `\\.\pipe\hello`. Borrowed from https://github.com/elastic/beats/blob/master/libbeat/api/npipe/listener_windows.go -func transformPipePath(name string) string { - if strings.HasPrefix(name, "npipe:///") { - path := strings.TrimPrefix(name, "npipe:///") - return `\\.\pipe\` + path - } - - if strings.HasPrefix(name, `\\.\pipe\`) { - return name - } - - return name -} diff --git a/pkg/api/listener_windows.go b/pkg/api/listener_windows.go new file mode 100644 index 000000000..407f07f77 --- /dev/null +++ b/pkg/api/listener_windows.go @@ -0,0 +1,60 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "context" + "fmt" + "github.com/Microsoft/go-winio" + "net" + "strings" +) + +// MakePipeListener produces a new listener for receiving requests over a named pipe. +func MakePipeListener(pipePath, descriptor string) (net.Listener, error) { + npipe := transformPipePath(pipePath) + l, err := winio.ListenPipe(npipe, &winio.PipeConfig{SecurityDescriptor: descriptor}) + if err != nil { + return nil, fmt.Errorf("fail to listen on the %q pipe: %v", pipePath, err) + } + return l, nil +} + +// DialPipe creates a dialer to be used with the http.Client to connect to a named pipe. +func DialPipe(pipePath string) func(context.Context, string, string) (net.Conn, error) { + npipe := transformPipePath(pipePath) + return func(ctx context.Context, _, _ string) (net.Conn, error) { + return winio.DialPipeContext(ctx, npipe) + } +} + +// transformPipePath takes an input type name defined as a URI like `npipe:///hello` and transform it into +// `\\.\pipe\hello`. Borrowed from https://github.com/elastic/beats/blob/master/libbeat/api/npipe/listener_windows.go +func transformPipePath(name string) string { + if strings.HasPrefix(name, "npipe:///") { + path := strings.TrimPrefix(name, "npipe:///") + return `\\.\pipe\` + path + } + + if strings.HasPrefix(name, `\\.\pipe\`) { + return name + } + + return name +} diff --git a/pkg/api/server.go b/pkg/api/server.go index 2d1848c8b..814cb9793 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -20,17 +20,18 @@ package api import ( "expvar" - "github.com/rabbitstack/fibratus/pkg/api/handler" - "github.com/rabbitstack/fibratus/pkg/config" - log "github.com/sirupsen/logrus" "net" "net/http" "net/http/pprof" "runtime/debug" "strings" + + "github.com/rabbitstack/fibratus/pkg/api/handler" + "github.com/rabbitstack/fibratus/pkg/config" + log "github.com/sirupsen/logrus" ) -func setupServer(lis net.Listener, c *config.Config) { +func setupAndListen(lis net.Listener, c *config.Config) { mux := http.NewServeMux() mux.Handle("/config", handler.Config(c)) mux.Handle("/debug/vars", expvar.Handler()) @@ -54,3 +55,11 @@ func setupServer(lis net.Listener, c *config.Config) { } }() } + +// CloseServer shutdowns the server by stopping the listener. +func CloseServer() error { + if listener != nil { + return listener.Close() + } + return nil +} diff --git a/pkg/api/server_linux.go b/pkg/api/server_linux.go new file mode 100644 index 000000000..2d55b1c46 --- /dev/null +++ b/pkg/api/server_linux.go @@ -0,0 +1,36 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import "github.com/rabbitstack/fibratus/pkg/config" + +// StartServer starts the HTTP server with the specified configuration. +func StartServer(c *config.Config) error { + var err error + apiConfig := c.API + + listener, err = makeTCPListener(apiConfig.Transport) + if err != nil { + return err + } + + setupAndListen(listener, c) + + return nil +} diff --git a/pkg/api/server_windows.go b/pkg/api/server_windows.go index 2884a0a2b..50d4d1e78 100644 --- a/pkg/api/server_windows.go +++ b/pkg/api/server_windows.go @@ -21,13 +21,10 @@ package api import ( "fmt" "github.com/rabbitstack/fibratus/pkg/config" - "net" "os/user" "strings" ) -var listener net.Listener - // StartServer starts the HTTP server with the specified configuration. func StartServer(c *config.Config) error { var err error @@ -54,15 +51,7 @@ func StartServer(c *config.Config) error { return err } - setupServer(listener, c) + setupAndListen(listener, c) return nil } - -// CloseServer shutdowns the server by stopping the listener. -func CloseServer() error { - if listener != nil { - return listener.Close() - } - return nil -} diff --git a/pkg/config/alertsender.go b/pkg/config/alertsender.go index 3c7558f58..a4682d9b1 100644 --- a/pkg/config/alertsender.go +++ b/pkg/config/alertsender.go @@ -33,7 +33,7 @@ var errAlertsenderConfig = func(sender string, err error) error { return fmt.Errorf("%s alert sender invalid config: %v", sender, err) } -func (c *Config) tryLoadAlertSenders() error { +func (c *BaseConfig) tryLoadAlertSenders() error { configs := make([]alertsender.Config, 0) alertsenders := c.viper.AllSettings()["alertsenders"] if alertsenders == nil { diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 000000000..f857646b0 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,283 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + "time" + + "github.com/rabbitstack/fibratus/pkg/aggregator" + "github.com/rabbitstack/fibratus/pkg/aggregator/transformers" + removet "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/remove" + renamet "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/rename" + replacet "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/replace" + tagst "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/tags" + trimt "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/trim" + "github.com/rabbitstack/fibratus/pkg/alertsender" + mailsender "github.com/rabbitstack/fibratus/pkg/alertsender/mail" + slacksender "github.com/rabbitstack/fibratus/pkg/alertsender/slack" + "github.com/rabbitstack/fibratus/pkg/outputs" + "github.com/rabbitstack/fibratus/pkg/outputs/amqp" + "github.com/rabbitstack/fibratus/pkg/outputs/console" + "github.com/rabbitstack/fibratus/pkg/outputs/elasticsearch" + "github.com/rabbitstack/fibratus/pkg/util/log" + "github.com/rabbitstack/fibratus/pkg/util/multierror" + yara "github.com/rabbitstack/fibratus/pkg/yara/config" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +const ( + kcapFile = "kcap.file" + configFile = "config-file" + + serializeEnvs = "kevent.serialize-envs" +) + +// BaseConfig contains common fields for configuration sections. +type BaseConfig struct { + // Kstream stores different configuration options for fine tuning thea kstream consumer/controller settings. + Kstream KstreamConfig `json:"kstream" yaml:"kstream"` + // Filament contains filament settings + Filament FilamentConfig `json:"filament" yaml:"filament"` + + // API stores global HTTP API preferences + API APIConfig `json:"api" yaml:"api"` + // Yara contains configuration that influences the behaviour of the Yara engine + Yara yara.Config `json:"yara" yaml:"yara"` + // Aggregator stores event aggregator configuration + Aggregator aggregator.Config `json:"aggregator" yaml:"aggregator"` + // Log contains log-specific configuration options + Log log.Config `json:"logging" yaml:"logging"` + + // Output stores the currently active output config + Output outputs.Config + // Transformers stores transformer configurations + Transformers []transformers.Config + // Alertsenders stores alert sender configurations + Alertsenders []alertsender.Config + + // Filters contains filter group definitions + Filters *Filters `json:"filters" yaml:"filters"` + + // KcapFile specifies the kcap output file name + KcapFile string + + flags *pflag.FlagSet + viper *viper.Viper + opts *Options +} + +// Options determines which config flags are toggled depending on the command type. +type Options struct { + capture bool + replay bool + run bool + list bool + stats bool +} + +// Option is the type alias for the config option. +type Option func(*Options) + +// WithCapture determines the capture command is executed. +func WithCapture() Option { + return func(o *Options) { + o.capture = true + } +} + +// WithReplay determines the replay command is executed. +func WithReplay() Option { + return func(o *Options) { + o.replay = true + } +} + +// WithRun determines the main command is executed. +func WithRun() Option { + return func(o *Options) { + o.run = true + } +} + +// WithList determines the list command is executed. +func WithList() Option { + return func(o *Options) { + o.list = true + } +} + +// WithStats determines the stats command is executed. +func WithStats() Option { + return func(o *Options) { + o.stats = true + } +} + +// TryLoadFile attempts to load the configuration file from specified path on the file system. +func (c *BaseConfig) TryLoadFile(file string) error { + c.viper.SetConfigFile(file) + return c.viper.ReadInConfig() +} + +// Validate ensures that all configuration options provided by user have the expected values. It returns +// a list of validation errors prefixed with the offending configuration property/flag. +func (c *BaseConfig) Validate() error { + // we'll first validate the structure and values of the config file + file := c.viper.GetString(configFile) + var out interface{} + b, err := ioutil.ReadFile(file) + if err != nil { + return err + } + switch filepath.Ext(file) { + case ".yaml", ".yml": + err = yaml.Unmarshal(b, &out) + case ".json": + err = json.Unmarshal(b, &out) + default: + return fmt.Errorf("%s is not a supported config file extension", filepath.Ext(file)) + } + if err != nil { + return fmt.Errorf("couldn't read the config file: %v", err) + } + // validate config file content + valid, errs := validate(interpolateSchema(), out) + if !valid || len(errs) > 0 { + return fmt.Errorf("invalid config: %v", multierror.Wrap(errs...)) + } + // now validate the Viper config flags + valid, errs = validate(interpolateSchema(), c.viper.AllSettings()) + if !valid || len(errs) > 0 { + return fmt.Errorf("invalid config: %v", multierror.Wrap(errs...)) + } + return nil +} + +// GetConfigFile gets the path of the configuration file from Viper value. +func (c Config) GetConfigFile() string { return c.viper.GetString(configFile) } + +// MustViperize adds the flag set to the Cobra command and binds them within the Viper flags. +func (c *BaseConfig) MustViperize(cmd *cobra.Command) { + cmd.PersistentFlags().AddFlagSet(c.flags) + if err := c.viper.BindPFlags(cmd.PersistentFlags()); err != nil { + panic(err) + } + if c.opts.capture || c.opts.replay { + if err := cmd.MarkPersistentFlagRequired(kcapFile); err != nil { + panic(err) + } + } +} + +func newWithOpts(options ...Option) *Config { + opts := &Options{} + + for _, opt := range options { + opt(opts) + } + + v := viper.New() + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) + + flagSet := new(pflag.FlagSet) + + c := &Config{ + BaseConfig: BaseConfig{ + Kstream: KstreamConfig{}, + Filament: FilamentConfig{}, + API: APIConfig{}, + Log: log.Config{}, + Aggregator: aggregator.Config{}, + Filters: &Filters{}, + viper: v, + flags: flagSet, + opts: opts, + }, + } + + if opts.run || opts.replay { + aggregator.AddFlags(flagSet) + console.AddFlags(flagSet) + amqp.AddFlags(flagSet) + elasticsearch.AddFlags(flagSet) + removet.AddFlags(flagSet) + replacet.AddFlags(flagSet) + renamet.AddFlags(flagSet) + trimt.AddFlags(flagSet) + tagst.AddFlags(flagSet) + mailsender.AddFlags(flagSet) + slacksender.AddFlags(flagSet) + yara.AddFlags(flagSet) + } + + return c +} + +func (c *BaseConfig) init() error { + c.Kstream.initFromViper(c.viper) + c.Filament.initFromViper(c.viper) + c.API.initFromViper(c.viper) + c.Aggregator.InitFromViper(c.viper) + c.Log.InitFromViper(c.viper) + c.Yara.InitFromViper(c.viper) + c.Filters.initFromViper(c.viper) + + c.KcapFile = c.viper.GetString(kcapFile) + + if c.opts.run || c.opts.replay { + if err := c.tryLoadOutput(); err != nil { + return err + } + if err := c.tryLoadTransformers(); err != nil { + return err + } + if err := c.tryLoadAlertSenders(); err != nil { + return err + } + } + return nil +} + +func (c *BaseConfig) addCommonFlags() { + if c.opts.run || c.opts.replay { + c.flags.StringP(filamentName, "f", "", "Specifies the filament to execute") + c.flags.StringSlice(rulesFromPaths, []string{}, "Comma-separated list of rules files") + c.flags.StringSlice(rulesFromURLs, []string{}, "Comma-separated list of rules URL resources") + } + if c.opts.capture { + c.flags.StringP(kcapFile, "o", "", "The path of the output kcap file") + } + if c.opts.replay { + c.flags.StringP(kcapFile, "k", "", "The path of the input kcap file") + } + if c.opts.run || c.opts.replay || c.opts.capture || c.opts.stats { + c.flags.String(transport, `localhost:8080`, "Specifies the underlying transport protocol for the API HTTP server") + c.flags.Duration(timeout, time.Second*15, "Determines the timeout for the API server responses") + } + c.Log.AddFlags(c.flags) +} diff --git a/pkg/config/config_linux.go b/pkg/config/config_linux.go new file mode 100644 index 000000000..ed8f1a22d --- /dev/null +++ b/pkg/config/config_linux.go @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +type Config struct { + BaseConfig +} + +func NewWithOpts(options ...Option) *Config { + config := newWithOpts(options...) + + config.addCommonFlags() + config.addFlags() + + return config +} + +func (c *Config) Init() error { + return c.init() +} + +func (c *Config) addFlags() { + c.flags.String(configFile, "/etc/fibratus/fibratus.yml", "Indicates the location of the configuration file") +} diff --git a/pkg/config/config_windows.go b/pkg/config/config_windows.go index 91bd52009..0c064c521 100644 --- a/pkg/config/config_windows.go +++ b/pkg/config/config_windows.go @@ -21,41 +21,21 @@ package config import ( "encoding/json" "fmt" - "github.com/rabbitstack/fibratus/pkg/aggregator" - "github.com/rabbitstack/fibratus/pkg/aggregator/transformers" - removet "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/remove" - replacet "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/replace" - tagst "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/tags" - "github.com/rabbitstack/fibratus/pkg/kevent" - "github.com/rabbitstack/fibratus/pkg/outputs/amqp" - "github.com/rabbitstack/fibratus/pkg/outputs/elasticsearch" - "github.com/rabbitstack/fibratus/pkg/util/log" - "github.com/rabbitstack/fibratus/pkg/util/multierror" - yara "github.com/rabbitstack/fibratus/pkg/yara/config" - "gopkg.in/yaml.v3" "io/ioutil" "time" - renamet "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/rename" - trimt "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/trim" + "github.com/rabbitstack/fibratus/pkg/kevent" + "gopkg.in/yaml.v3" + "os" + "path/filepath" + "strings" - "github.com/rabbitstack/fibratus/pkg/alertsender" - mailsender "github.com/rabbitstack/fibratus/pkg/alertsender/mail" - slacksender "github.com/rabbitstack/fibratus/pkg/alertsender/slack" - "github.com/rabbitstack/fibratus/pkg/outputs" - "github.com/rabbitstack/fibratus/pkg/outputs/console" "github.com/rabbitstack/fibratus/pkg/pe" - "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" - "os" - "path/filepath" - "strings" ) const ( - kcapFile = "kcap.file" - configFile = "config-file" debugPrivilege = "debug-privilege" initHandleSnapshot = "handle.init-snapshot" @@ -63,177 +43,39 @@ const ( serializeImages = "kevent.serialize-images" serializeHandles = "kevent.serialize-handles" serializePE = "kevent.serialize-pe" - serializeEnvs = "kevent.serialize-envs" ) // Config stores configuration options for fine tuning the behaviour of Fibratus. type Config struct { - // Kstream stores different configuration options for fine tuning kstream consumer/controller settings. - Kstream KstreamConfig `json:"kstream" yaml:"kstream"` - // Filament contains filament settings - Filament FilamentConfig `json:"filament" yaml:"filament"` + BaseConfig // PE contains the settings that influences the behaviour of the PE (Portable Executable) reader. PE pe.Config `json:"pe" yaml:"pe"` - // Output stores the currently active output config - Output outputs.Config // InitHandleSnapshot indicates whether initial handle snapshot is built InitHandleSnapshot bool `json:"init-handle-snapshot" yaml:"init-handle-snapshot"` - DebugPrivilege bool `json:"debug-privilege" yaml:"debug-privilege"` - KcapFile string - - // API stores global HTTP API preferences - API APIConfig `json:"api" yaml:"api"` - // Yara contains configuration that influences the behaviour of the Yara engine - Yara yara.Config `json:"yara" yaml:"yara"` - // Aggregator stores event aggregator configuration - Aggregator aggregator.Config `json:"aggregator" yaml:"aggregator"` - // Log contains log-specific configuration options - Log log.Config `json:"logging" yaml:"logging"` - - // Transformers stores transformer configurations - Transformers []transformers.Config - // Alertsenders stores alert sender configurations - Alertsenders []alertsender.Config - - // Filters contains filter group definitions - Filters *Filters `json:"filters" yaml:"filters"` - - flags *pflag.FlagSet - viper *viper.Viper - opts *Options -} - -// Options determines which config flags are toggled depending on the command type. -type Options struct { - capture bool - replay bool - run bool - list bool - stats bool -} - -// Option is the type alias for the config option. -type Option func(*Options) - -// WithCapture determines the capture command is executed. -func WithCapture() Option { - return func(o *Options) { - o.capture = true - } -} - -// WithReplay determines the replay command is executed. -func WithReplay() Option { - return func(o *Options) { - o.replay = true - } -} - -// WithRun determines the main command is executed. -func WithRun() Option { - return func(o *Options) { - o.run = true - } -} - -// WithList determines the list command is executed. -func WithList() Option { - return func(o *Options) { - o.list = true - } -} - -// WithStats determines the stats command is executed. -func WithStats() Option { - return func(o *Options) { - o.stats = true - } + // DebugPrivilege determines whether fibratus process token acquires the debug privilege + DebugPrivilege bool `json:"debug-privilege" yaml:"debug-privilege"` } // NewWithOpts builds a new configuration store from a variety of sources such as configuration files, // environment variables or command line flags. func NewWithOpts(options ...Option) *Config { - opts := &Options{} - - for _, opt := range options { - opt(opts) - } - - v := viper.New() - v.AutomaticEnv() - v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) - - flagSet := new(pflag.FlagSet) - - c := &Config{ - Kstream: KstreamConfig{}, - Filament: FilamentConfig{}, - API: APIConfig{}, - PE: pe.Config{}, - Log: log.Config{}, - Aggregator: aggregator.Config{}, - Filters: &Filters{}, - viper: v, - flags: flagSet, - opts: opts, - } - - if opts.run || opts.replay { - aggregator.AddFlags(flagSet) - console.AddFlags(flagSet) - amqp.AddFlags(flagSet) - elasticsearch.AddFlags(flagSet) - removet.AddFlags(flagSet) - replacet.AddFlags(flagSet) - renamet.AddFlags(flagSet) - trimt.AddFlags(flagSet) - tagst.AddFlags(flagSet) - mailsender.AddFlags(flagSet) - slacksender.AddFlags(flagSet) - yara.AddFlags(flagSet) - } + config := newWithOpts(options...) if opts.run || opts.capture { pe.AddFlags(flagSet) } - c.addFlags() + config.addFlags() - return c -} - -// GetConfigFile gets the path of the configuration file from Viper value. -func (c Config) GetConfigFile() string { - return c.viper.GetString(configFile) -} - -// MustViperize adds the flag set to the Cobra command and binds them within the Viper flags. -func (c *Config) MustViperize(cmd *cobra.Command) { - cmd.PersistentFlags().AddFlagSet(c.flags) - if err := c.viper.BindPFlags(cmd.PersistentFlags()); err != nil { - panic(err) - } - if c.opts.capture || c.opts.replay { - if err := cmd.MarkPersistentFlagRequired(kcapFile); err != nil { - panic(err) - } - } + return config } // Init setups the configuration state from Viper. func (c *Config) Init() error { - c.Kstream.initFromViper(c.viper) - c.Filament.initFromViper(c.viper) - c.API.initFromViper(c.viper) c.PE.InitFromViper(c.viper) - c.Aggregator.InitFromViper(c.viper) - c.Log.InitFromViper(c.viper) - c.Yara.InitFromViper(c.viper) - c.Filters.initFromViper(c.viper) c.InitHandleSnapshot = c.viper.GetBool(initHandleSnapshot) c.DebugPrivilege = c.viper.GetBool(debugPrivilege) - c.KcapFile = c.viper.GetString(kcapFile) kevent.SerializeThreads = c.viper.GetBool(serializeThreads) kevent.SerializeImages = c.viper.GetBool(serializeImages) @@ -241,63 +83,9 @@ func (c *Config) Init() error { kevent.SerializePE = c.viper.GetBool(serializePE) kevent.SerializeEnvs = c.viper.GetBool(serializeEnvs) - if c.opts.run || c.opts.replay { - if err := c.tryLoadOutput(); err != nil { - return err - } - if err := c.tryLoadTransformers(); err != nil { - return err - } - if err := c.tryLoadAlertSenders(); err != nil { - return err - } - } - return nil -} - -// TryLoadFile attempts to load the configuration file from specified path on the file system. -func (c *Config) TryLoadFile(file string) error { - c.viper.SetConfigFile(file) - return c.viper.ReadInConfig() -} - -// Validate ensures that all configuration options provided by user have the expected values. It returns -// a list of validation errors prefixed with the offending configuration property/flag. -func (c *Config) Validate() error { - // we'll first validate the structure and values of the config file - file := c.viper.GetString(configFile) - var out interface{} - b, err := ioutil.ReadFile(file) - if err != nil { - return err - } - switch filepath.Ext(file) { - case ".yaml", ".yml": - err = yaml.Unmarshal(b, &out) - case ".json": - err = json.Unmarshal(b, &out) - default: - return fmt.Errorf("%s is not a supported config file extension", filepath.Ext(file)) - } - if err != nil { - return fmt.Errorf("couldn't read the config file: %v", err) - } - // validate config file content - valid, errs := validate(interpolateSchema(), out) - if !valid || len(errs) > 0 { - return fmt.Errorf("invalid config: %v", multierror.Wrap(errs...)) - } - // now validate the Viper config flags - valid, errs = validate(interpolateSchema(), c.viper.AllSettings()) - if !valid || len(errs) > 0 { - return fmt.Errorf("invalid config: %v", multierror.Wrap(errs...)) - } - return nil + return c.init() } -// File returns the config file path. -func (c *Config) File() string { return c.viper.GetString(configFile) } - func (c *Config) addFlags() { c.flags.String(configFile, filepath.Join(os.Getenv("PROGRAMFILES"), "fibratus", "config", "fibratus.yml"), "Indicates the location of the configuration file") if c.opts.run || c.opts.replay { diff --git a/pkg/config/filters.go b/pkg/config/filters.go index 1b39d7547..ed6637e83 100644 --- a/pkg/config/filters.go +++ b/pkg/config/filters.go @@ -22,12 +22,6 @@ import ( "bytes" "encoding/base64" "fmt" - "github.com/rabbitstack/fibratus/pkg/filter/funcmap" - "github.com/rabbitstack/fibratus/pkg/kevent" - "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" - "github.com/rabbitstack/fibratus/pkg/util/multierror" - "github.com/spf13/viper" - "gopkg.in/yaml.v3" "io" "io/ioutil" "net/http" @@ -36,6 +30,13 @@ import ( "path/filepath" "strings" "text/template" + + "github.com/rabbitstack/fibratus/pkg/filter/funcmap" + "github.com/rabbitstack/fibratus/pkg/kevent" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" + "github.com/rabbitstack/fibratus/pkg/util/multierror" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" ) // FilterGroupPolicy is the type alias for the filter group policy @@ -446,3 +447,48 @@ func encodeFilterActions(buf []byte) ([]byte, error) { } return b, nil } + +var filterGroupSchema = ` +{ + "$schema": "http://json-schema.org/draft-07/schema#", + + "type": "object", + "properties": { + "group": {"type": "string", "minLength": 1}, + "selector": { + "type": "object", + "properties": { + "type": {"type": "string", "enum": ["CreateProcess", "CreateThread", "TerminateProcess", "TerminateThread", "LoadImage", "UnloadImage", "CreateFile", "CloseFile", "ReadFile", "WriteFile", "DeleteFile", "RenameFile", "SetFileInformation", "EnumDirectory", "RegCreateKey", "RegOpenKey", "RegSetValue", "RegQueryValue", "RegQueryKey", "RegDeleteKey", "RegDeleteValue", "Accept", "Send", "Recv", "Connect", "Disconnect", "Reconnect", "Retransmit", "CreateHandle", "CloseHandle"]}, + "category": {"type": "string", "enum": ["registry", "file", "net", "process", "thread", "image", "handle"]} + }, + "additionalProperties": false, + "oneOf": [ + {"required": ["type"]}, + {"required": ["category"]} + ] + }, + "enabled": {"type": "boolean"}, + "policy": {"type": "string", "enum": ["include", "exclude", "INCLUDE", "EXCLUDE"]}, + "relation": {"type": "string", "enum": ["or", "and", "OR", "AND"]}, + "tags": {"type": "array", "items": [{"type": "string", "minLength": 1}]}, + "from-strings": { + "type": "array", + "items": + { + "type": "object", + "properties": { + "name": {"type": "string", "minLength": 3}, + "def": {"type": "string", "minLength": 3}, + "action": {"type": "string"} + }, + "required": ["name", "def"], + "minItems": 1, + "additionalProperties": false + } + + } + }, + "required": ["group", "enabled", "selector", "from-strings"], + "additionalProperties": false +} +` diff --git a/pkg/config/kstream_linux.go b/pkg/config/kstream_linux.go new file mode 100644 index 000000000..10d359e28 --- /dev/null +++ b/pkg/config/kstream_linux.go @@ -0,0 +1,36 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import "github.com/spf13/viper" + +type KstreamConfig struct { + RingBufferSize int `json:"ring-buffer-size" yaml:"ring-buffer-size"` + Watermark int `json:"watermark" yaml:"watermark"` + VerifierLogsize int + EnableVerifierLogging bool + // BlacklistKevents are kernel event names that will be dropped from the kernel event stream. + BlacklistKevents []string `json:"blacklist.events" yaml:"blacklist.events"` + // BlacklistImages are process image names that will be rejected if they generate a kernel event. + BlacklistImages []string `json:"blacklist.images" yaml:"blacklist.images"` +} + +func (k *KstreamConfig) initFromViper(v *viper.Viper) { + +} diff --git a/pkg/config/kstream_test.go b/pkg/config/kstream_test.go index c75019ce0..029e5ccfb 100644 --- a/pkg/config/kstream_test.go +++ b/pkg/config/kstream_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/config/kstream.go b/pkg/config/kstream_windows.go similarity index 99% rename from pkg/config/kstream.go rename to pkg/config/kstream_windows.go index a57865498..26c06452a 100644 --- a/pkg/config/kstream.go +++ b/pkg/config/kstream_windows.go @@ -1,5 +1,3 @@ -// +build windows - /* * Copyright 2019-2020 by Nedim Sabic Sabic * https://www.fibratus.io diff --git a/pkg/config/output.go b/pkg/config/output.go index 45ea21d79..c0ffc338f 100644 --- a/pkg/config/output.go +++ b/pkg/config/output.go @@ -21,22 +21,23 @@ package config import ( "errors" "fmt" + "reflect" + "strconv" + "github.com/rabbitstack/fibratus/pkg/outputs" "github.com/rabbitstack/fibratus/pkg/outputs/amqp" "github.com/rabbitstack/fibratus/pkg/outputs/console" "github.com/rabbitstack/fibratus/pkg/outputs/elasticsearch" "github.com/rabbitstack/fibratus/pkg/outputs/null" + "github.com/rabbitstack/fibratus/pkg/util/service" log "github.com/sirupsen/logrus" - "golang.org/x/sys/windows/svc" - "reflect" - "strconv" ) var errNoOutputSection = errors.New("no output section in config") var errOutputConfig = func(output string, err error) error { return fmt.Errorf("%s output invalid config: %v", output, err) } -func (c *Config) tryLoadOutput() error { +func (c *BaseConfig) tryLoadOutput() error { output := c.viper.AllSettings()["output"] if output == nil { return errNoOutputSection @@ -106,8 +107,7 @@ func (c *Config) tryLoadOutput() error { // if it is not an interactive session but the console output is enabled // we default to null output and warn about that - in, err := svc.IsAnInteractiveSession() - if err == nil && !in && c.Output.Output != nil { + if !service.IsInteractive() && c.Output.Output != nil { if c.Output.Type == outputs.Console { log.Warn("running in non-interactive session with console output. " + "Please configure a different output type. Defaulting to null output") diff --git a/pkg/config/schema_linux.go b/pkg/config/schema_linux.go new file mode 100644 index 000000000..0c8726a72 --- /dev/null +++ b/pkg/config/schema_linux.go @@ -0,0 +1,23 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +func interpolateSchema() string { + return "" +} diff --git a/pkg/config/schema_windows.go b/pkg/config/schema_windows.go index 70d80846b..b244697d2 100644 --- a/pkg/config/schema_windows.go +++ b/pkg/config/schema_windows.go @@ -437,51 +437,6 @@ var schema = ` } ` -var filterGroupSchema = ` -{ - "$schema": "http://json-schema.org/draft-07/schema#", - - "type": "object", - "properties": { - "group": {"type": "string", "minLength": 1}, - "selector": { - "type": "object", - "properties": { - "type": {"type": "string", "enum": ["CreateProcess", "CreateThread", "TerminateProcess", "TerminateThread", "LoadImage", "UnloadImage", "CreateFile", "CloseFile", "ReadFile", "WriteFile", "DeleteFile", "RenameFile", "SetFileInformation", "EnumDirectory", "RegCreateKey", "RegOpenKey", "RegSetValue", "RegQueryValue", "RegQueryKey", "RegDeleteKey", "RegDeleteValue", "Accept", "Send", "Recv", "Connect", "Disconnect", "Reconnect", "Retransmit", "CreateHandle", "CloseHandle"]}, - "category": {"type": "string", "enum": ["registry", "file", "net", "process", "thread", "image", "handle"]} - }, - "additionalProperties": false, - "oneOf": [ - {"required": ["type"]}, - {"required": ["category"]} - ] - }, - "enabled": {"type": "boolean"}, - "policy": {"type": "string", "enum": ["include", "exclude", "INCLUDE", "EXCLUDE"]}, - "relation": {"type": "string", "enum": ["or", "and", "OR", "AND"]}, - "tags": {"type": "array", "items": [{"type": "string", "minLength": 1}]}, - "from-strings": { - "type": "array", - "items": - { - "type": "object", - "properties": { - "name": {"type": "string", "minLength": 3}, - "def": {"type": "string", "minLength": 3}, - "action": {"type": "string"} - }, - "required": ["name", "def"], - "minItems": 1, - "additionalProperties": false - } - - } - }, - "required": ["group", "enabled", "selector", "from-strings"], - "additionalProperties": false -} -` - type schemaConfig struct { MaxBuffers uint32 MinBuffers uint32 diff --git a/pkg/config/transformer.go b/pkg/config/transformer.go index 24b5df7d6..6d53eb467 100644 --- a/pkg/config/transformer.go +++ b/pkg/config/transformer.go @@ -20,18 +20,19 @@ package config import ( "fmt" + "reflect" + "github.com/rabbitstack/fibratus/pkg/aggregator/transformers" "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/remove" "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/rename" "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/replace" "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/tags" "github.com/rabbitstack/fibratus/pkg/aggregator/transformers/trim" - "reflect" ) var errTransformerConfig = func(t string, err error) error { return fmt.Errorf("%s transformer invalid config: %v", t, err) } -func (c *Config) tryLoadTransformers() error { +func (c *BaseConfig) tryLoadTransformers() error { transforms := c.viper.AllSettings()["transformers"] if transforms == nil { return nil diff --git a/pkg/ebpf/c/bpf/api.h b/pkg/ebpf/c/bpf/api.h new file mode 100644 index 000000000..2b403959d --- /dev/null +++ b/pkg/ebpf/c/bpf/api.h @@ -0,0 +1,92 @@ +#ifndef __BPF_HELPERS_H +#define __BPF_HELPERS_H + +/* helper macro to place programs, maps, license in + * different sections in elf_bpf file. Section names + * are interpreted by elf_bpf loader + */ +#define SEC(NAME) __attribute__((section(NAME), used)) + +/* helper functions called from eBPF programs */ + +static int (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) = + (void *) BPF_FUNC_trace_printk; + +/* macro for printing debug info to the tracing pipe, useful just for + debugging purposes and not recommended to use in production systems. + + use `sudo cat /sys/kernel/debug/tracing/trace_pipe` to read debug info. + */ +#define klog(fmt, ...) \ + ({ \ + char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \ + }) + +#define PERF_PUSH(map, evt) u32 cpu = bpf_get_smp_processor_id(); \ + bpf_perf_event_output(ctx, &map, \ + cpu, \ + &evt, sizeof(evt)); \ + +/* current task, pid, uid, command, timestamp, processor id */ +static unsigned long long (*bpf_get_smp_processor_id)(void) = + (void *) BPF_FUNC_get_smp_processor_id; +static unsigned long long (*bpf_get_current_pid_tgid)(void) = + (void *) BPF_FUNC_get_current_pid_tgid; +static unsigned long long (*bpf_get_current_uid_gid)(void) = + (void *) BPF_FUNC_get_current_uid_gid; +static int (*bpf_get_current_comm)(void *buf, int buf_size) = + (void *) BPF_FUNC_get_current_comm; +static long (*bpf_get_current_task)(void) = + (void *) BPF_FUNC_get_current_task; +static unsigned long long (*bpf_ktime_get_ns)(void) = + (void *) BPF_FUNC_ktime_get_ns; + +static int (*bpf_probe_read)(void *dst, int size, void *unsafe_ptr) = + (void *) BPF_FUNC_probe_read; + +static int (*bpf_probe_read_str)(void *dst, int size, const void *unsafe_ptr) = + (void *) BPF_FUNC_probe_read_str; + +/* eBPF maps */ +static void *(*bpf_map_lookup_elem)(void *map, void *key) = + (void *) BPF_FUNC_map_lookup_elem; +static int (*bpf_map_update_elem)(void *map, void *key, void *value, + unsigned long long flags) = + (void *) BPF_FUNC_map_update_elem; +static int (*bpf_map_delete_elem)(void *map, void *key) = + (void *) BPF_FUNC_map_delete_elem; + +static void (*bpf_tail_call)(void *ctx, void *map, int index) = + (void *)BPF_FUNC_tail_call; + +static int (*bpf_perf_event_output)(void *ctx, void *map, + unsigned long long flags, void *data, + int size) = + (void *) BPF_FUNC_perf_event_output; + +#define BUF_SIZE_MAP_NS 256 + +struct bpf_map_def { + unsigned int type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; + unsigned int pinning; + char namespace[BUF_SIZE_MAP_NS]; +}; + +#define PT_REGS_PARM1(x) ((x)->di) +#define PT_REGS_PARM2(x) ((x)->si) +#define PT_REGS_PARM3(x) ((x)->dx) +#define PT_REGS_PARM4(x) ((x)->cx) +#define PT_REGS_PARM5(x) ((x)->r8) +#define PT_REGS_PARM6(x) ((x)->r9) +#define PT_REGS_RET(x) ((x)->sp) +#define PT_REGS_FP(x) ((x)->bp) +#define PT_REGS_RC(x) ((x)->ax) +#define PT_REGS_SP(x) ((x)->sp) +#define PT_REGS_IP(x) ((x)->ip) + +#endif diff --git a/pkg/ebpf/c/kevent.h b/pkg/ebpf/c/kevent.h new file mode 100644 index 000000000..c72810816 --- /dev/null +++ b/pkg/ebpf/c/kevent.h @@ -0,0 +1,26 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +struct kevent_header { + u64 ts; /* timestamp in nanoseconds from epoch */ + u32 pid; /* process identifier that produced the event */ + u32 tid; /* thread identifier that produced the event */ + u32 cpu; /* logical core on which the event was generated */ + u32 nparams; /* number of event parameters */ + u16 type; /* event type which corresponds to syscall id */ +}; diff --git a/pkg/ebpf/c/kprobe.c b/pkg/ebpf/c/kprobe.c new file mode 100644 index 000000000..32bc0dd48 --- /dev/null +++ b/pkg/ebpf/c/kprobe.c @@ -0,0 +1,111 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/* In Linux 5.4 asm_inline was introduced, but it's not supported by clang. + * Redefine it to just asm to enable successful compilation. + */ + +#ifdef asm_volatile_goto +#undef asm_volatile_goto +#define asm_volatile_goto(...) asm volatile("invalid use of asm_volatile_goto") +#endif + +#ifdef asm_inline +#undef asm_inline +#define asm_inline asm +#endif +#include + +/* In Linux 5.4 asm_inline was introduced, but it's not supported by clang. + * Redefine it to just asm to enable successful compilation. + */ +#ifdef asm_inline +#undef asm_inline +#define asm_inline asm +#endif + +#include +#include + +#include "bpf/api.h" +#include "kevent.h" +#include "maps.h" +#include "syscall.h" + +#define _READ(P) ({ typeof(P) _val; \ + memset(&_val, 0, sizeof(_val)); \ + bpf_probe_read(&_val, sizeof(_val), &P); \ + _val; \ + }) + +void tail_call(struct sys_exit_args *ctx, long syscall_id) { + bpf_tail_call(ctx, &tracers, syscall_id); +} + +bool __attribute__((always_inline)) discard_pid(struct kevent_header *khdr) { + return bpf_map_lookup_elem(&pid_discarders, &khdr->pid) != NULL; +} + +SEC("raw_tracepoint/sys_exit") +int sys_exit_tracepoint(struct sys_exit_args *ctx) { + struct pt_regs *regs = (struct pt_regs *)ctx->regs; + + long id = _READ(regs->orig_ax); + + tail_call(ctx, id); + return 0; +} + +SEC("raw_tracepoint/sys_read") +int sys_read(struct sys_exit_args *ctx) { + int offset = sizeof(struct kevent_header); + u32 cpu = bpf_get_smp_processor_id(); + + u64 pid_tgid = bpf_get_current_pid_tgid(); + + struct pt_regs *regs = (struct pt_regs *)ctx->regs; + + long id = _READ(regs->orig_ax); + + char *buf = bpf_map_lookup_elem(&buffer_area, &cpu); + if (buf == NULL) { + return 0; + } + + struct kevent_header *khdr = (struct kevent_header *)buf; + khdr->ts = bpf_ktime_get_ns(); + khdr->pid = pid_tgid >> 32; + khdr->tid = pid_tgid & 0xffffffff; + khdr->cpu = cpu; + khdr->type = id; + + if (discard_pid(khdr)) { + return 0; + } + + int res = bpf_perf_event_output(ctx, + &perf, + BPF_F_CURRENT_CPU, + buf, offset & BUFFER_SIZE_MAX); + return 0; +} + +char _license[] SEC("license") = "GPL"; +__u32 _version SEC("version") = 0xFFFFFFFE; diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h new file mode 100644 index 000000000..12e802a20 --- /dev/null +++ b/pkg/ebpf/c/maps.h @@ -0,0 +1,59 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define BUFFER_SIZE (1 << 14) +#define BUFFER_SIZE_MAX (BUFFER_SIZE - 1) +#define BUFFER_SIZE_HALF (BUFFER_SIZE_MAX >> 1) + +struct bpf_map_def SEC("maps/stats") stats = { + .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u32), + .max_entries = 0, +}; + +/* This map stores tracer programs with the keys being the syscall identifier + * and the values being the eBPF program. + */ +struct bpf_map_def SEC("maps/tracers") tracers = { + .type = BPF_MAP_TYPE_PROG_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u32), + .max_entries = sizeof(u16), +}; + +struct bpf_map_def SEC("maps/pid_discarders") pid_discarders = { + .type = BPF_MAP_TYPE_LRU_HASH, + .key_size = sizeof(u32), + .value_size = sizeof(u32), + .max_entries = 32000, +}; + +struct bpf_map_def SEC("maps/buffer_area") buffer_area = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(u32), + .value_size = BUFFER_SIZE, + .max_entries = BUFFER_SIZE, +}; + +struct bpf_map_def SEC("maps/perf") perf = { + .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u32), + .max_entries = 0, +}; diff --git a/pkg/ebpf/c/read.h b/pkg/ebpf/c/read.h new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/ebpf/c/syscall.h b/pkg/ebpf/c/syscall.h new file mode 100644 index 000000000..cb665af04 --- /dev/null +++ b/pkg/ebpf/c/syscall.h @@ -0,0 +1,22 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +struct sys_exit_args { + unsigned long regs; + unsigned long ret; +}; \ No newline at end of file diff --git a/pkg/errors/errors_linux.go b/pkg/errors/errors_linux.go new file mode 100644 index 000000000..d8aa81c28 --- /dev/null +++ b/pkg/errors/errors_linux.go @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package errors + +import "errors" + +var ( + // ErrNotRoot represents the error that is returned when the operation fails + // due to the lack of root privileges. + ErrNotRoot = errors.New("must be run as root") +) diff --git a/pkg/filament/filament.go b/pkg/filament/filament.go index 30d817699..b189bc040 100644 --- a/pkg/filament/filament.go +++ b/pkg/filament/filament.go @@ -1,3 +1,4 @@ +//go:build filament && windows // +build filament,windows /* diff --git a/pkg/filament/filament_test.go b/pkg/filament/filament_test.go index 5e0ac825e..19fe09359 100644 --- a/pkg/filament/filament_test.go +++ b/pkg/filament/filament_test.go @@ -1,3 +1,4 @@ +//go:build filament && windows // +build filament,windows /* diff --git a/pkg/filament/filament_unsupported.go b/pkg/filament/filament_unsupported.go index 376738a33..fd5f83b0d 100644 --- a/pkg/filament/filament_unsupported.go +++ b/pkg/filament/filament_unsupported.go @@ -1,3 +1,4 @@ +//go:build !filament // +build !filament /* diff --git a/pkg/filament/kdict.go b/pkg/filament/kdict.go index 316e6cbeb..4550c2f60 100644 --- a/pkg/filament/kdict.go +++ b/pkg/filament/kdict.go @@ -1,3 +1,4 @@ +//go:build filament && windows // +build filament,windows /* diff --git a/pkg/filament/kdict_test.go b/pkg/filament/kdict_test.go index 0d4bb77e0..22c184ca4 100644 --- a/pkg/filament/kdict_test.go +++ b/pkg/filament/kdict_test.go @@ -1,3 +1,4 @@ +//go:build filament && windows // +build filament,windows /* diff --git a/pkg/filament/table.go b/pkg/filament/table.go index a21037c23..88445755b 100644 --- a/pkg/filament/table.go +++ b/pkg/filament/table.go @@ -1,3 +1,4 @@ +//go:build filament && windows // +build filament,windows /* diff --git a/pkg/filament/table_test.go b/pkg/filament/table_test.go index 7d1173f26..a3e77320d 100644 --- a/pkg/filament/table_test.go +++ b/pkg/filament/table_test.go @@ -1,3 +1,4 @@ +//go:build filament && windows // +build filament,windows /* diff --git a/pkg/filter/accessor.go b/pkg/filter/accessor.go index d76b6c126..b2ba71421 100644 --- a/pkg/filter/accessor.go +++ b/pkg/filter/accessor.go @@ -19,17 +19,10 @@ package filter import ( - "errors" "github.com/rabbitstack/fibratus/pkg/filter/fields" - "github.com/rabbitstack/fibratus/pkg/fs" "github.com/rabbitstack/fibratus/pkg/kevent" "github.com/rabbitstack/fibratus/pkg/kevent/kparams" - "github.com/rabbitstack/fibratus/pkg/network" - "github.com/rabbitstack/fibratus/pkg/pe" pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" - "path/filepath" - "strconv" - "strings" ) // accessor dictates the behaviour of the field accessors. One of the main responsibilities of the accessor is @@ -40,21 +33,6 @@ type accessor interface { get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) } -// getAccessors initializes and returns all available accessors. -func getAccessors() []accessor { - return []accessor{ - newPSAccessor(), - newPEAccessor(), - newFileAccessor(), - newKevtAccessor(), - newImageAccessor(), - newThreadAccessor(), - newHandleAccessor(), - newNetworkAccessor(), - newRegistryAccessor(), - } -} - // kevtAccessor extracts kernel event specific values. type kevtAccessor struct{} @@ -122,618 +100,3 @@ func (k *kevtAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, return nil, nil } } - -// psAccessor extracts process's state or kevent specific values. -type psAccessor struct{} - -func newPSAccessor() accessor { return &psAccessor{} } - -func (ps *psAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { - case fields.PsPid: - return kevt.PID, nil - case fields.PsPpid: - ps := kevt.PS - if ps == nil { - return kevt.Kparams.GetPpid() - } - return ps.Ppid, nil - case fields.PsName: - ps := kevt.PS - if ps == nil || ps.Name == "" { - return kevt.Kparams.GetString(kparams.ProcessName) - } - return ps.Name, nil - case fields.PsComm: - ps := kevt.PS - if ps == nil { - return kevt.Kparams.GetString(kparams.Comm) - } - return ps.Comm, nil - case fields.PsExe: - ps := kevt.PS - if ps == nil { - return nil, nil - } - return ps.Exe, nil - case fields.PsArgs: - ps := kevt.PS - if ps == nil { - return nil, nil - } - return ps.Args, nil - case fields.PsCwd: - ps := kevt.PS - if ps == nil { - return nil, nil - } - return ps.Cwd, nil - case fields.PsSID: - ps := kevt.PS - if ps == nil { - return nil, nil - } - return ps.SID, nil - case fields.PsSessionID: - ps := kevt.PS - if ps == nil { - return nil, nil - } - return ps.SessionID, nil - case fields.PsEnvs: - ps := kevt.PS - if ps == nil { - return nil, nil - } - envs := make([]string, 0, len(ps.Envs)) - for env := range ps.Envs { - envs = append(envs, env) - } - return envs, nil - case fields.PsModules: - ps := kevt.PS - if ps == nil { - return nil, nil - } - mods := make([]string, 0, len(ps.Modules)) - for _, m := range ps.Modules { - mods = append(mods, filepath.Base(m.Name)) - } - return mods, nil - case fields.PsHandles: - ps := kevt.PS - if ps == nil { - return nil, nil - } - handles := make([]string, len(ps.Handles)) - for i, handle := range ps.Handles { - handles[i] = handle.Name - } - return handles, nil - case fields.PsHandleTypes: - ps := kevt.PS - if ps == nil { - return nil, nil - } - types := make([]string, len(ps.Handles)) - for i, handle := range ps.Handles { - if types[i] == handle.Type { - continue - } - types[i] = handle.Type - } - return types, nil - case fields.PsParentName: - parent := getParentPs(kevt) - if parent == nil { - return nil, nil - } - return parent.Name, nil - case fields.PsParentComm: - parent := getParentPs(kevt) - if parent == nil { - return nil, nil - } - return parent.Comm, nil - case fields.PsParentExe: - parent := getParentPs(kevt) - if parent == nil { - return nil, nil - } - return parent.Exe, nil - case fields.PsParentArgs: - parent := getParentPs(kevt) - if parent == nil { - return nil, nil - } - return parent.Args, nil - case fields.PsParentCwd: - parent := getParentPs(kevt) - if parent == nil { - return nil, nil - } - return parent.Cwd, nil - case fields.PsParentSID: - parent := getParentPs(kevt) - if parent == nil { - return nil, nil - } - return parent.SID, nil - case fields.PsParentSessionID: - parent := getParentPs(kevt) - if parent == nil { - return nil, nil - } - return parent.SessionID, nil - case fields.PsParentEnvs: - ps := getParentPs(kevt) - if ps == nil { - return nil, nil - } - envs := make([]string, 0, len(ps.Envs)) - for env := range ps.Envs { - envs = append(envs, env) - } - return envs, nil - case fields.PsParentHandles: - ps := getParentPs(kevt) - if ps == nil { - return nil, nil - } - handles := make([]string, len(ps.Handles)) - for i, handle := range ps.Handles { - handles[i] = handle.Name - } - return handles, nil - case fields.PsParentHandleTypes: - ps := getParentPs(kevt) - if ps == nil { - return nil, nil - } - types := make([]string, len(ps.Handles)) - for i, handle := range ps.Handles { - if types[i] == handle.Type { - continue - } - types[i] = handle.Type - } - return types, nil - default: - switch { - case f.IsEnvsSequence(): - // access the specific environment variable - env, _ := captureInBrackets(f.String()) - ps := kevt.PS - if ps == nil { - return nil, nil - } - v, ok := ps.Envs[env] - if ok { - return v, nil - } - // match on prefix - for k, v := range ps.Envs { - if strings.HasPrefix(k, env) { - return v, nil - } - } - case f.IsModsSequence(): - name, segment := captureInBrackets(f.String()) - ps := kevt.PS - if ps == nil { - return nil, nil - } - mod := ps.FindModule(name) - if mod == nil { - return nil, nil - } - - switch segment { - case fields.ModuleSize: - return mod.Size, nil - case fields.ModuleChecksum: - return mod.Checksum, nil - case fields.ModuleBaseAddress: - return mod.BaseAddress.String(), nil - case fields.ModuleDefaultAddress: - return mod.DefaultBaseAddress.String(), nil - case fields.ModuleLocation: - return filepath.Dir(mod.Name), nil - } - case f.IsAncestorSequence(): - return ancestorFields(f.String(), kevt) - } - - return nil, nil - } -} - -// ancestorFields recursively walks the process ancestors and extracts -// the required field values. If we get the `root` key, the root ancestor -// fields are inspected, while `any` accumulates values of all ancestors. -// Alternatively, the key may represent the depth that only returns the -// ancestor located at the given depth, starting with 1 which is the immediate -// process parent. -func ancestorFields(field string, kevt *kevent.Kevent) (kparams.Value, error) { - key, segment := captureInBrackets(field) - if key == "" || segment == "" { - return nil, nil - } - - var ps *pstypes.PS - - switch key { - case "root": - pstypes.Walk(func(proc *pstypes.PS) { - ps = proc - }, kevt.PS) - - case "any": - values := make([]string, 0) - pstypes.Walk(func(ps *pstypes.PS) { - switch segment { - case fields.ProcessName: - values = append(values, ps.Name) - case fields.ProcessID: - values = append(values, strconv.Itoa(int(ps.PID))) - case fields.ProcessSID: - values = append(values, ps.SID) - case fields.ProcessSessionID: - values = append(values, strconv.Itoa(int(ps.SessionID))) - case fields.ProcessCwd: - values = append(values, ps.Cwd) - case fields.ProcessComm: - values = append(values, ps.Comm) - case fields.ProcessArgs: - values = append(values, ps.Args...) - case fields.ProcessExe: - values = append(values, ps.Exe) - } - }, kevt.PS) - - return values, nil - - default: - depth, err := strconv.Atoi(key) - if err != nil { - return nil, err - } - var i int - pstypes.Walk(func(proc *pstypes.PS) { - i++ - if i == depth { - ps = proc - } - }, kevt.PS) - } - - if ps == nil { - return nil, nil - } - - switch segment { - case fields.ProcessName: - return ps.Name, nil - case fields.ProcessID: - return ps.PID, nil - case fields.ProcessSID: - return ps.SID, nil - case fields.ProcessSessionID: - return ps.SessionID, nil - case fields.ProcessCwd: - return ps.Cwd, nil - case fields.ProcessComm: - return ps.Comm, nil - case fields.ProcessArgs: - return ps.Args, nil - case fields.ProcessExe: - return ps.Exe, nil - } - - return nil, nil -} - -// threadAccessor fetches thread parameters from thread kernel events. -type threadAccessor struct{} - -func newThreadAccessor() accessor { - return &threadAccessor{} -} - -func (t *threadAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { - case fields.ThreadBasePrio: - return kevt.Kparams.GetUint8(kparams.BasePrio) - case fields.ThreadIOPrio: - return kevt.Kparams.GetUint8(kparams.IOPrio) - case fields.ThreadPagePrio: - return kevt.Kparams.GetUint8(kparams.PagePrio) - case fields.ThreadKstackBase: - v, err := kevt.Kparams.GetHex(kparams.KstackBase) - if err != nil { - return nil, err - } - return v.String(), nil - case fields.ThreadKstackLimit: - v, err := kevt.Kparams.GetHex(kparams.KstackLimit) - if err != nil { - return nil, err - } - return v.String(), nil - case fields.ThreadUstackBase: - v, err := kevt.Kparams.GetHex(kparams.UstackBase) - if err != nil { - return nil, err - } - return v.String(), nil - case fields.ThreadUstackLimit: - v, err := kevt.Kparams.GetHex(kparams.UstackLimit) - if err != nil { - return nil, err - } - return v.String(), nil - case fields.ThreadEntrypoint: - v, err := kevt.Kparams.GetHex(kparams.ThreadEntrypoint) - if err != nil { - return nil, err - } - return v.String(), nil - case fields.ThreadPID: - return kevt.Kparams.GetUint32(kparams.ProcessID) - } - return nil, nil -} - -// fileAccessor extracts file specific values. -type fileAccessor struct{} - -func newFileAccessor() accessor { - return &fileAccessor{} -} - -func (l *fileAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { - case fields.FileName: - return kevt.Kparams.GetString(kparams.FileName) - case fields.FileOffset: - return kevt.Kparams.GetUint64(kparams.FileOffset) - case fields.FileIOSize: - return kevt.Kparams.GetUint32(kparams.FileIoSize) - case fields.FileShareMask: - m, err := kevt.Kparams.Get(kparams.FileShareMask) - if err != nil { - return nil, err - } - mode, ok := m.(fs.FileShareMode) - if !ok { - return nil, errors.New("couldn't type assert to file share mode enum") - } - return mode.String(), nil - case fields.FileOperation: - op, err := kevt.Kparams.Get(kparams.FileOperation) - if err != nil { - return nil, err - } - fop, ok := op.(fs.FileDisposition) - if !ok { - return nil, errors.New("couldn't type assert to file operation enum") - } - return fop.String(), nil - case fields.FileObject: - return kevt.Kparams.GetUint64(kparams.FileObject) - case fields.FileType: - return kevt.Kparams.GetString(kparams.FileType) - case fields.FileExtension: - file, err := kevt.Kparams.GetString(kparams.FileName) - if err != nil { - return nil, err - } - return filepath.Ext(file), nil - } - return nil, nil -} - -// imageAccessor extracts image (DLL) kevent values. -type imageAccessor struct{} - -func newImageAccessor() accessor { - return &imageAccessor{} -} - -func (i *imageAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { - case fields.ImageName: - return kevt.Kparams.GetString(kparams.ImageFilename) - case fields.ImageDefaultAddress: - address, err := kevt.Kparams.GetHex(kparams.ImageDefaultBase) - if err != nil { - return nil, err - } - return address.String(), nil - case fields.ImageBase: - address, err := kevt.Kparams.GetHex(kparams.ImageBase) - if err != nil { - return nil, err - } - return address.String(), nil - case fields.ImageSize: - return kevt.Kparams.GetUint32(kparams.ImageSize) - case fields.ImageChecksum: - return kevt.Kparams.GetUint32(kparams.ImageCheckSum) - case fields.ImagePID: - return kevt.Kparams.GetPid() - } - return nil, nil -} - -// registryAccessor extracts registry specific parameters. -type registryAccessor struct{} - -func newRegistryAccessor() accessor { - return ®istryAccessor{} -} - -func (r *registryAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { - case fields.RegistryKeyName: - return kevt.Kparams.GetString(kparams.RegKeyName) - case fields.RegistryKeyHandle: - keyHandle, err := kevt.Kparams.GetHex(kparams.RegKeyHandle) - if err != nil { - return nil, err - } - return keyHandle.String(), nil - case fields.RegistryValue: - return kevt.Kparams.Get(kparams.RegValue) - case fields.RegistryValueType: - return kevt.Kparams.GetString(kparams.RegValueType) - case fields.RegistryStatus: - return kevt.Kparams.GetString(kparams.NTStatus) - } - return nil, nil -} - -// networkAccessor deals with extracting the network specific kernel event parameters. -type networkAccessor struct{} - -func newNetworkAccessor() accessor { return &networkAccessor{} } - -func (n *networkAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { - case fields.NetDIP: - return kevt.Kparams.GetIP(kparams.NetDIP) - case fields.NetSIP: - return kevt.Kparams.GetIP(kparams.NetSIP) - case fields.NetDport: - return kevt.Kparams.GetUint16(kparams.NetDport) - case fields.NetSport: - return kevt.Kparams.GetUint16(kparams.NetSport) - case fields.NetDportName: - return kevt.Kparams.GetString(kparams.NetDportName) - case fields.NetSportName: - return kevt.Kparams.GetString(kparams.NetSportName) - case fields.NetL4Proto: - v, err := kevt.Kparams.Get(kparams.NetL4Proto) - if err != nil { - return nil, err - } - l4proto, ok := v.(network.L4Proto) - if !ok { - return nil, errors.New("couldn't type assert to L4 proto enum") - } - return l4proto.String(), nil - case fields.NetPacketSize: - return kevt.Kparams.GetUint32(kparams.NetSize) - case fields.NetSIPNames: - return kevt.Kparams.GetStringSlice(kparams.NetSIPNames) - case fields.NetDIPNames: - return kevt.Kparams.GetStringSlice(kparams.NetDIPNames) - } - return nil, nil -} - -// handleAccessor extracts handle event values. -type handleAccessor struct{} - -func newHandleAccessor() accessor { return &handleAccessor{} } - -func (h *handleAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { - case fields.HandleID: - return kevt.Kparams.GetHexAsUint32(kparams.HandleID) - case fields.HandleType: - return kevt.Kparams.GetString(kparams.HandleObjectTypeName) - case fields.HandleName: - return kevt.Kparams.GetString(kparams.HandleObjectName) - case fields.HandleObject: - handleObject, err := kevt.Kparams.GetHex(kparams.HandleObject) - if err != nil { - return nil, err - } - return handleObject.String(), nil - } - return nil, nil -} - -// peAccessor extracts PE specific values. -type peAccessor struct{} - -func newPEAccessor() accessor { - return &peAccessor{} -} - -func (*peAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - var p *pe.PE - if kevt.PS != nil && kevt.PS.PE != nil { - p = kevt.PS.PE - } - if p == nil { - return nil, nil - } - - switch f { - case fields.PeEntrypoint: - return p.EntryPoint, nil - case fields.PeBaseAddress: - return p.ImageBase, nil - case fields.PeNumSections: - return p.NumberOfSections, nil - case fields.PeNumSymbols: - return p.NumberOfSymbols, nil - case fields.PeSymbols: - return p.Symbols, nil - case fields.PeImports: - return p.Imports, nil - default: - switch { - case f.IsPeSectionsSequence(): - // get the section name - sname, segment := captureInBrackets(f.String()) - sec := p.Section(sname) - if sec == nil { - return nil, nil - } - switch segment { - case fields.SectionEntropy: - return sec.Entropy, nil - case fields.SectionMD5Hash: - return sec.Md5, nil - case fields.SectionSize: - return sec.Size, nil - } - case f.IsPeResourcesSequence(): - // consult the resource name - key, _ := captureInBrackets(f.String()) - v, ok := p.VersionResources[key] - if ok { - return v, nil - } - // match on prefix (e.g. pe.resources[Org] = Blackwater) - for k, v := range p.VersionResources { - if strings.HasPrefix(k, key) { - return v, nil - } - } - } - } - - return nil, nil -} - -func captureInBrackets(s string) (string, fields.Segment) { - lbracket := strings.Index(s, "[") - if lbracket == -1 { - return "", "" - } - rbracket := strings.Index(s, "]") - if rbracket == -1 { - return "", "" - } - if lbracket+1 > len(s) { - return "", "" - } - if rbracket+2 < len(s) { - return s[lbracket+1 : rbracket], fields.Segment(s[rbracket+2:]) - } - return s[lbracket+1 : rbracket], "" -} diff --git a/pkg/filter/accessor_windows.go b/pkg/filter/accessor_windows.go new file mode 100644 index 000000000..f5e1cd60e --- /dev/null +++ b/pkg/filter/accessor_windows.go @@ -0,0 +1,663 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter + +import ( + "errors" + "github.com/rabbitstack/fibratus/pkg/filter/fields" + "github.com/rabbitstack/fibratus/pkg/fs" + "github.com/rabbitstack/fibratus/pkg/kevent" + "github.com/rabbitstack/fibratus/pkg/kevent/kparams" + "github.com/rabbitstack/fibratus/pkg/network" + "github.com/rabbitstack/fibratus/pkg/pe" + pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" + "path/filepath" + "strconv" + "strings" +) + +// getAccessors initializes and returns all available accessors. +func getAccessors() []accessor { + return []accessor{ + newPSAccessor(), + newPEAccessor(), + newFileAccessor(), + newKevtAccessor(), + newImageAccessor(), + newThreadAccessor(), + newHandleAccessor(), + newNetworkAccessor(), + newRegistryAccessor(), + } +} + +// psAccessor extracts process's state or kevent specific values. +type psAccessor struct{} + +func newPSAccessor() accessor { return &psAccessor{} } + +func (ps *psAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f { + case fields.PsPid: + return kevt.PID, nil + case fields.PsPpid: + ps := kevt.PS + if ps == nil { + return kevt.Kparams.GetPpid() + } + return ps.Ppid, nil + case fields.PsName: + ps := kevt.PS + if ps == nil || ps.Name == "" { + return kevt.Kparams.GetString(kparams.ProcessName) + } + return ps.Name, nil + case fields.PsComm: + ps := kevt.PS + if ps == nil { + return kevt.Kparams.GetString(kparams.Comm) + } + return ps.Comm, nil + case fields.PsExe: + ps := kevt.PS + if ps == nil { + return nil, nil + } + return ps.Exe, nil + case fields.PsArgs: + ps := kevt.PS + if ps == nil { + return nil, nil + } + return ps.Args, nil + case fields.PsCwd: + ps := kevt.PS + if ps == nil { + return nil, nil + } + return ps.Cwd, nil + case fields.PsSID: + ps := kevt.PS + if ps == nil { + return nil, nil + } + return ps.SID, nil + case fields.PsSessionID: + ps := kevt.PS + if ps == nil { + return nil, nil + } + return ps.SessionID, nil + case fields.PsEnvs: + ps := kevt.PS + if ps == nil { + return nil, nil + } + envs := make([]string, 0, len(ps.Envs)) + for env := range ps.Envs { + envs = append(envs, env) + } + return envs, nil + case fields.PsModules: + ps := kevt.PS + if ps == nil { + return nil, nil + } + mods := make([]string, 0, len(ps.Modules)) + for _, m := range ps.Modules { + mods = append(mods, filepath.Base(m.Name)) + } + return mods, nil + case fields.PsHandles: + ps := kevt.PS + if ps == nil { + return nil, nil + } + handles := make([]string, len(ps.Handles)) + for i, handle := range ps.Handles { + handles[i] = handle.Name + } + return handles, nil + case fields.PsHandleTypes: + ps := kevt.PS + if ps == nil { + return nil, nil + } + types := make([]string, len(ps.Handles)) + for i, handle := range ps.Handles { + if types[i] == handle.Type { + continue + } + types[i] = handle.Type + } + return types, nil + case fields.PsParentName: + parent := getParentPs(kevt) + if parent == nil { + return nil, nil + } + return parent.Name, nil + case fields.PsParentComm: + parent := getParentPs(kevt) + if parent == nil { + return nil, nil + } + return parent.Comm, nil + case fields.PsParentExe: + parent := getParentPs(kevt) + if parent == nil { + return nil, nil + } + return parent.Exe, nil + case fields.PsParentArgs: + parent := getParentPs(kevt) + if parent == nil { + return nil, nil + } + return parent.Args, nil + case fields.PsParentCwd: + parent := getParentPs(kevt) + if parent == nil { + return nil, nil + } + return parent.Cwd, nil + case fields.PsParentSID: + parent := getParentPs(kevt) + if parent == nil { + return nil, nil + } + return parent.SID, nil + case fields.PsParentSessionID: + parent := getParentPs(kevt) + if parent == nil { + return nil, nil + } + return parent.SessionID, nil + case fields.PsParentEnvs: + ps := getParentPs(kevt) + if ps == nil { + return nil, nil + } + envs := make([]string, 0, len(ps.Envs)) + for env := range ps.Envs { + envs = append(envs, env) + } + return envs, nil + case fields.PsParentHandles: + ps := getParentPs(kevt) + if ps == nil { + return nil, nil + } + handles := make([]string, len(ps.Handles)) + for i, handle := range ps.Handles { + handles[i] = handle.Name + } + return handles, nil + case fields.PsParentHandleTypes: + ps := getParentPs(kevt) + if ps == nil { + return nil, nil + } + types := make([]string, len(ps.Handles)) + for i, handle := range ps.Handles { + if types[i] == handle.Type { + continue + } + types[i] = handle.Type + } + return types, nil + default: + switch { + case f.IsEnvsSequence(): + // access the specific environment variable + env, _ := captureInBrackets(f.String()) + ps := kevt.PS + if ps == nil { + return nil, nil + } + v, ok := ps.Envs[env] + if ok { + return v, nil + } + // match on prefix + for k, v := range ps.Envs { + if strings.HasPrefix(k, env) { + return v, nil + } + } + case f.IsModsSequence(): + name, segment := captureInBrackets(f.String()) + ps := kevt.PS + if ps == nil { + return nil, nil + } + mod := ps.FindModule(name) + if mod == nil { + return nil, nil + } + + switch segment { + case fields.ModuleSize: + return mod.Size, nil + case fields.ModuleChecksum: + return mod.Checksum, nil + case fields.ModuleBaseAddress: + return mod.BaseAddress.String(), nil + case fields.ModuleDefaultAddress: + return mod.DefaultBaseAddress.String(), nil + case fields.ModuleLocation: + return filepath.Dir(mod.Name), nil + } + case f.IsAncestorSequence(): + return ancestorFields(f.String(), kevt) + } + + return nil, nil + } +} + +// ancestorFields recursively walks the process ancestors and extracts +// the required field values. If we get the `root` key, the root ancestor +// fields are inspected, while `any` accumulates values of all ancestors. +// Alternatively, the key may represent the depth that only returns the +// ancestor located at the given depth, starting with 1 which is the immediate +// process parent. +func ancestorFields(field string, kevt *kevent.Kevent) (kparams.Value, error) { + key, segment := captureInBrackets(field) + if key == "" || segment == "" { + return nil, nil + } + + var ps *pstypes.PS + + switch key { + case "root": + pstypes.Walk(func(proc *pstypes.PS) { + ps = proc + }, kevt.PS) + + case "any": + values := make([]string, 0) + pstypes.Walk(func(ps *pstypes.PS) { + switch segment { + case fields.ProcessName: + values = append(values, ps.Name) + case fields.ProcessID: + values = append(values, strconv.Itoa(int(ps.PID))) + case fields.ProcessSID: + values = append(values, ps.SID) + case fields.ProcessSessionID: + values = append(values, strconv.Itoa(int(ps.SessionID))) + case fields.ProcessCwd: + values = append(values, ps.Cwd) + case fields.ProcessComm: + values = append(values, ps.Comm) + case fields.ProcessArgs: + values = append(values, ps.Args...) + case fields.ProcessExe: + values = append(values, ps.Exe) + } + }, kevt.PS) + + return values, nil + + default: + depth, err := strconv.Atoi(key) + if err != nil { + return nil, err + } + var i int + pstypes.Walk(func(proc *pstypes.PS) { + i++ + if i == depth { + ps = proc + } + }, kevt.PS) + } + + if ps == nil { + return nil, nil + } + + switch segment { + case fields.ProcessName: + return ps.Name, nil + case fields.ProcessID: + return ps.PID, nil + case fields.ProcessSID: + return ps.SID, nil + case fields.ProcessSessionID: + return ps.SessionID, nil + case fields.ProcessCwd: + return ps.Cwd, nil + case fields.ProcessComm: + return ps.Comm, nil + case fields.ProcessArgs: + return ps.Args, nil + case fields.ProcessExe: + return ps.Exe, nil + } + + return nil, nil +} + +// threadAccessor fetches thread parameters from thread kernel events. +type threadAccessor struct{} + +func newThreadAccessor() accessor { + return &threadAccessor{} +} + +func (t *threadAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f { + case fields.ThreadBasePrio: + return kevt.Kparams.GetUint8(kparams.BasePrio) + case fields.ThreadIOPrio: + return kevt.Kparams.GetUint8(kparams.IOPrio) + case fields.ThreadPagePrio: + return kevt.Kparams.GetUint8(kparams.PagePrio) + case fields.ThreadKstackBase: + v, err := kevt.Kparams.GetHex(kparams.KstackBase) + if err != nil { + return nil, err + } + return v.String(), nil + case fields.ThreadKstackLimit: + v, err := kevt.Kparams.GetHex(kparams.KstackLimit) + if err != nil { + return nil, err + } + return v.String(), nil + case fields.ThreadUstackBase: + v, err := kevt.Kparams.GetHex(kparams.UstackBase) + if err != nil { + return nil, err + } + return v.String(), nil + case fields.ThreadUstackLimit: + v, err := kevt.Kparams.GetHex(kparams.UstackLimit) + if err != nil { + return nil, err + } + return v.String(), nil + case fields.ThreadEntrypoint: + v, err := kevt.Kparams.GetHex(kparams.ThreadEntrypoint) + if err != nil { + return nil, err + } + return v.String(), nil + case fields.ThreadPID: + return kevt.Kparams.GetUint32(kparams.ProcessID) + } + return nil, nil +} + +// fileAccessor extracts file specific values. +type fileAccessor struct{} + +func newFileAccessor() accessor { + return &fileAccessor{} +} + +func (l *fileAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f { + case fields.FileName: + return kevt.Kparams.GetString(kparams.FileName) + case fields.FileOffset: + return kevt.Kparams.GetUint64(kparams.FileOffset) + case fields.FileIOSize: + return kevt.Kparams.GetUint32(kparams.FileIoSize) + case fields.FileShareMask: + m, err := kevt.Kparams.Get(kparams.FileShareMask) + if err != nil { + return nil, err + } + mode, ok := m.(fs.FileShareMode) + if !ok { + return nil, errors.New("couldn't type assert to file share mode enum") + } + return mode.String(), nil + case fields.FileOperation: + op, err := kevt.Kparams.Get(kparams.FileOperation) + if err != nil { + return nil, err + } + fop, ok := op.(fs.FileDisposition) + if !ok { + return nil, errors.New("couldn't type assert to file operation enum") + } + return fop.String(), nil + case fields.FileObject: + return kevt.Kparams.GetUint64(kparams.FileObject) + case fields.FileType: + return kevt.Kparams.GetString(kparams.FileType) + case fields.FileExtension: + file, err := kevt.Kparams.GetString(kparams.FileName) + if err != nil { + return nil, err + } + return filepath.Ext(file), nil + } + return nil, nil +} + +// imageAccessor extracts image (DLL) kevent values. +type imageAccessor struct{} + +func newImageAccessor() accessor { + return &imageAccessor{} +} + +func (i *imageAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f { + case fields.ImageName: + return kevt.Kparams.GetString(kparams.ImageFilename) + case fields.ImageDefaultAddress: + address, err := kevt.Kparams.GetHex(kparams.ImageDefaultBase) + if err != nil { + return nil, err + } + return address.String(), nil + case fields.ImageBase: + address, err := kevt.Kparams.GetHex(kparams.ImageBase) + if err != nil { + return nil, err + } + return address.String(), nil + case fields.ImageSize: + return kevt.Kparams.GetUint32(kparams.ImageSize) + case fields.ImageChecksum: + return kevt.Kparams.GetUint32(kparams.ImageCheckSum) + case fields.ImagePID: + return kevt.Kparams.GetPid() + } + return nil, nil +} + +// registryAccessor extracts registry specific parameters. +type registryAccessor struct{} + +func newRegistryAccessor() accessor { + return ®istryAccessor{} +} + +func (r *registryAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f { + case fields.RegistryKeyName: + return kevt.Kparams.GetString(kparams.RegKeyName) + case fields.RegistryKeyHandle: + keyHandle, err := kevt.Kparams.GetHex(kparams.RegKeyHandle) + if err != nil { + return nil, err + } + return keyHandle.String(), nil + case fields.RegistryValue: + return kevt.Kparams.Get(kparams.RegValue) + case fields.RegistryValueType: + return kevt.Kparams.GetString(kparams.RegValueType) + case fields.RegistryStatus: + return kevt.Kparams.GetString(kparams.NTStatus) + } + return nil, nil +} + +// networkAccessor deals with extracting the network specific kernel event parameters. +type networkAccessor struct{} + +func newNetworkAccessor() accessor { return &networkAccessor{} } + +func (n *networkAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f { + case fields.NetDIP: + return kevt.Kparams.GetIP(kparams.NetDIP) + case fields.NetSIP: + return kevt.Kparams.GetIP(kparams.NetSIP) + case fields.NetDport: + return kevt.Kparams.GetUint16(kparams.NetDport) + case fields.NetSport: + return kevt.Kparams.GetUint16(kparams.NetSport) + case fields.NetDportName: + return kevt.Kparams.GetString(kparams.NetDportName) + case fields.NetSportName: + return kevt.Kparams.GetString(kparams.NetSportName) + case fields.NetL4Proto: + v, err := kevt.Kparams.Get(kparams.NetL4Proto) + if err != nil { + return nil, err + } + l4proto, ok := v.(network.L4Proto) + if !ok { + return nil, errors.New("couldn't type assert to L4 proto enum") + } + return l4proto.String(), nil + case fields.NetPacketSize: + return kevt.Kparams.GetUint32(kparams.NetSize) + case fields.NetSIPNames: + return kevt.Kparams.GetStringSlice(kparams.NetSIPNames) + case fields.NetDIPNames: + return kevt.Kparams.GetStringSlice(kparams.NetDIPNames) + } + return nil, nil +} + +// handleAccessor extracts handle event values. +type handleAccessor struct{} + +func newHandleAccessor() accessor { return &handleAccessor{} } + +func (h *handleAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f { + case fields.HandleID: + return kevt.Kparams.GetHexAsUint32(kparams.HandleID) + case fields.HandleType: + return kevt.Kparams.GetString(kparams.HandleObjectTypeName) + case fields.HandleName: + return kevt.Kparams.GetString(kparams.HandleObjectName) + case fields.HandleObject: + handleObject, err := kevt.Kparams.GetHex(kparams.HandleObject) + if err != nil { + return nil, err + } + return handleObject.String(), nil + } + return nil, nil +} + +// peAccessor extracts PE specific values. +type peAccessor struct{} + +func newPEAccessor() accessor { + return &peAccessor{} +} + +func (*peAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + var p *pe.PE + if kevt.PS != nil && kevt.PS.PE != nil { + p = kevt.PS.PE + } + if p == nil { + return nil, nil + } + + switch f { + case fields.PeEntrypoint: + return p.EntryPoint, nil + case fields.PeBaseAddress: + return p.ImageBase, nil + case fields.PeNumSections: + return p.NumberOfSections, nil + case fields.PeNumSymbols: + return p.NumberOfSymbols, nil + case fields.PeSymbols: + return p.Symbols, nil + case fields.PeImports: + return p.Imports, nil + default: + switch { + case f.IsPeSectionsSequence(): + // get the section name + sname, segment := captureInBrackets(f.String()) + sec := p.Section(sname) + if sec == nil { + return nil, nil + } + switch segment { + case fields.SectionEntropy: + return sec.Entropy, nil + case fields.SectionMD5Hash: + return sec.Md5, nil + case fields.SectionSize: + return sec.Size, nil + } + case f.IsPeResourcesSequence(): + // consult the resource name + key, _ := captureInBrackets(f.String()) + v, ok := p.VersionResources[key] + if ok { + return v, nil + } + // match on prefix (e.g. pe.resources[Org] = Blackwater) + for k, v := range p.VersionResources { + if strings.HasPrefix(k, key) { + return v, nil + } + } + } + } + + return nil, nil +} + +func captureInBrackets(s string) (string, fields.Segment) { + lbracket := strings.Index(s, "[") + if lbracket == -1 { + return "", "" + } + rbracket := strings.Index(s, "]") + if rbracket == -1 { + return "", "" + } + if lbracket+1 > len(s) { + return "", "" + } + if rbracket+2 < len(s) { + return s[lbracket+1 : rbracket], fields.Segment(s[rbracket+2:]) + } + return s[lbracket+1 : rbracket], "" +} diff --git a/pkg/filter/fields/fields.go b/pkg/filter/fields/fields.go index a49723d0c..c23e41ea6 100644 --- a/pkg/filter/fields/fields.go +++ b/pkg/filter/fields/fields.go @@ -19,10 +19,64 @@ package fields import ( - "github.com/rabbitstack/fibratus/pkg/kevent/kparams" "sort" + + "github.com/rabbitstack/fibratus/pkg/kevent/kparams" ) +const ( + // KevtSeq is the event sequence number + KevtSeq Field = "kevt.seq" + // KevtPID is the process identifier that generated the event + KevtPID Field = "kevt.pid" + // KevtTID is the thread identifier that generated the event + KevtTID Field = "kevt.tid" + // KevtCPU is the CPU core where the event was generated + KevtCPU Field = "kevt.cpu" + // KevtDesc represents the event description + KevtDesc Field = "kevt.desc" + // KevtHost represents the host where the event was produced + KevtHost Field = "kevt.host" + // KevtTime is the event time + KevtTime Field = "kevt.time" + // KevtTimeHour is the hour part of the event time + KevtTimeHour Field = "kevt.time.h" + // KevtTimeMin is the minute part of the event time + KevtTimeMin Field = "kevt.time.m" + // KevtTimeSec is the second part of the event time + KevtTimeSec Field = "kevt.time.s" + // KevtTimeNs is the nanosecond part of the event time + KevtTimeNs Field = "kevt.time.ns" + // KevtDate is the event date + KevtDate Field = "kevt.date" + // KevtDateDay is the day of event date + KevtDateDay Field = "kevt.date.d" + // KevtDateMonth is the month of event date + KevtDateMonth Field = "kevt.date.m" + // KevtDateYear is the year of event date + KevtDateYear Field = "kevt.date.y" + // KevtDateTz is the time zone of event timestamp + KevtDateTz Field = "kevt.date.tz" + // KevtDateWeek is the event week number + KevtDateWeek Field = "kevt.date.week" + // KevtDateWeekday is the event week day + KevtDateWeekday Field = "kevt.date.weekday" + // KevtName is the event name + KevtName Field = "kevt.name" + // KevtCategory is the event category + KevtCategory Field = "kevt.category" + // KevtMeta is the event metadata + KevtMeta Field = "kevt.meta" + // KevtNparams is the number of event parameters + KevtNparams Field = "kevt.nparams" +) + +// Field represents the type alias for the field +type Field string + +// String casts the field type to string. +func (f Field) String() string { return string(f) } + // FieldInfo is the field metadata descriptor. type FieldInfo struct { Field Field diff --git a/pkg/filter/fields/fields_linux.go b/pkg/filter/fields/fields_linux.go new file mode 100644 index 000000000..0af3d7b87 --- /dev/null +++ b/pkg/filter/fields/fields_linux.go @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fields + +var fields = map[Field]FieldInfo{} + +func Lookup(name string) Field { + return "" +} diff --git a/pkg/filter/fields/fields_windows.go b/pkg/filter/fields/fields_windows.go index 9e869c141..8e9c3257c 100644 --- a/pkg/filter/fields/fields_windows.go +++ b/pkg/filter/fields/fields_windows.go @@ -28,9 +28,6 @@ import ( // contains the indexed field name. Next is the indexed key and, finally the segment. var pathRegexp = regexp.MustCompile(`(pe.sections|pe.resources|ps.envs|ps.modules|ps.ancestor)\[(.+\s*)].?(.*)`) -// Field represents the type alias for the field -type Field string - const ( // PsPid represents the process id field PsPid Field = "ps.pid" @@ -123,51 +120,6 @@ const ( // PeResources represents PE resources PeResources Field = "pe.resources" - // KevtSeq is the event sequence number - KevtSeq Field = "kevt.seq" - // KevtPID is the process identifier that generated the event - KevtPID Field = "kevt.pid" - // KevtTID is the thread identifier that generated the event - KevtTID Field = "kevt.tid" - // KevtCPU is the CPU core where the event was generated - KevtCPU Field = "kevt.cpu" - // KevtDesc represents the event description - KevtDesc Field = "kevt.desc" - // KevtHost represents the host where the event was produced - KevtHost Field = "kevt.host" - // KevtTime is the event time - KevtTime Field = "kevt.time" - // KevtTimeHour is the hour part of the event time - KevtTimeHour Field = "kevt.time.h" - // KevtTimeMin is the minute part of the event time - KevtTimeMin Field = "kevt.time.m" - // KevtTimeSec is the second part of the event time - KevtTimeSec Field = "kevt.time.s" - // KevtTimeNs is the nanosecond part of the event time - KevtTimeNs Field = "kevt.time.ns" - // KevtDate is the event date - KevtDate Field = "kevt.date" - // KevtDateDay is the day of event date - KevtDateDay Field = "kevt.date.d" - // KevtDateMonth is the month of event date - KevtDateMonth Field = "kevt.date.m" - // KevtDateYear is the year of event date - KevtDateYear Field = "kevt.date.y" - // KevtDateTz is the time zone of event timestamp - KevtDateTz Field = "kevt.date.tz" - // KevtDateWeek is the event week number - KevtDateWeek Field = "kevt.date.week" - // KevtDateWeekday is the event week day - KevtDateWeekday Field = "kevt.date.weekday" - // KevtName is the event name - KevtName Field = "kevt.name" - // KevtCategory is the event category - KevtCategory Field = "kevt.category" - // KevtMeta is the event metadata - KevtMeta Field = "kevt.meta" - // KevtNparams is the number of event parameters - KevtNparams Field = "kevt.nparams" - // HandleID represents the handle identifier within the process address space HandleID Field = "handle.id" // HandleObject represents the handle object address @@ -243,9 +195,6 @@ const ( None Field = "" ) -// String casts the field type to string. -func (f Field) String() string { return string(f) } - // Segment represents the type alias for the segment. Segment // denotes the location of the value within an indexed field. type Segment string diff --git a/pkg/filter/filter_linux.go b/pkg/filter/filter_linux.go new file mode 100644 index 000000000..41d1cc3e9 --- /dev/null +++ b/pkg/filter/filter_linux.go @@ -0,0 +1,29 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter + +import "github.com/rabbitstack/fibratus/pkg/config" + +func New(expr string, config *config.Config) Filter { + return nil +} + +func NewFromCLI(args []string, config *config.Config) (Filter, error) { + return nil, nil +} diff --git a/pkg/filter/funcmap/funcmap.go b/pkg/filter/funcmap/funcmap.go new file mode 100644 index 000000000..2642f00f4 --- /dev/null +++ b/pkg/filter/funcmap/funcmap.go @@ -0,0 +1,101 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package funcmap + +import ( + "fmt" + "strings" + "text/template" + + "github.com/Masterminds/sprig/v3" + "github.com/rabbitstack/fibratus/pkg/alertsender" + log "github.com/sirupsen/logrus" +) + +// New returns the template func map +// populated with some useful template functions +// that can be used in filter actions. Some functions +// are late-bound, so we merely provide a declaration. +// The real function is attached when the filter action +// is triggered. +func New() template.FuncMap { + f := sprig.TxtFuncMap() + + extra := template.FuncMap{ + // This is a placeholder for the functions that might be + // late-bound to a template. By declaring them here, we + // can still execute the template associated with the + // filter action to ensure template syntax is correct + "emit": func(title string, text string, args ...string) string { return "" }, + "kill": func(pid uint32) string { return "" }, + "stringify": func(in []interface{}) string { + values := make([]string, 0) + for _, e := range in { + s, ok := e.(string) + if !ok { + continue + } + values = append(values, fmt.Sprintf("'%s'", s)) + } + return fmt.Sprintf("(%s)", strings.Join(values, ", ")) + }, + } + + for k, v := range extra { + f[k] = v + } + + return f +} + +// InitFuncs assigns late-bound functions to the func map. +func InitFuncs(funcMap template.FuncMap) { + funcMap["emit"] = emit + funcMap["kill"] = kill +} + +// emit sends an alert via all configured alert senders. +func emit(title string, text string, args ...string) string { + senders := alertsender.FindAll() + if len(senders) == 0 { + return "no alertsenders registered. Alert won't be sent" + } + + severity := "normal" + tags := make([]string, 0) + if len(args) > 0 { + severity = args[0] + } + if len(args) > 1 { + tags = args[1:] + } + + for _, s := range senders { + alert := alertsender.NewAlert( + title, + text, + tags, + alertsender.ParseSeverityFromString(severity), + ) + if err := s.Send(alert); err != nil { + log.Warnf("unable to emit alert from rule: %v", err) + } + } + return "" +} diff --git a/pkg/filter/funcmap/funcmap_linux.go b/pkg/filter/funcmap/funcmap_linux.go new file mode 100644 index 000000000..c552e6765 --- /dev/null +++ b/pkg/filter/funcmap/funcmap_linux.go @@ -0,0 +1,24 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package funcmap + +// kill terminates a process with specified pid. +func kill(pid uint32) string { + return "" +} diff --git a/pkg/filter/funcmap/funcmap_windows.go b/pkg/filter/funcmap/funcmap_windows.go index c18f79ac2..8a1990614 100644 --- a/pkg/filter/funcmap/funcmap_windows.go +++ b/pkg/filter/funcmap/funcmap_windows.go @@ -20,86 +20,9 @@ package funcmap import ( "fmt" - "github.com/Masterminds/sprig/v3" - "github.com/rabbitstack/fibratus/pkg/alertsender" - log "github.com/sirupsen/logrus" - "strings" "syscall" - "text/template" ) -// New returns the template func map -// populated with some useful template functions -// that can be used in filter actions. Some functions -// are late-bound, so we merely provide a declaration. -// The real function is attached when the filter action -// is triggered. -func New() template.FuncMap { - f := sprig.TxtFuncMap() - - extra := template.FuncMap{ - // This is a placeholder for the functions that might be - // late-bound to a template. By declaring them here, we - // can still execute the template associated with the - // filter action to ensure template syntax is correct - "emit": func(title string, text string, args ...string) string { return "" }, - "kill": func(pid uint32) string { return "" }, - "stringify": func(in []interface{}) string { - values := make([]string, 0) - for _, e := range in { - s, ok := e.(string) - if !ok { - continue - } - values = append(values, fmt.Sprintf("'%s'", s)) - } - return fmt.Sprintf("(%s)", strings.Join(values, ", ")) - }, - } - - for k, v := range extra { - f[k] = v - } - - return f -} - -// InitFuncs assigns late-bound functions to the func map. -func InitFuncs(funcMap template.FuncMap) { - funcMap["emit"] = emit - funcMap["kill"] = kill -} - -// emit sends an alert via all configured alert senders. -func emit(title string, text string, args ...string) string { - senders := alertsender.FindAll() - if len(senders) == 0 { - return "no alertsenders registered. Alert won't be sent" - } - - severity := "normal" - tags := make([]string, 0) - if len(args) > 0 { - severity = args[0] - } - if len(args) > 1 { - tags = args[1:] - } - - for _, s := range senders { - alert := alertsender.NewAlert( - title, - text, - tags, - alertsender.ParseSeverityFromString(severity), - ) - if err := s.Send(alert); err != nil { - log.Warnf("unable to emit alert from rule: %v", err) - } - } - return "" -} - // kill terminates a process with specified pid. func kill(pid uint32) string { h, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, pid) diff --git a/pkg/fs/attrs.go b/pkg/fs/attrs.go index ad8571343..9412dedfb 100644 --- a/pkg/fs/attrs.go +++ b/pkg/fs/attrs.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/fs/dev.go b/pkg/fs/dev.go index 850e0ed66..1a0508787 100644 --- a/pkg/fs/dev.go +++ b/pkg/fs/dev.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/fs/dev_test.go b/pkg/fs/dev_test.go index 6c012132a..9c8101f8c 100644 --- a/pkg/fs/dev_test.go +++ b/pkg/fs/dev_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/fs/file.go b/pkg/fs/file.go index bd2704369..78b0d62fd 100644 --- a/pkg/fs/file.go +++ b/pkg/fs/file.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/fs/file_test.go b/pkg/fs/file_test.go index 8969fc1cd..9520fe6ba 100644 --- a/pkg/fs/file_test.go +++ b/pkg/fs/file_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/fs/types.go b/pkg/fs/types.go index aeb6eb467..1de0064a6 100644 --- a/pkg/fs/types.go +++ b/pkg/fs/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/alpc.go b/pkg/handle/alpc.go index b9f84e362..bda12fdf9 100644 --- a/pkg/handle/alpc.go +++ b/pkg/handle/alpc.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/key.go b/pkg/handle/key.go index 8bf246901..f8523f5c1 100644 --- a/pkg/handle/key.go +++ b/pkg/handle/key.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/key_test.go b/pkg/handle/key_test.go index 1d1b5114d..0bc31b529 100644 --- a/pkg/handle/key_test.go +++ b/pkg/handle/key_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/mutant.go b/pkg/handle/mutant.go index 407207cfc..07096a115 100644 --- a/pkg/handle/mutant.go +++ b/pkg/handle/mutant.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/object.go b/pkg/handle/object.go index 12ba74c81..48d1454ff 100644 --- a/pkg/handle/object.go +++ b/pkg/handle/object.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/object_test.go b/pkg/handle/object_test.go index bdbc0273e..4312d5fe9 100644 --- a/pkg/handle/object_test.go +++ b/pkg/handle/object_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/snapshotter.go b/pkg/handle/snapshotter.go index 031591d36..bb8d1ac55 100644 --- a/pkg/handle/snapshotter.go +++ b/pkg/handle/snapshotter.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/snapshotter_mock.go b/pkg/handle/snapshotter_mock.go index 165be65b7..1cd4ebcca 100644 --- a/pkg/handle/snapshotter_mock.go +++ b/pkg/handle/snapshotter_mock.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/snapshotter_test.go b/pkg/handle/snapshotter_test.go index 5e55fabd1..92624fcfb 100644 --- a/pkg/handle/snapshotter_test.go +++ b/pkg/handle/snapshotter_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/timeout.go b/pkg/handle/timeout.go index e592809b2..af12342c0 100644 --- a/pkg/handle/timeout.go +++ b/pkg/handle/timeout.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/timeout_test.go b/pkg/handle/timeout_test.go index 2ad241be4..f31e3aee4 100644 --- a/pkg/handle/timeout_test.go +++ b/pkg/handle/timeout_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/types.go b/pkg/handle/types.go index 838980469..7e8ac1f47 100644 --- a/pkg/handle/types.go +++ b/pkg/handle/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/types/marshaller.go b/pkg/handle/types/marshaller.go index 20b03c279..0af262e89 100644 --- a/pkg/handle/types/marshaller.go +++ b/pkg/handle/types/marshaller.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/types/marshaller_test.go b/pkg/handle/types/marshaller_test.go index c373ec694..3ef8faf1c 100644 --- a/pkg/handle/types/marshaller_test.go +++ b/pkg/handle/types/marshaller_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/handle/types/types.go b/pkg/handle/types/types.go index 65dcaf280..482e72755 100644 --- a/pkg/handle/types/types.go +++ b/pkg/handle/types/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/kcap/header.go b/pkg/kcap/header.go index 69332424f..5c9105e3c 100644 --- a/pkg/kcap/header.go +++ b/pkg/kcap/header.go @@ -1,3 +1,4 @@ +//go:build kcap // +build kcap /* diff --git a/pkg/kcap/reader.go b/pkg/kcap/reader.go index 0e050354a..d76e848a8 100644 --- a/pkg/kcap/reader.go +++ b/pkg/kcap/reader.go @@ -1,3 +1,4 @@ +//go:build kcap // +build kcap /* diff --git a/pkg/kcap/reader_unsupported.go b/pkg/kcap/reader_unsupported.go index d72078763..c6ed5f86d 100644 --- a/pkg/kcap/reader_unsupported.go +++ b/pkg/kcap/reader_unsupported.go @@ -1,3 +1,4 @@ +//go:build !kcap // +build !kcap /* diff --git a/pkg/kcap/reader_windows.go b/pkg/kcap/reader_windows.go index e45e82e89..522fd871d 100644 --- a/pkg/kcap/reader_windows.go +++ b/pkg/kcap/reader_windows.go @@ -1,3 +1,4 @@ +//go:build kcap // +build kcap /* diff --git a/pkg/kcap/reader_windows_test.go b/pkg/kcap/reader_windows_test.go index 622355120..1989bee1a 100644 --- a/pkg/kcap/reader_windows_test.go +++ b/pkg/kcap/reader_windows_test.go @@ -1,3 +1,4 @@ +//go:build kcap // +build kcap /* diff --git a/pkg/kcap/section/section.go b/pkg/kcap/section/section.go index d602125be..8cda3e7d2 100644 --- a/pkg/kcap/section/section.go +++ b/pkg/kcap/section/section.go @@ -20,10 +20,14 @@ package section import ( "fmt" + kcapver "github.com/rabbitstack/fibratus/pkg/kcap/version" "github.com/rabbitstack/fibratus/pkg/util/bytes" ) +// Type describes the type of a section +type Type uint8 + // Section represents the header describing the type, length and the version of each section. type Section [10]byte diff --git a/pkg/kcap/section/section_linux.go b/pkg/kcap/section/section_linux.go new file mode 100644 index 000000000..d8972bd8b --- /dev/null +++ b/pkg/kcap/section/section_linux.go @@ -0,0 +1,24 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package section + +// String returns the type name. +func (s Type) String() string { + return "" +} diff --git a/pkg/kcap/section/section_windows.go b/pkg/kcap/section/section_windows.go index ebcb4f2a2..07d9e4671 100644 --- a/pkg/kcap/section/section_windows.go +++ b/pkg/kcap/section/section_windows.go @@ -18,9 +18,6 @@ package section -// Type describes the type of a section -type Type uint8 - const ( // Process is the process header type Process Type = iota + 1 diff --git a/pkg/kcap/types_linux.go b/pkg/kcap/types_linux.go index 52f0ef6d0..326162fbf 100644 --- a/pkg/kcap/types_linux.go +++ b/pkg/kcap/types_linux.go @@ -18,6 +18,13 @@ package kcap +import ( + "context" + + "github.com/rabbitstack/fibratus/pkg/filter" + "github.com/rabbitstack/fibratus/pkg/kevent" +) + type Writer interface { // Write accepts two channels. The event channel receives events pushed by the kstream consumer. When the event // is peeked from the channel, it is serialized and written to the underlying byte buffer. diff --git a/pkg/kcap/version/version.go b/pkg/kcap/version/version.go new file mode 100644 index 000000000..959eb5426 --- /dev/null +++ b/pkg/kcap/version/version.go @@ -0,0 +1,22 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package version + +// Version designates the type for specifying the current section version. +type Version uint16 diff --git a/pkg/kcap/version/version_linux.go b/pkg/kcap/version/version_linux.go new file mode 100644 index 000000000..d3dd754b5 --- /dev/null +++ b/pkg/kcap/version/version_linux.go @@ -0,0 +1,19 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package version diff --git a/pkg/kcap/writer.go b/pkg/kcap/writer.go index ec3ef78cb..55f2784eb 100644 --- a/pkg/kcap/writer.go +++ b/pkg/kcap/writer.go @@ -1,3 +1,4 @@ +//go:build kcap // +build kcap /* diff --git a/pkg/kcap/writer_unsupported.go b/pkg/kcap/writer_unsupported.go index 60d7ccba6..3fb7cea24 100644 --- a/pkg/kcap/writer_unsupported.go +++ b/pkg/kcap/writer_unsupported.go @@ -1,3 +1,4 @@ +//go:build !kcap // +build !kcap /* diff --git a/pkg/kcap/writer_windows.go b/pkg/kcap/writer_windows.go index be97ae929..434fa9cd1 100644 --- a/pkg/kcap/writer_windows.go +++ b/pkg/kcap/writer_windows.go @@ -1,3 +1,4 @@ +//go:build kcap // +build kcap /* diff --git a/pkg/kcap/writer_windows_test.go b/pkg/kcap/writer_windows_test.go index d946564e4..50e5d3645 100644 --- a/pkg/kcap/writer_windows_test.go +++ b/pkg/kcap/writer_windows_test.go @@ -1,3 +1,4 @@ +//go:build kcap // +build kcap /* diff --git a/pkg/kevent/formatter_linux.go b/pkg/kevent/formatter_linux.go new file mode 100644 index 000000000..c63087d19 --- /dev/null +++ b/pkg/kevent/formatter_linux.go @@ -0,0 +1,42 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kevent + +import "strconv" + +func (f *Formatter) Format(kevt *Kevent) []byte { + if kevt == nil { + return []byte{} + } + values := map[string]interface{}{ + ts: kevt.Timestamp.String(), + pid: strconv.FormatUint(uint64(kevt.PID), 10), + tid: strconv.FormatUint(uint64(kevt.Tid), 10), + seq: strconv.FormatUint(kevt.Seq, 10), + cpu: strconv.FormatUint(uint64(kevt.CPU), 10), + typ: kevt.Name, + cat: kevt.Category, + desc: kevt.Description, + host: kevt.Host, + meta: kevt.Metadata.String(), + kparameters: kevt.Kparams.String(), + } + + return f.t.ExecuteString(values) +} diff --git a/pkg/kevent/kevent.go b/pkg/kevent/kevent.go index eb1c92860..3bce192c5 100644 --- a/pkg/kevent/kevent.go +++ b/pkg/kevent/kevent.go @@ -20,13 +20,13 @@ package kevent import ( "fmt" - kcapver "github.com/rabbitstack/fibratus/pkg/kcap/version" - "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" - pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" - "github.com/rabbitstack/fibratus/pkg/util/hostname" "strings" "sync" "time" + + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" + pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" + "github.com/rabbitstack/fibratus/pkg/util/hostname" ) // pool is used to alleviate the pressure on the heap allocator @@ -97,7 +97,7 @@ func (kevt *Kevent) String() string { Timestamp: %s, Kparams: %s, Metadata: %s, - %s + %#v `, kevt.Seq, kevt.PID, @@ -173,18 +173,6 @@ func Empty() *Kevent { } } -// NewFromKcap recovers the kernel event instance from the kcapture byte buffer. -func NewFromKcap(buf []byte) (*Kevent, error) { - kevt := &Kevent{ - Kparams: make(Kparams), - Metadata: make(map[string]string), - } - if err := kevt.UnmarshalRaw(buf, kcapver.KevtSecV1); err != nil { - return nil, err - } - return kevt, nil -} - // AddMeta appends a key/value pair to event's metadata. func (kevt *Kevent) AddMeta(k, v string) { kevt.Metadata[k] = v diff --git a/pkg/kevent/kevent_linux.go b/pkg/kevent/kevent_linux.go new file mode 100644 index 000000000..4a277f0c2 --- /dev/null +++ b/pkg/kevent/kevent_linux.go @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kevent + +import ( + "encoding/binary" + "fmt" +) + +// Header describes the layout of the event header +// that is pushed to the perf ring buffer. +type Header struct { + // Timestamp expressed in nanoseconds since epoch + Timestamp uint64 + // Pid is the process identifer that produced the event + Pid uint32 + // Tid is the thread identifier that produced the event + Tid uint32 + // CPU core on which the event was generated + CPU uint32 + // Nparams represents the number of parameters for particular event + Nparams uint32 + // Type indicates the event type which is usually the syscall number + Type uint16 +} + +// HeaderFromRawSample constructs the event header from perf raw sample. +func HeaderFromRawSample(rawSample []byte) *Header { + var header = new(Header) + header.Timestamp = binary.LittleEndian.Uint64(rawSample[0:]) + header.Pid = binary.LittleEndian.Uint32(rawSample[8:]) + header.Tid = binary.LittleEndian.Uint32(rawSample[12:]) + header.CPU = binary.LittleEndian.Uint32(rawSample[16:]) + header.Nparams = binary.LittleEndian.Uint32(rawSample[20:]) + header.Type = binary.LittleEndian.Uint16(rawSample[24:]) + return header +} + +// String returns the raw string representation of the event header. +func (h Header) String() string { + return fmt.Sprintf( + ` + Timestamp: %d + Pid: %d + Tid: %d + CPU: %d + Nparams: %d + Type: %d`, + h.Timestamp, + h.Pid, + h.Tid, + h.CPU, + h.Nparams, + h.Type, + ) +} + +func NewFromKcap(buf []byte) (*Kevent, error) { + return nil, nil +} diff --git a/pkg/kevent/kevent_windows.go b/pkg/kevent/kevent_windows.go index b4b1b3966..0c086d1b5 100644 --- a/pkg/kevent/kevent_windows.go +++ b/pkg/kevent/kevent_windows.go @@ -18,7 +18,22 @@ package kevent -import "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" +import ( + kcapver "github.com/rabbitstack/fibratus/pkg/kcap/version" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" +) + +// NewFromKcap recovers the kernel event instance from the kcapture byte buffer. +func NewFromKcap(buf []byte) (*Kevent, error) { + kevt := &Kevent{ + Kparams: make(Kparams), + Metadata: make(map[string]string), + } + if err := kevt.UnmarshalRaw(buf, kcapver.KevtSecV1); err != nil { + return nil, err + } + return kevt, nil +} // IsNetworkTCP determines whether the kevent pertains to network TCP events. func (kevt Kevent) IsNetworkTCP() bool { diff --git a/pkg/kevent/kparam_linux.go b/pkg/kevent/kparam_linux.go new file mode 100644 index 000000000..aed37ee99 --- /dev/null +++ b/pkg/kevent/kparam_linux.go @@ -0,0 +1,33 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kevent + +import "github.com/rabbitstack/fibratus/pkg/kevent/kparams" + +func NewKparam(name string, typ kparams.Type, value kparams.Value) *Kparam { + return nil +} + +// String returns the string representation of the parameter value. +func (k Kparam) String() string { + if k.Value == nil { + return "" + } + return "" +} diff --git a/pkg/kevent/kparams/canonicalize.go b/pkg/kevent/kparams/canonicalize.go index 78ea0c246..9b317c7bd 100644 --- a/pkg/kevent/kparams/canonicalize.go +++ b/pkg/kevent/kparams/canonicalize.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/kevent/kparams/canonicalize_test.go b/pkg/kevent/kparams/canonicalize_test.go index 63f687f88..74c54b658 100644 --- a/pkg/kevent/kparams/canonicalize_test.go +++ b/pkg/kevent/kparams/canonicalize_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/kevent/kparams/fields.go b/pkg/kevent/kparams/fields.go new file mode 100644 index 000000000..e043c39e1 --- /dev/null +++ b/pkg/kevent/kparams/fields.go @@ -0,0 +1,28 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kparams + +const ( + // ProcessID represents the process identifier. + ProcessID = "pid" + // ThreadID field represents the thread identifier. + ThreadID = "tid" + // ProcessParentID field represents the parent process identifier. + ProcessParentID = "ppid" +) diff --git a/pkg/kevent/kparams/fields_windows.go b/pkg/kevent/kparams/fields_windows.go index 8e90a64ad..722073b10 100644 --- a/pkg/kevent/kparams/fields_windows.go +++ b/pkg/kevent/kparams/fields_windows.go @@ -22,14 +22,8 @@ const ( // NTStatus is the parameter that identifies the NTSTATUS value. NTStatus = "status" - // ProcessID represents the process identifier. - ProcessID = "pid" // ProcessObject field represents the address of the process object in the kernel. ProcessObject = "kproc" - // ThreadID field represents the thread identifier. - ThreadID = "tid" - // ProcessParentID field represents the parent process identifier. - ProcessParentID = "ppid" // SessionID fields represents the session identifier. SessionID = "session_id" // UserSID field is the security identifier associated to the process token under which it is run. diff --git a/pkg/kevent/kparams/size.go b/pkg/kevent/kparams/size.go index b59cb07af..be5ba1a56 100644 --- a/pkg/kevent/kparams/size.go +++ b/pkg/kevent/kparams/size.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/kevent/kparams/size_test.go b/pkg/kevent/kparams/size_test.go index 180f9e0ff..221a02a1f 100644 --- a/pkg/kevent/kparams/size_test.go +++ b/pkg/kevent/kparams/size_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/kevent/kparams/types.go b/pkg/kevent/kparams/types.go new file mode 100644 index 000000000..29e503684 --- /dev/null +++ b/pkg/kevent/kparams/types.go @@ -0,0 +1,80 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kparams + +import "strconv" + +const ( + // NA defines absent parameter's value + NA = "na" +) + +// Value defines the container for parameter values +type Value interface{} + +// Type defines kernel event parameter type +type Type uint16 + +// Hex is the type alias for hexadecimal values +type Hex string + +// NewHex creates a new Hex type from the given integer value. +func NewHex(v Value) Hex { + switch n := v.(type) { + case uint8: + return Hex(strconv.FormatUint(uint64(n), 16)) + case uint16: + return Hex(strconv.FormatUint(uint64(n), 16)) + case uint32: + return Hex(strconv.FormatUint(uint64(n), 16)) + case int32: + return Hex(strconv.FormatInt(int64(n), 16)) + case uint64: + return Hex(strconv.FormatUint(n, 16)) + case int64: + return Hex(strconv.FormatInt(n, 16)) + default: + return "" + } +} + +// Uint8 yields an uint8 value from its hex representation. +func (hex Hex) Uint8() uint8 { return uint8(hex.parseUint(8)) } + +// Uint16 yields an uint16 value from its hex representation. +func (hex Hex) Uint16() uint16 { return uint16(hex.parseUint(16)) } + +// Uint32 yields an uint32 value from its hex representation. +func (hex Hex) Uint32() uint32 { return uint32(hex.parseUint(32)) } + +// Uint64 yields an uint64 value from its hex representation. +func (hex Hex) Uint64() uint64 { return hex.parseUint(64) } + +func (hex Hex) parseUint(bitSize int) uint64 { + num, err := strconv.ParseUint(string(hex), 16, bitSize) + if err != nil { + return uint64(0) + } + return num +} + +// String returns a string representation of the hex value. +func (hex Hex) String() string { + return string(hex) +} diff --git a/pkg/kevent/kparams/types_linux.go b/pkg/kevent/kparams/types_linux.go new file mode 100644 index 000000000..543ca70ee --- /dev/null +++ b/pkg/kevent/kparams/types_linux.go @@ -0,0 +1,87 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kparams + +const ( + // Null is a null parameter type + Null Type = iota + // UnicodeString a string of 16-bit characters. By default, assumed to have been encoded using UTF-16LE + UnicodeString + // AnsiString a string of 8-bit characters + AnsiString + // Int8 a signed 8-bit integer + Int8 + // Uint8 an unsigned 8-bit integer + Uint8 + // Int16 a signed 16-bit integer + Int16 + // Uint16 an unsigned 16-bit integer + Uint16 + // Int32 a signed 32-bit integer + Int32 + // Uint32 an unsigned 32-bit integer + Uint32 + // Int64 a signed 64-bit integer + Int64 + // Uint64 an unsigned 64-bit integer + Uint64 + // Float an IEEE 4-byte floating-point number + Float + // Double an IEEE 8-byte floating-point number + Double + // Bool a 32-bit value where 0 is false and 1 is true + Bool + // Binary is a binary data of variable size. The size must be specified in the data definition as a constant or a reference to another (integer) data item.For an IP V6 address, the data should be an IN6_ADDR structure. + // For a socket address, the data should be a SOCKADDR_STORAGE structure. The AF_INET, AF_INET6, and AF_LINK address families are supported + Binary + // Pointer an unsigned 32-bit or 64-bit pointer value. The size depends on the architecture of the computer logging the event + Pointer + // PID is the process identifier + PID + // TID is the thread identifier + TID + // HexInt8 is the hexadecimal representation of 8-bit integer + HexInt8 + // HexInt16 is the hexadecimal representation of 16-bit integer + HexInt16 + // HexInt32 is the hexadecimal representation of 32-bit integer + HexInt32 + // HexInt64 is the hexadecimal representation of 64-bit integer + HexInt64 + // Port represents the endpoint port number + Port + // IP is the IP address + IP + // IPv4 is the IPv4 address + IPv4 + // IPv6 is the IPv6 address + IPv6 + // Time represents the timestamp + Time + // Slice represents a collection of items + Slice + // Enum represents an enumeration + Enum + // Map represents a map + Map + // Object is the generic object + Object + // Unknown represent an unknown parameter type + Unknown +) diff --git a/pkg/kevent/kparams/types_windows.go b/pkg/kevent/kparams/types_windows.go index c3487f772..a5b9e7eaf 100644 --- a/pkg/kevent/kparams/types_windows.go +++ b/pkg/kevent/kparams/types_windows.go @@ -18,69 +18,6 @@ package kparams -import ( - "strconv" -) - -const ( - // NA defines absent parameter's value - NA = "na" -) - -// Value defines the container for parameter values -type Value interface{} - -// Type defines kernel event parameter type -type Type uint16 - -// Hex is the type alias for hexadecimal values -type Hex string - -// NewHex creates a new Hex type from the given integer value. -func NewHex(v Value) Hex { - switch n := v.(type) { - case uint8: - return Hex(strconv.FormatUint(uint64(n), 16)) - case uint16: - return Hex(strconv.FormatUint(uint64(n), 16)) - case uint32: - return Hex(strconv.FormatUint(uint64(n), 16)) - case int32: - return Hex(strconv.FormatInt(int64(n), 16)) - case uint64: - return Hex(strconv.FormatUint(n, 16)) - case int64: - return Hex(strconv.FormatInt(n, 16)) - default: - return "" - } -} - -// Uint8 yields an uint8 value from its hex representation. -func (hex Hex) Uint8() uint8 { return uint8(hex.parseUint(8)) } - -// Uint16 yields an uint16 value from its hex representation. -func (hex Hex) Uint16() uint16 { return uint16(hex.parseUint(16)) } - -// Uint32 yields an uint32 value from its hex representation. -func (hex Hex) Uint32() uint32 { return uint32(hex.parseUint(32)) } - -// Uint64 yields an uint64 value from its hex representation. -func (hex Hex) Uint64() uint64 { return hex.parseUint(64) } - -func (hex Hex) parseUint(bitSize int) uint64 { - num, err := strconv.ParseUint(string(hex), 16, bitSize) - if err != nil { - return uint64(0) - } - return num -} - -// String returns a string representation of the hex value. -func (hex Hex) String() string { - return string(hex) -} - const ( // Null is a null parameter type Null Type = iota diff --git a/pkg/kevent/ktypes/ktypes.go b/pkg/kevent/ktypes/ktypes.go new file mode 100644 index 000000000..f4703f183 --- /dev/null +++ b/pkg/kevent/ktypes/ktypes.go @@ -0,0 +1,30 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ktypes + +// UnmarshalYAML converts the ktype name to ktype array type. +func (k *Ktype) UnmarshalYAML(unmarshal func(interface{}) error) error { + var ktyp string + err := unmarshal(&ktyp) + if err != nil { + return err + } + *k = KeventNameToKtype(ktyp) + return nil +} diff --git a/pkg/kevent/ktypes/ktypes_linux.go b/pkg/kevent/ktypes/ktypes_linux.go new file mode 100644 index 000000000..7488a0afb --- /dev/null +++ b/pkg/kevent/ktypes/ktypes_linux.go @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ktypes + +import "math" + +// Ktype represents the kernel event type which usually maps to syscall identifier +type Ktype uint16 + +const ( + // Read reads data from a file descriptor + Read Ktype = iota +) + +// UnknownKtype assumes we'll never exhaust the 1<<16 - 1 syscall range +const UnknownKtype Ktype = math.MaxUint16 + +// String returns human-readable event representation. +func (k Ktype) String() string { + switch k { + case Read: + return "read" + default: + return "unknown" + } +} + +// RawID coerces the ktype to uint32 value to satisfy +// eBPF map marshaller alignment requirements. +func (k Ktype) RawID() uint32 { return uint32(k) } + +func (k Ktype) Hash() uint32 { return 0 } diff --git a/pkg/kevent/ktypes/ktypes_windows.go b/pkg/kevent/ktypes/ktypes_windows.go index 45f80f20e..7ac3628cd 100644 --- a/pkg/kevent/ktypes/ktypes_windows.go +++ b/pkg/kevent/ktypes/ktypes_windows.go @@ -298,17 +298,6 @@ func (k Ktype) Dropped(capture bool) bool { } } -// UnmarshalYAML converts the ktype name to ktype array type. -func (k *Ktype) UnmarshalYAML(unmarshal func(interface{}) error) error { - var ktyp string - err := unmarshal(&ktyp) - if err != nil { - return err - } - *k = KeventNameToKtype(ktyp) - return nil -} - // Pack transforms event provider GUID and the op code into `Ktype` type. The type provides a convenient way // to compare different kernel event types. func Pack(g syscall.GUID, opcode uint8) Ktype { diff --git a/pkg/kevent/ktypes/metainfo.go b/pkg/kevent/ktypes/metainfo.go new file mode 100644 index 000000000..15c540f76 --- /dev/null +++ b/pkg/kevent/ktypes/metainfo.go @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ktypes + +import "sort" + +// GetKtypesMeta returns kernel event types metadata. +func GetKtypesMeta() []KeventInfo { + ktypes := make([]KeventInfo, 0, len(kevents)) + for _, ktyp := range kevents { + ktypes = append(ktypes, ktyp) + } + sort.Slice(ktypes, func(i, j int) bool { return ktypes[i].Category < ktypes[j].Category }) + return ktypes +} + +// KeventNameToKtype converts a human-readable kernel event name to its internal kernel type representation. +func KeventNameToKtype(name string) Ktype { + if ktype, ok := ktypes[name]; ok { + return ktype + } + return UnknownKtype +} diff --git a/pkg/kevent/ktypes/metainfo_linux.go b/pkg/kevent/ktypes/metainfo_linux.go new file mode 100644 index 000000000..b47daca58 --- /dev/null +++ b/pkg/kevent/ktypes/metainfo_linux.go @@ -0,0 +1,59 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ktypes + +import "github.com/rabbitstack/fibratus/pkg/kevent/kparams" + +// KeventInfo describes the kernel event meta info such as human readable name, category +// and event's description. +type KeventInfo struct { + // Name is the human-readable representation of the kernel event (e.g. CreateProcess, DeleteFile). + Name string + // Category designates the category to which kernel event pertains. (e.g. process, net) + Category Category + // Description is the short explanation that describes the purpose of the kernel event. + Description string + // Kpars describes event parameters information. + Kpars []KparInfo +} + +// KparInfo describes each of the parameters captured in kernel tracepoint. +type KparInfo struct { + Name string // parameter name, such as fd + Type kparams.Type // parameter type, such as int64 +} + +var kevents = map[Ktype]KeventInfo{ + Read: {"read", File, "reads data from a file descriptor", []KparInfo{}}, +} + +var ktypes = map[string]Ktype{ + "read": Read, +} + +// GetKtypesMap returns the map of available ktypes. +func GetKtypesMap() map[string]Ktype { return ktypes } + +// KtypeToKeventInfo derives event metainfo from its type. +func KtypeToKeventInfo(ktype Ktype) KeventInfo { + if kinfo, ok := kevents[ktype]; ok { + return kinfo + } + return KeventInfo{Name: "N/A", Category: Unknown} +} diff --git a/pkg/kevent/ktypes/metainfo_windows.go b/pkg/kevent/ktypes/metainfo_windows.go index f5a88d41e..404e961f3 100644 --- a/pkg/kevent/ktypes/metainfo_windows.go +++ b/pkg/kevent/ktypes/metainfo_windows.go @@ -18,8 +18,6 @@ package ktypes -import "sort" - // KeventInfo describes the kernel event meta info such as human readable name, category // and event's description. type KeventInfo struct { @@ -129,21 +127,3 @@ func KtypeToKeventInfo(ktype Ktype) KeventInfo { } return KeventInfo{Name: "N/A", Category: Unknown} } - -// KeventNameToKtype converts a human-readable kernel event name to its internal kernel type representation. -func KeventNameToKtype(name string) Ktype { - if ktype, ok := ktypes[name]; ok { - return ktype - } - return UnknownKtype -} - -// GetKtypesMeta returns kernel event types metadata. -func GetKtypesMeta() []KeventInfo { - ktypes := make([]KeventInfo, 0, len(kevents)) - for _, ktyp := range kevents { - ktypes = append(ktypes, ktyp) - } - sort.Slice(ktypes, func(i, j int) bool { return ktypes[i].Category < ktypes[j].Category }) - return ktypes -} diff --git a/pkg/kevent/marshaller_linux.go b/pkg/kevent/marshaller_linux.go new file mode 100644 index 000000000..874d055bb --- /dev/null +++ b/pkg/kevent/marshaller_linux.go @@ -0,0 +1,33 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kevent + +import kcapver "github.com/rabbitstack/fibratus/pkg/kcap/version" + +func (kevt *Kevent) MarshalRaw() []byte { + return nil +} + +func (kevt *Kevent) UnmarshalRaw(b []byte, ver kcapver.Version) error { + return nil +} + +func (kevt *Kevent) MarshalJSON() []byte { + return nil +} diff --git a/pkg/kevent/marshaller_test.go b/pkg/kevent/marshaller_windows_test.go similarity index 100% rename from pkg/kevent/marshaller_test.go rename to pkg/kevent/marshaller_windows_test.go diff --git a/pkg/kevent/sequencer_linux.go b/pkg/kevent/sequencer_linux.go new file mode 100644 index 000000000..91ca05474 --- /dev/null +++ b/pkg/kevent/sequencer_linux.go @@ -0,0 +1,24 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kevent + +type Sequencer struct { +} + +func NewSequencer() *Sequencer { return &Sequencer{} } diff --git a/pkg/kstream/interceptors/chain_linux.go b/pkg/kstream/interceptors/chain_linux.go new file mode 100644 index 000000000..865067635 --- /dev/null +++ b/pkg/kstream/interceptors/chain_linux.go @@ -0,0 +1,23 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interceptors + +type chain struct { + interceptors []KstreamInterceptor +} diff --git a/pkg/kstream/kstream_rundownc.go b/pkg/kstream/kstream_rundownc.go index daea15557..f76ed8784 100644 --- a/pkg/kstream/kstream_rundownc.go +++ b/pkg/kstream/kstream_rundownc.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/kstream/kstreamc.go b/pkg/kstream/kstreamc.go new file mode 100644 index 000000000..1aca959b3 --- /dev/null +++ b/pkg/kstream/kstreamc.go @@ -0,0 +1,52 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kstream + +import ( + "expvar" + + "github.com/rabbitstack/fibratus/pkg/filter" + "github.com/rabbitstack/fibratus/pkg/kevent" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" +) + +var ( + // failedKevents counts the number of kevents that failed to process grouped by error + failedKevents = expvar.NewMap("kstream.kevents.failures") +) + +// Consumer is the interface all kernel event stream consumers have to satisfy. +type Consumer interface { + // OpenKstream initializes the kernel event stream by setting up the required + // infrastructure for event collection. + OpenKstream() error + // CloseKstream shutdowns the currently running kernel event stream consumer by closing the corresponding + // session. + CloseKstream() error + // Errors returns the channel where errors are pushed. + Errors() chan error + // Events returns the buffered channel for pulling collected kernel events. + Events() chan *kevent.Kevent + // SetFilter initializes the filter that's applied on the kernel events. + SetFilter(filter filter.Filter) +} + +type blacklist map[ktypes.Ktype]string + +func (b blacklist) has(ktype ktypes.Ktype) bool { return b[ktype] != "" } diff --git a/pkg/kstream/kstreamc_linux.go b/pkg/kstream/kstreamc_linux.go index 708ef1663..c009022da 100644 --- a/pkg/kstream/kstreamc_linux.go +++ b/pkg/kstream/kstreamc_linux.go @@ -17,3 +17,256 @@ */ package kstream + +import ( + "bytes" + "embed" + "errors" + "expvar" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/perf" + + "github.com/rabbitstack/fibratus/pkg/config" + "github.com/rabbitstack/fibratus/pkg/filter" + "github.com/rabbitstack/fibratus/pkg/kevent" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" + "github.com/rabbitstack/fibratus/pkg/util/limit" + log "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +const ( + // kprobeFilename represents the kprobe object file name that is embedded via the go:embed directive + kprobeFilename = "kprobe.o" + // rawTracepointSysExit is the raw tracepoint to which the program is attached + rawTracepointSysExit = "sys_exit" +) + +//go:embed kprobe.o +var kprog embed.FS + +var ( + // errKprobeNotEmbedded defines the error for the missing kprobe object file in the binary embedded section + errKprobeNotEmbedded = errors.New("kprobe object file was not embedded or couldn't be read") + // lostPerfEvents computes lost event samples per CPU + lostPerfEvents = expvar.NewMap("kevent.lost.perf.events") +) + +type kstreamConsumer struct { + // objs contains a collection of programs and maps that + // are defined in the eBPF object file + objs *ebpf.Collection + // spec contains metadata about eBPF objects collection + spec *ebpf.CollectionSpec + + // perfReader is responsible for consuming inbound + // raw data blobs that are pushed from kernel space + // when a particular syscall is invoked + perfReader *perf.Reader + + // tracepoints represents the raw tracepoint where + // the program is attached + tracepoint link.Link + + maps Maps + errs chan error + kevts chan *kevent.Kevent + + config *config.Config + + sequencer *kevent.Sequencer + + filter filter.Filter +} + +// NewConsumer fabrics a new ebpf-based event stream consumer. +func NewConsumer(config *config.Config) (Consumer, error) { + // TODO: check kernel version + b, err := kprog.ReadFile(kprobeFilename) + if err != nil { + return nil, errKprobeNotEmbedded + } + spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + // increase the rlimit of the current process to provide + // sufficient space for locking memory for eBPF maps + if err := unix.Setrlimit(unix.RLIMIT_MEMLOCK, limit.WithInfinity()); err != nil { + return nil, fmt.Errorf("failed to increase temporary rlimit: %w", err) + } + + // load precompiled objects into the kernel + opts := ebpf.CollectionOptions{ + Programs: ebpf.ProgramOptions{ + LogLevel: btoi(config.Kstream.EnableVerifierLogging), + LogSize: config.Kstream.VerifierLogsize, + }, + Maps: ebpf.MapOptions{}, + } + var objs *ebpf.Collection + objs, err = ebpf.NewCollectionWithOptions(spec, opts) + if err != nil { + return nil, fmt.Errorf("unable to load kprobe: %w", err) + } + maps := Maps(objs.Maps) + if err := maps.VerifyMaps(); err != nil { + return nil, err + } + + // populate tracer programs. This step consists of + // traversing the collection of ebpf programs and + // indexing the ktype number to the corresponding + // program file descriptor. + keventsBlacklist := makeKeventBlacklist(config.Kstream.BlacklistKevents) + for _, progSpec := range spec.Programs { + ktype := ktypes.KeventNameToKtype(strings.TrimPrefix(progSpec.Name, "sys_")) + if ktype == ktypes.UnknownKtype || keventsBlacklist.has(ktype) { + continue + } + // obtain the prog fd + prog, ok := objs.Programs[progSpec.Name] + if !ok { + continue + } + // associate ktype with its tracer prog + if err := maps.Put(Tracers, ktype.RawID(), uint32(prog.FD())); err != nil { + return nil, err + } + } + + // populate discarders map + for _, proc := range config.Kstream.BlacklistImages { + + } + + kconsumer := &kstreamConsumer{ + objs: objs, + spec: spec, + maps: maps, + config: config, + kevts: make(chan *kevent.Kevent), + errs: make(chan error, 1000), + } + + return kconsumer, nil +} + +// OpenKstream attaches the eBPF program to the raw tracepoint for +// intercepting all syscall exit events and polls the perf ring buffer +// for incoming events. +func (k *kstreamConsumer) OpenKstream() error { + var err error + readerOpts := perf.ReaderOptions{ + Watermark: 128, + } + perfMap := k.maps.GetMap(Perf) + k.perfReader, err = perf.NewReaderWithOptions(perfMap, 8*os.Getpagesize(), readerOpts) + if err != nil { + return err + } + // attaches the prog to raw tracepoint + progName := rawTracepointSysExit + "_tracepoint" + rawTracepointProg := k.objs.Programs[progName] + if rawTracepointProg == nil { + return fmt.Errorf("missing program %s", progName) + } + k.tracepoint, err = link.AttachRawTracepoint(link.RawTracepointOptions{Name: rawTracepointSysExit, Program: rawTracepointProg}) + if err != nil { + return err + } + + // start consuming from perf ring buffer + go func() { + for { + record, err := k.perfReader.Read() + if err != nil { + if perf.IsClosed(err) { + log.Info("perf ring buffer is closing") + return + } + k.errs <- err + continue + } + if record.LostSamples > 0 { + lostPerfEvents.Add(strconv.Itoa(record.CPU), int64(record.LostSamples)) + continue + } + rawSample := record.RawSample + if err := k.processKevent(rawSample); err != nil { + failedKevents.Add(err.Error(), 1) + k.errs <- err + } + } + }() + + return nil +} + +func (k kstreamConsumer) CloseKstream() error { + // disable the raw tracepoint + if err := k.tracepoint.Close(); err != nil { + return err + } + k.objs.Close() + return k.perfReader.Close() +} + +func (k kstreamConsumer) Errors() chan error { + return k.errs +} + +func (k kstreamConsumer) Events() chan *kevent.Kevent { + return k.kevts +} + +func (k *kstreamConsumer) SetFilter(filter filter.Filter) { + k.filter = filter +} + +func (k kstreamConsumer) processKevent(rawSample []byte) error { + header := kevent.HeaderFromRawSample(rawSample) + if header == nil { + return nil + } + kevt := kevent.New( + 1, + header.Pid, + header.Tid, + uint8(header.CPU), + ktypes.Ktype(header.Type), + time.Unix(0, int64(header.Timestamp)), + nil, + ) + k.kevts <- kevt + + return nil +} + +func (k kstreamConsumer) produceParams(rawSample []byte, header *kevent.Header) map[string]*kevent.Kparam { + return nil +} + +func btoi(b bool) uint32 { + if b { + return uint32(1) + } + return 0 +} + +func makeKeventBlacklist(kevents []string) blacklist { + keventsBlacklist := make(blacklist) + for _, name := range kevents { + if ktype := ktypes.KeventNameToKtype(name); ktype != ktypes.UnknownKtype { + keventsBlacklist[ktype] = name + } + } + return keventsBlacklist +} diff --git a/pkg/kstream/kstreamc_windows.go b/pkg/kstream/kstreamc_windows.go index b479e459f..5082cdcc5 100644 --- a/pkg/kstream/kstreamc_windows.go +++ b/pkg/kstream/kstreamc_windows.go @@ -53,8 +53,6 @@ const ( ) var ( - // failedKevents counts the number of kevents that failed to process - failedKevents = expvar.NewMap("kstream.kevents.failures") failedKeventsByMissingSchema = expvar.NewMap("kstream.kevents.missing.schema.errors") // keventsEnqueued counts the number of events that are pushed to the queue keventsEnqueued = expvar.NewInt("kstream.kevents.enqueued") @@ -79,28 +77,6 @@ var ( currentPid = uint32(os.Getpid()) ) -// Consumer is the interface for the kernel event stream consumer. -type Consumer interface { - // OpenKstream initializes the kernel event stream by setting the event record callback and instructing it - // to consume events from log buffers. This operation can fail if opening the kernel logger session results - // in an invalid trace handler. Errors returned by `ProcessTrace` are sent to the channel since this function - // blocks the current thread and we schedule its execution in a separate goroutine. - OpenKstream() error - // CloseKstream shutdowns the currently running kernel event stream consumer by closing the corresponding - // session. - CloseKstream() error - // Errors returns the channel where errors are pushed. - Errors() chan error - // Events returns the buffered channel for pulling collected kernel events. - Events() chan *kevent.Kevent - // SetFilter initializes the filter that's applied on the kernel events. - SetFilter(filter filter.Filter) -} - -type blacklist map[ktypes.Ktype]string - -func (b blacklist) has(ktype ktypes.Ktype) bool { return b[ktype] != "" } - type kstreamConsumer struct { handle etw.TraceHandle diff --git a/pkg/kstream/maps.go b/pkg/kstream/maps.go new file mode 100644 index 000000000..617393cff --- /dev/null +++ b/pkg/kstream/maps.go @@ -0,0 +1,75 @@ +//go:build linux +// +build linux + +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kstream + +import ( + "fmt" + + "github.com/cilium/ebpf" +) + +type MapType uint16 + +const ( + // Perf map is an array whose size is the number of available CPUs, + // and each cell contains a value relative to one CPU. + // The value to retrieve is indicated by flags, that + // contains the index of the CPU to look up, masked + // with BPF_F_INDEX_MASK. + Perf MapType = iota + Tracers + Discarders +) + +var ebpfMaps = [...]string{ + "perf", + "tracers", +} + +func (m MapType) String() string { + switch m { + case Perf: + return "perf" + case Tracers: + return "tracers" + } + return "" +} + +type Maps map[string]*ebpf.Map + +func (maps Maps) VerifyMaps() error { + for _, mapName := range ebpfMaps { + if _, ok := maps[mapName]; !ok { + return fmt.Errorf("missing map %s", mapName) + } + } + return nil +} + +func (maps Maps) GetMap(m MapType) *ebpf.Map { + return maps[m.String()] +} + +func (maps Maps) Put(m MapType, key, value interface{}) error { + return maps[m.String()].Put(key, value) +} diff --git a/pkg/pe/config.go b/pkg/pe/config.go index 8f03b7df0..3810b0ac1 100644 --- a/pkg/pe/config.go +++ b/pkg/pe/config.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/entropy.go b/pkg/pe/entropy.go index 23c9a2937..7265d63ca 100644 --- a/pkg/pe/entropy.go +++ b/pkg/pe/entropy.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/marshaller.go b/pkg/pe/marshaller.go index 5cdb528d0..5f6fbe3a4 100644 --- a/pkg/pe/marshaller.go +++ b/pkg/pe/marshaller.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/marshaller_test.go b/pkg/pe/marshaller_test.go index a0d807d72..f8adb66c6 100644 --- a/pkg/pe/marshaller_test.go +++ b/pkg/pe/marshaller_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/reader.go b/pkg/pe/reader.go index 45411ccce..603174511 100644 --- a/pkg/pe/reader.go +++ b/pkg/pe/reader.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/reader_test.go b/pkg/pe/reader_test.go index 441eebe76..9bd1274e6 100644 --- a/pkg/pe/reader_test.go +++ b/pkg/pe/reader_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/resource/types.go b/pkg/pe/resource/types.go index 379200bb7..213d6f824 100644 --- a/pkg/pe/resource/types.go +++ b/pkg/pe/resource/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/resources.go b/pkg/pe/resources.go index a4a4e47f6..e10fb6d84 100644 --- a/pkg/pe/resources.go +++ b/pkg/pe/resources.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/resources_test.go b/pkg/pe/resources_test.go index 712117f93..834701b60 100644 --- a/pkg/pe/resources_test.go +++ b/pkg/pe/resources_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/section.go b/pkg/pe/section.go index 137d2b32b..1d85f91f1 100644 --- a/pkg/pe/section.go +++ b/pkg/pe/section.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/section_test.go b/pkg/pe/section_test.go index 712117f93..834701b60 100644 --- a/pkg/pe/section_test.go +++ b/pkg/pe/section_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/pe/types.go b/pkg/pe/types.go index 0bbc56e2f..9b27ba048 100644 --- a/pkg/pe/types.go +++ b/pkg/pe/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/ps/peb.go b/pkg/ps/peb.go index 326611263..fe7c6c024 100644 --- a/pkg/ps/peb.go +++ b/pkg/ps/peb.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/ps/peb_test.go b/pkg/ps/peb_test.go index 1c0a76f0e..ef9cff998 100644 --- a/pkg/ps/peb_test.go +++ b/pkg/ps/peb_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/ps/types/types_linux.go b/pkg/ps/types/types_linux.go new file mode 100644 index 000000000..552de475a --- /dev/null +++ b/pkg/ps/types/types_linux.go @@ -0,0 +1,23 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +type PS struct { + Parent *PS +} diff --git a/pkg/syscall/etw/etw.go b/pkg/syscall/etw/etw.go index 47f75e122..42cb1fd50 100644 --- a/pkg/syscall/etw/etw.go +++ b/pkg/syscall/etw/etw.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/etw/types.go b/pkg/syscall/etw/types.go index 45629f289..375747c97 100644 --- a/pkg/syscall/etw/types.go +++ b/pkg/syscall/etw/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/file/file.go b/pkg/syscall/file/file.go index a053420e9..8a67e5086 100644 --- a/pkg/syscall/file/file.go +++ b/pkg/syscall/file/file.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/file/types.go b/pkg/syscall/file/types.go index 0ff4f8f93..30c07b084 100644 --- a/pkg/syscall/file/types.go +++ b/pkg/syscall/file/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/handle/handle.go b/pkg/syscall/handle/handle.go index 586c842a9..21f940bcc 100644 --- a/pkg/syscall/handle/handle.go +++ b/pkg/syscall/handle/handle.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/object/alpc.go b/pkg/syscall/object/alpc.go index eb7d2aee7..1be6e2d39 100644 --- a/pkg/syscall/object/alpc.go +++ b/pkg/syscall/object/alpc.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/object/event.go b/pkg/syscall/object/event.go index 832c4c63b..4515272e1 100644 --- a/pkg/syscall/object/event.go +++ b/pkg/syscall/object/event.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/object/mutant.go b/pkg/syscall/object/mutant.go index fe3aeb187..60762b7e4 100644 --- a/pkg/syscall/object/mutant.go +++ b/pkg/syscall/object/mutant.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/object/object.go b/pkg/syscall/object/object.go index 0ff234643..448e597c9 100644 --- a/pkg/syscall/object/object.go +++ b/pkg/syscall/object/object.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/object/types.go b/pkg/syscall/object/types.go index 68686ce17..19c13b299 100644 --- a/pkg/syscall/object/types.go +++ b/pkg/syscall/object/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/process/process.go b/pkg/syscall/process/process.go index 2eb9cca42..ff7379b6c 100644 --- a/pkg/syscall/process/process.go +++ b/pkg/syscall/process/process.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/process/types.go b/pkg/syscall/process/types.go index 7ef569a3d..1687bee8e 100644 --- a/pkg/syscall/process/types.go +++ b/pkg/syscall/process/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/registry/key.go b/pkg/syscall/registry/key.go index fdca2bd84..fd47bb952 100644 --- a/pkg/syscall/registry/key.go +++ b/pkg/syscall/registry/key.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/security/privileges.go b/pkg/syscall/security/privileges.go index a1b90885f..a249222f5 100644 --- a/pkg/syscall/security/privileges.go +++ b/pkg/syscall/security/privileges.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/security/sid.go b/pkg/syscall/security/sid.go index 94d8f2fe4..5740b042e 100644 --- a/pkg/syscall/security/sid.go +++ b/pkg/syscall/security/sid.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/sys/sys.go b/pkg/syscall/sys/sys.go index 0ba2fa041..c2064ba5a 100644 --- a/pkg/syscall/sys/sys.go +++ b/pkg/syscall/sys/sys.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/tdh/tdh.go b/pkg/syscall/tdh/tdh.go index 7f275fa57..14954be22 100644 --- a/pkg/syscall/tdh/tdh.go +++ b/pkg/syscall/tdh/tdh.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/tdh/types.go b/pkg/syscall/tdh/types.go index 48d8ab432..8db252245 100644 --- a/pkg/syscall/tdh/types.go +++ b/pkg/syscall/tdh/types.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/thread/thread.go b/pkg/syscall/thread/thread.go index df0fb910e..e8e15e4bc 100644 --- a/pkg/syscall/thread/thread.go +++ b/pkg/syscall/thread/thread.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/utf16/string.go b/pkg/syscall/utf16/string.go index ee5e32874..e59e65bac 100644 --- a/pkg/syscall/utf16/string.go +++ b/pkg/syscall/utf16/string.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/syscall/winerrno/errors.go b/pkg/syscall/winerrno/errors.go index 673b3b4ed..eeecee009 100644 --- a/pkg/syscall/winerrno/errors.go +++ b/pkg/syscall/winerrno/errors.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/util/filetime/filetime.go b/pkg/util/filetime/filetime.go index b931976ba..b8fd5298f 100644 --- a/pkg/util/filetime/filetime.go +++ b/pkg/util/filetime/filetime.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/util/hostname/hostname.go b/pkg/util/hostname/hostname.go new file mode 100644 index 000000000..2b386ba2c --- /dev/null +++ b/pkg/util/hostname/hostname.go @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hostname + +import "expvar" + +// hostname is the current host name or FQDN +var hostname string + +// hostnameErrors exposes host/fqdn resolution errors +var hostnameErrors = expvar.NewMap("hostname.errors") diff --git a/pkg/util/hostname/hostname_linux.go b/pkg/util/hostname/hostname_linux.go new file mode 100644 index 000000000..27c86d1f2 --- /dev/null +++ b/pkg/util/hostname/hostname_linux.go @@ -0,0 +1,35 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hostname + +import "os" + +// Get returns the host name or the FQDN of the machine. +func Get() string { + if hostname != "" { + return hostname + } + var err error + hostname, err = os.Hostname() + if err != nil { + hostnameErrors.Add(err.Error(), 1) + } + + return hostname +} diff --git a/pkg/util/hostname/hostname_windows.go b/pkg/util/hostname/hostname_windows.go index 2a8038fce..2bd8ada8e 100644 --- a/pkg/util/hostname/hostname_windows.go +++ b/pkg/util/hostname/hostname_windows.go @@ -26,12 +26,6 @@ import ( "unsafe" ) -// hostname is the current host name or FQDN -var hostname string - -// hostnameErrors exposes host/fqdn resolution errors -var hostnameErrors = expvar.NewMap("hostname.errors") - const computerNamePhysicalDNSFullyQualified = 7 var ( diff --git a/pkg/util/ip/ip.go b/pkg/util/ip/ip.go index e5dae3595..ddde28cad 100644 --- a/pkg/util/ip/ip.go +++ b/pkg/util/ip/ip.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/util/ip/ip_test.go b/pkg/util/ip/ip_test.go index d4dd9af4d..29543f189 100644 --- a/pkg/util/ip/ip_test.go +++ b/pkg/util/ip/ip_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/util/limit/limit.go b/pkg/util/limit/limit.go new file mode 100644 index 000000000..b2cc475e1 --- /dev/null +++ b/pkg/util/limit/limit.go @@ -0,0 +1,35 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package limit + +import "golang.org/x/sys/unix" + +func With(cur, max uint64) *unix.Rlimit { + return &unix.Rlimit{ + Cur: cur, + Max: max, + } +} + +func WithInfinity() *unix.Rlimit { + return &unix.Rlimit{ + Cur: unix.RLIM_INFINITY, + Max: unix.RLIM_INFINITY, + } +} diff --git a/pkg/util/log/logger.go b/pkg/util/log/logger.go index e34bb3d84..da20bb696 100644 --- a/pkg/util/log/logger.go +++ b/pkg/util/log/logger.go @@ -22,12 +22,14 @@ import ( "errors" "expvar" "fmt" - "github.com/rabbitstack/fibratus/pkg/util/log/rotate" - fs "github.com/rifflock/lfshook" - "github.com/sirupsen/logrus" "io/ioutil" "os" "path/filepath" + "runtime" + + "github.com/rabbitstack/fibratus/pkg/util/log/rotate" + fs "github.com/rifflock/lfshook" + "github.com/sirupsen/logrus" ) var ( @@ -39,10 +41,14 @@ var ( func InitFromConfig(c Config) error { exe, err := os.Executable() var path string - if err != nil { - path = filepath.Join(os.Getenv("PROGRAMFILES"), "fibratus", "logs") + if runtime.GOOS == "windows" { + if err != nil { + path = filepath.Join(os.Getenv("PROGRAMFILES"), "fibratus", "logs") + } else { + path = filepath.Join(filepath.Dir(exe), "..", "logs") + } } else { - path = filepath.Join(filepath.Dir(exe), "..", "logs") + path = "/var/run/fibratus/logs" } if c.Path != "" { path = c.Path @@ -68,7 +74,7 @@ func InitFromConfig(c Config) error { case "json": formatter = &logrus.JSONFormatter{} case "text": - formatter = &logrus.TextFormatter{} + formatter = &logrus.TextFormatter{FullTimestamp: true} default: formatter = &logrus.JSONFormatter{} } diff --git a/pkg/util/rest/rest.go b/pkg/util/rest/rest.go index c712326c0..6d82c6b2f 100644 --- a/pkg/util/rest/rest.go +++ b/pkg/util/rest/rest.go @@ -21,9 +21,7 @@ package rest import ( "context" "errors" - "github.com/rabbitstack/fibratus/pkg/api" "io/ioutil" - "net" "net/http" "path" "strings" @@ -42,22 +40,6 @@ type opts struct { // Option represents the option for the HTTP client. type Option func(o *opts) -// WithTransport sets the preferred transport for the HTTP client. -func WithTransport(addr string) Option { - return func(o *opts) { - o.addr = addr - if strings.HasPrefix(addr, `npipe:///`) { - transport = &http.Transport{ - DialContext: api.DialPipe(addr), - } - } else { - transport = &http.Transport{ - DialContext: (&net.Dialer{}).DialContext, - } - } - } -} - // WithURI initializes the URI where the request is sent. func WithURI(uri string) Option { return func(o *opts) { diff --git a/pkg/util/rest/rest_linux.go b/pkg/util/rest/rest_linux.go new file mode 100644 index 000000000..377ceaabe --- /dev/null +++ b/pkg/util/rest/rest_linux.go @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rest + +// WithTransport sets the preferred transport for the HTTP client. +func WithTransport(addr string) Option { + return func(o *opts) { + + } +} diff --git a/pkg/util/rest/rest_windows.go b/pkg/util/rest/rest_windows.go new file mode 100644 index 000000000..20fe74e9d --- /dev/null +++ b/pkg/util/rest/rest_windows.go @@ -0,0 +1,35 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rest + +// WithTransport sets the preferred transport for the HTTP client. +func WithTransport(addr string) Option { + return func(o *opts) { + o.addr = addr + if strings.HasPrefix(addr, `npipe:///`) { + transport = &http.Transport{ + DialContext: api.DialPipe(addr), + } + } else { + transport = &http.Transport{ + DialContext: (&net.Dialer{}).DialContext, + } + } + } +} diff --git a/pkg/util/rest/rest_test.go b/pkg/util/rest/rest_windows_test.go similarity index 100% rename from pkg/util/rest/rest_test.go rename to pkg/util/rest/rest_windows_test.go diff --git a/pkg/util/service/service_linux.go b/pkg/util/service/service_linux.go new file mode 100644 index 000000000..8ddfc7a52 --- /dev/null +++ b/pkg/util/service/service_linux.go @@ -0,0 +1,21 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +func IsInteractive() bool { return true } diff --git a/pkg/util/service/service_windows.go b/pkg/util/service/service_windows.go new file mode 100644 index 000000000..0e14ee9b2 --- /dev/null +++ b/pkg/util/service/service_windows.go @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import "golang.org/x/sys/windows/svc" + +func IsInteractive() bool { + in, err := svc.IsAnInteractiveSession() + if err != nil || !in { + return false + } + return in +} diff --git a/pkg/util/term/fb.go b/pkg/util/term/fb.go index 0b44610a6..27523132b 100644 --- a/pkg/util/term/fb.go +++ b/pkg/util/term/fb.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/util/term/term.go b/pkg/util/term/term.go index 997a5550a..ea5cd977a 100644 --- a/pkg/util/term/term.go +++ b/pkg/util/term/term.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows /* diff --git a/pkg/util/user/user_linux.go b/pkg/util/user/user_linux.go new file mode 100644 index 000000000..8bbb2b9ba --- /dev/null +++ b/pkg/util/user/user_linux.go @@ -0,0 +1,26 @@ +/* + * Copyright 2020-2021 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package user + +import "os" + +// IsRoot returns true if current user is root. +func IsRoot() bool { + return os.Geteuid() == 0 +} diff --git a/pkg/yara/scanner.go b/pkg/yara/scanner.go index bcc43f8a8..d2658603d 100644 --- a/pkg/yara/scanner.go +++ b/pkg/yara/scanner.go @@ -1,3 +1,4 @@ +//go:build yara // +build yara /* diff --git a/pkg/yara/scanner_test.go b/pkg/yara/scanner_test.go index bc51f899b..1198d78ba 100644 --- a/pkg/yara/scanner_test.go +++ b/pkg/yara/scanner_test.go @@ -1,3 +1,4 @@ +//go:build yara // +build yara /* diff --git a/pkg/yara/scanner_unsupported.go b/pkg/yara/scanner_unsupported.go index 14cd8622b..63f28b77f 100644 --- a/pkg/yara/scanner_unsupported.go +++ b/pkg/yara/scanner_unsupported.go @@ -1,3 +1,4 @@ +//go:build !yara // +build !yara /* diff --git a/pkg/yara/scanner_windows.go b/pkg/yara/scanner_windows.go index edb2c33c2..584592bda 100644 --- a/pkg/yara/scanner_windows.go +++ b/pkg/yara/scanner_windows.go @@ -1,3 +1,4 @@ +//go:build yara // +build yara /* From 820645eeebcd213fe09662fad0c20d417496555a Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Tue, 14 Sep 2021 17:22:00 +0200 Subject: [PATCH 2/5] introduce the `internal` package, more kstreamc work --- cmd/fibratus/app/run_linux.go | 2 +- internal/cgroups/cgroups.go | 46 +++++++++++++++++++ .../procfs/comm.go | 18 +++++--- internal/procfs/procfs.go | 33 +++++++++++++ pkg/config/config_linux.go | 6 +++ pkg/config/config_windows.go | 24 +++++++++- pkg/config/kstream_linux.go | 16 +++++-- pkg/config/output.go | 12 ----- pkg/ebpf/c/kprobe.c | 15 +++--- pkg/ebpf/c/maps.h | 6 +-- .../service_linux.go => ebpf/c/types.h} | 8 ++-- pkg/kstream/kstreamc_linux.go | 26 ++++++++--- pkg/kstream/maps.go | 22 +++++++++ 13 files changed, 189 insertions(+), 45 deletions(-) create mode 100644 internal/cgroups/cgroups.go rename pkg/util/service/service_windows.go => internal/procfs/comm.go (71%) create mode 100644 internal/procfs/procfs.go rename pkg/{util/service/service_linux.go => ebpf/c/types.h} (85%) diff --git a/cmd/fibratus/app/run_linux.go b/cmd/fibratus/app/run_linux.go index a5236ab85..5ae5f7241 100644 --- a/cmd/fibratus/app/run_linux.go +++ b/cmd/fibratus/app/run_linux.go @@ -71,7 +71,7 @@ func run(cmd *cobra.Command, args []string) error { // try to load the kprobe by reading the embedded // bytecode and parsing all of the ELF objects that // build up the kprobe program - kstreamc, err := kstream.NewConsumer() + kstreamc, err := kstream.NewConsumer(cfg) if err != nil { return err } diff --git a/internal/cgroups/cgroups.go b/internal/cgroups/cgroups.go new file mode 100644 index 000000000..8f53bc96b --- /dev/null +++ b/internal/cgroups/cgroups.go @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cgroups + +import ( + "io/ioutil" + "regexp" +) + +// cgroupContainerRegexp represents the regular expression for matching the cgroup path pertaning +// to one of the container engines or orchestration platforms. +// +// docker - regular Docker containers +// lxc - Linux containers +// kubepods - the cgroup path for Kubernetes-managed containers +// ecs - the cgroup path for ECS (Elastic Container service) containers +// libpod - podman containers +// crio - crio runtime containers +var cgroupContainerRegexp = regexp.MustCompile("docker|lxc|kubepods|ecs|libpod|crio") + +// InContainer determines whether the current process is running in a container. +var InContainer = matchesContainerCgroup() + +func matchesContainerCgroup() bool { + f, err := ioutil.ReadFile("/proc/self/cgroup") + if err != nil { + return false + } + return cgroupContainerRegexp.MatchString(string(f)) +} diff --git a/pkg/util/service/service_windows.go b/internal/procfs/comm.go similarity index 71% rename from pkg/util/service/service_windows.go rename to internal/procfs/comm.go index 0e14ee9b2..bbf047f5b 100644 --- a/pkg/util/service/service_windows.go +++ b/internal/procfs/comm.go @@ -16,14 +16,18 @@ * limitations under the License. */ -package service +package procfs -import "golang.org/x/sys/windows/svc" +import ( + "io/ioutil" + "strings" +) -func IsInteractive() bool { - in, err := svc.IsAnInteractiveSession() - if err != nil || !in { - return false +// SelfComm returns the process image name of the current process. +func SelfComm() string { + comm, err := ioutil.ReadFile("/proc/self/comm") + if err != nil { + return "fibratus" } - return in + return strings.TrimSpace(string(comm)) } diff --git a/internal/procfs/procfs.go b/internal/procfs/procfs.go new file mode 100644 index 000000000..f6f192e58 --- /dev/null +++ b/internal/procfs/procfs.go @@ -0,0 +1,33 @@ +/* + * Copyright 2019-2020 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package procfs + +import "os" + +// Path returns the path to the procfs. In container envs this path +// is overridden and points to the bind-mount location as specified +// when deploying the container. This function attempts to get the +// procfs location from the `PROCFS` environment variable and fallbacks +// to `/proc` if the env variable is not defined. +func Path() string { + if path := os.Getenv("PROCFS"); path != "" { + return path + } + return "/proc" +} diff --git a/pkg/config/config_linux.go b/pkg/config/config_linux.go index ed8f1a22d..04ba4d70a 100644 --- a/pkg/config/config_linux.go +++ b/pkg/config/config_linux.go @@ -18,6 +18,8 @@ package config +import "os" + type Config struct { BaseConfig } @@ -37,4 +39,8 @@ func (c *Config) Init() error { func (c *Config) addFlags() { c.flags.String(configFile, "/etc/fibratus/fibratus.yml", "Indicates the location of the configuration file") + if c.opts.run || c.opts.capture { + c.flags.Int(watermark, 128, "") + c.flags.Int(ringBufferSize, 8*os.Getpagesize(), "") + } } diff --git a/pkg/config/config_windows.go b/pkg/config/config_windows.go index 0c064c521..2f3424d51 100644 --- a/pkg/config/config_windows.go +++ b/pkg/config/config_windows.go @@ -83,7 +83,20 @@ func (c *Config) Init() error { kevent.SerializePE = c.viper.GetBool(serializePE) kevent.SerializeEnvs = c.viper.GetBool(serializeEnvs) - return c.init() + if err := c.init(); err != nil { + return err + } + + // if it is not an interactive session but the console output is enabled + // we default to null output and warn about that + if isWindowsService() && c.Output.Output != nil { + if c.Output.Type == outputs.Console { + log.Warn("running in non-interactive session with console output. " + + "Please configure a different output type. Defaulting to null output") + c.Output.Type, c.Output.Output = outputs.Null, &null.Config{} + return nil + } + } } func (c *Config) addFlags() { @@ -130,3 +143,12 @@ func (c *Config) addFlags() { } c.Log.AddFlags(c.flags) } + +// isWindowsService returns true if fibratus is running as Windows service. +func isWindowsService() bool { + in, err := svc.IsAnInteractiveSession() + if err != nil { + return false + } + return !in +} diff --git a/pkg/config/kstream_linux.go b/pkg/config/kstream_linux.go index 10d359e28..df749f4f3 100644 --- a/pkg/config/kstream_linux.go +++ b/pkg/config/kstream_linux.go @@ -20,11 +20,16 @@ package config import "github.com/spf13/viper" +const ( + watermark = "kstream.watermark" + ringBufferSize = "kstream.ring-buffer-size" +) + type KstreamConfig struct { - RingBufferSize int `json:"ring-buffer-size" yaml:"ring-buffer-size"` - Watermark int `json:"watermark" yaml:"watermark"` - VerifierLogsize int - EnableVerifierLogging bool + RingBufferSize int `json:"ring-buffer-size" yaml:"ring-buffer-size"` + Watermark int `json:"watermark" yaml:"watermark"` + VerifierLogsize int `json:"verifier-log-size" yaml:"verifier-log-size"` + EnableVerifierLogging bool `json:"enable-verifier-logging" yaml:"enable-verifier-logging"` // BlacklistKevents are kernel event names that will be dropped from the kernel event stream. BlacklistKevents []string `json:"blacklist.events" yaml:"blacklist.events"` // BlacklistImages are process image names that will be rejected if they generate a kernel event. @@ -32,5 +37,6 @@ type KstreamConfig struct { } func (k *KstreamConfig) initFromViper(v *viper.Viper) { - + k.RingBufferSize = v.GetInt(ringBufferSize) + k.Watermark = v.GetInt(watermark) } diff --git a/pkg/config/output.go b/pkg/config/output.go index c0ffc338f..dfb02553c 100644 --- a/pkg/config/output.go +++ b/pkg/config/output.go @@ -29,7 +29,6 @@ import ( "github.com/rabbitstack/fibratus/pkg/outputs/console" "github.com/rabbitstack/fibratus/pkg/outputs/elasticsearch" "github.com/rabbitstack/fibratus/pkg/outputs/null" - "github.com/rabbitstack/fibratus/pkg/util/service" log "github.com/sirupsen/logrus" ) @@ -105,17 +104,6 @@ func (c *BaseConfig) tryLoadOutput() error { } } - // if it is not an interactive session but the console output is enabled - // we default to null output and warn about that - if !service.IsInteractive() && c.Output.Output != nil { - if c.Output.Type == outputs.Console { - log.Warn("running in non-interactive session with console output. " + - "Please configure a different output type. Defaulting to null output") - c.Output.Type, c.Output.Output = outputs.Null, &null.Config{} - return nil - } - } - // default to null output if c.Output.Output == nil { log.Warn("all outputs disabled. Defaulting to null output") diff --git a/pkg/ebpf/c/kprobe.c b/pkg/ebpf/c/kprobe.c index 32bc0dd48..3e90bba07 100644 --- a/pkg/ebpf/c/kprobe.c +++ b/pkg/ebpf/c/kprobe.c @@ -46,6 +46,7 @@ #include "bpf/api.h" #include "kevent.h" +#include "types.h" #include "maps.h" #include "syscall.h" @@ -59,8 +60,10 @@ void tail_call(struct sys_exit_args *ctx, long syscall_id) { bpf_tail_call(ctx, &tracers, syscall_id); } -bool __attribute__((always_inline)) discard_pid(struct kevent_header *khdr) { - return bpf_map_lookup_elem(&pid_discarders, &khdr->pid) != NULL; +bool __attribute__((always_inline)) discard_kevent() { + struct discarder_key key; + bpf_get_current_comm(&key.comm, sizeof(key.comm)); + return bpf_map_lookup_elem(&discarders, &key) != NULL; } SEC("raw_tracepoint/sys_exit") @@ -75,6 +78,10 @@ int sys_exit_tracepoint(struct sys_exit_args *ctx) { SEC("raw_tracepoint/sys_read") int sys_read(struct sys_exit_args *ctx) { + if (discard_kevent()) { + return 0; + } + int offset = sizeof(struct kevent_header); u32 cpu = bpf_get_smp_processor_id(); @@ -96,10 +103,6 @@ int sys_read(struct sys_exit_args *ctx) { khdr->cpu = cpu; khdr->type = id; - if (discard_pid(khdr)) { - return 0; - } - int res = bpf_perf_event_output(ctx, &perf, BPF_F_CURRENT_CPU, diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h index 12e802a20..6a8e6edbf 100644 --- a/pkg/ebpf/c/maps.h +++ b/pkg/ebpf/c/maps.h @@ -37,10 +37,10 @@ struct bpf_map_def SEC("maps/tracers") tracers = { .max_entries = sizeof(u16), }; -struct bpf_map_def SEC("maps/pid_discarders") pid_discarders = { +struct bpf_map_def SEC("maps/discarders") discarders = { .type = BPF_MAP_TYPE_LRU_HASH, - .key_size = sizeof(u32), - .value_size = sizeof(u32), + .key_size = sizeof(struct discarder_key), + .value_size = sizeof(struct discarder_key), .max_entries = 32000, }; diff --git a/pkg/util/service/service_linux.go b/pkg/ebpf/c/types.h similarity index 85% rename from pkg/util/service/service_linux.go rename to pkg/ebpf/c/types.h index 8ddfc7a52..6126800ed 100644 --- a/pkg/util/service/service_linux.go +++ b/pkg/ebpf/c/types.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 by Nedim Sabic Sabic + * Copyright 2020-2021 by Nedim Sabic Sabic * https://www.fibratus.io * All Rights Reserved. * @@ -16,6 +16,6 @@ * limitations under the License. */ -package service - -func IsInteractive() bool { return true } + struct discarder_key { + char comm[TASK_COMM_LEN]; + }; \ No newline at end of file diff --git a/pkg/kstream/kstreamc_linux.go b/pkg/kstream/kstreamc_linux.go index c009022da..aeda8aed2 100644 --- a/pkg/kstream/kstreamc_linux.go +++ b/pkg/kstream/kstreamc_linux.go @@ -24,7 +24,6 @@ import ( "errors" "expvar" "fmt" - "os" "strconv" "strings" "time" @@ -33,6 +32,7 @@ import ( "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/perf" + "github.com/rabbitstack/fibratus/internal/procfs" "github.com/rabbitstack/fibratus/pkg/config" "github.com/rabbitstack/fibratus/pkg/filter" "github.com/rabbitstack/fibratus/pkg/kevent" @@ -57,6 +57,8 @@ var ( errKprobeNotEmbedded = errors.New("kprobe object file was not embedded or couldn't be read") // lostPerfEvents computes lost event samples per CPU lostPerfEvents = expvar.NewMap("kevent.lost.perf.events") + + discarderFailedInsertions = expvar.NewMap("kstream.discarders.failed.insertions") ) type kstreamConsumer struct { @@ -142,9 +144,16 @@ func NewConsumer(config *config.Config) (Consumer, error) { } } - // populate discarders map - for _, proc := range config.Kstream.BlacklistImages { - + // populate discarders map with process image names. + // Any event that is originated by the process image + // present in the discarders map is dropped in the raw + // syscall tracepoint hook + comms := append(config.Kstream.BlacklistImages, procfs.SelfComm()) + for _, comm := range comms { + key := NewDiscarderKey(comm) + if err := maps.Put(Discarders, key, key); err != nil { + discarderFailedInsertions.Add(err.Error(), 1) + } } kconsumer := &kstreamConsumer{ @@ -163,12 +172,17 @@ func NewConsumer(config *config.Config) (Consumer, error) { // intercepting all syscall exit events and polls the perf ring buffer // for incoming events. func (k *kstreamConsumer) OpenKstream() error { + watermark := k.config.Kstream.Watermark + perCPUBuffer := k.config.Kstream.RingBufferSize + if watermark > perCPUBuffer { + watermark = perCPUBuffer / 2 + } var err error readerOpts := perf.ReaderOptions{ - Watermark: 128, + Watermark: watermark, } perfMap := k.maps.GetMap(Perf) - k.perfReader, err = perf.NewReaderWithOptions(perfMap, 8*os.Getpagesize(), readerOpts) + k.perfReader, err = perf.NewReaderWithOptions(perfMap, perCPUBuffer, readerOpts) if err != nil { return err } diff --git a/pkg/kstream/maps.go b/pkg/kstream/maps.go index 617393cff..e61aa6e50 100644 --- a/pkg/kstream/maps.go +++ b/pkg/kstream/maps.go @@ -43,6 +43,20 @@ const ( var ebpfMaps = [...]string{ "perf", "tracers", + "discarders", +} + +type DiscarderKey struct { + Comm [16]byte +} + +func (d *DiscarderKey) MarshalBinary() ([]byte, error) { + return d.Comm[:], nil +} + +func (d *DiscarderKey) UnmarshalBinary(buf []byte) error { + copy(d.Comm[:], buf) + return nil } func (m MapType) String() string { @@ -51,6 +65,8 @@ func (m MapType) String() string { return "perf" case Tracers: return "tracers" + case Discarders: + return "discarders" } return "" } @@ -73,3 +89,9 @@ func (maps Maps) GetMap(m MapType) *ebpf.Map { func (maps Maps) Put(m MapType, key, value interface{}) error { return maps[m.String()].Put(key, value) } + +func NewDiscarderKey(proc string) *DiscarderKey { + var comm [16]byte + copy(comm[:], proc) + return &DiscarderKey{comm} +} From e5db8a80c653f31cbb756f32933276dd15c1a1c2 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Sat, 18 Sep 2021 13:08:16 +0200 Subject: [PATCH 3/5] fix windows compile errors --- cmd/fibratus/app/capture_windows.go | 9 ++------- pkg/config/config_windows.go | 23 ++++++++++++++--------- pkg/kcap/version/version_windows.go | 3 --- pkg/util/hostname/hostname_windows.go | 1 - pkg/util/limit/limit.go | 3 +++ pkg/util/rest/rest_windows.go | 8 ++++++++ 6 files changed, 27 insertions(+), 20 deletions(-) diff --git a/cmd/fibratus/app/capture_windows.go b/cmd/fibratus/app/capture_windows.go index 9f7ff4794..df2c5270b 100644 --- a/cmd/fibratus/app/capture_windows.go +++ b/cmd/fibratus/app/capture_windows.go @@ -19,6 +19,8 @@ package app import ( + "time" + "github.com/rabbitstack/fibratus/cmd/fibratus/common" "github.com/rabbitstack/fibratus/pkg/api" "github.com/rabbitstack/fibratus/pkg/config" @@ -32,15 +34,8 @@ import ( "github.com/rabbitstack/fibratus/pkg/util/spinner" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "time" ) -var captureCmd = &cobra.Command{ - Use: "capture [filter]", - Short: "Capture kernel event stream to the kcap file", - RunE: capture, -} - var ( // capture command config captureConfig = config.NewWithOpts(config.WithCapture()) diff --git a/pkg/config/config_windows.go b/pkg/config/config_windows.go index 2f3424d51..4dacd86b1 100644 --- a/pkg/config/config_windows.go +++ b/pkg/config/config_windows.go @@ -19,20 +19,18 @@ package config import ( - "encoding/json" - "fmt" - "io/ioutil" "time" - "github.com/rabbitstack/fibratus/pkg/kevent" - "gopkg.in/yaml.v3" "os" "path/filepath" - "strings" + + "github.com/rabbitstack/fibratus/pkg/kevent" + "github.com/rabbitstack/fibratus/pkg/outputs" + "github.com/rabbitstack/fibratus/pkg/outputs/null" + log "github.com/sirupsen/logrus" + "golang.org/x/sys/windows/svc" "github.com/rabbitstack/fibratus/pkg/pe" - "github.com/spf13/pflag" - "github.com/spf13/viper" ) const ( @@ -61,8 +59,14 @@ type Config struct { func NewWithOpts(options ...Option) *Config { config := newWithOpts(options...) + opts := &Options{} + + for _, opt := range options { + opt(opts) + } + if opts.run || opts.capture { - pe.AddFlags(flagSet) + pe.AddFlags(config.flags) } config.addFlags() @@ -97,6 +101,7 @@ func (c *Config) Init() error { return nil } } + return nil } func (c *Config) addFlags() { diff --git a/pkg/kcap/version/version_windows.go b/pkg/kcap/version/version_windows.go index 11391969d..bf32bde28 100644 --- a/pkg/kcap/version/version_windows.go +++ b/pkg/kcap/version/version_windows.go @@ -18,9 +18,6 @@ package version -// Version designates the type for specifying the current section version. -type Version uint16 - const ( // KevtSecV1 is the v1 of the kernel event section KevtSecV1 Version = iota + 1 diff --git a/pkg/util/hostname/hostname_windows.go b/pkg/util/hostname/hostname_windows.go index 2bd8ada8e..c5d561768 100644 --- a/pkg/util/hostname/hostname_windows.go +++ b/pkg/util/hostname/hostname_windows.go @@ -19,7 +19,6 @@ package hostname import ( - "expvar" "net" "os" "syscall" diff --git a/pkg/util/limit/limit.go b/pkg/util/limit/limit.go index b2cc475e1..0f77e7a93 100644 --- a/pkg/util/limit/limit.go +++ b/pkg/util/limit/limit.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + /* * Copyright 2019-2020 by Nedim Sabic Sabic * https://www.fibratus.io diff --git a/pkg/util/rest/rest_windows.go b/pkg/util/rest/rest_windows.go index 20fe74e9d..e1791e0f7 100644 --- a/pkg/util/rest/rest_windows.go +++ b/pkg/util/rest/rest_windows.go @@ -18,6 +18,14 @@ package rest +import ( + "net" + "net/http" + "strings" + + "github.com/rabbitstack/fibratus/pkg/api" +) + // WithTransport sets the preferred transport for the HTTP client. func WithTransport(addr string) Option { return func(o *opts) { From 5639aaaa6dd893b603a4d095a1c8280b5efeb1ba Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Sat, 18 Sep 2021 13:09:50 +0200 Subject: [PATCH 4/5] typo --- pkg/config/config_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/config_windows.go b/pkg/config/config_windows.go index 4dacd86b1..34c5cb971 100644 --- a/pkg/config/config_windows.go +++ b/pkg/config/config_windows.go @@ -74,7 +74,7 @@ func NewWithOpts(options ...Option) *Config { return config } -// Init setups the configuration state from Viper. +// Init sets up the configuration state from Viper. func (c *Config) Init() error { c.PE.InitFromViper(c.viper) From ee0f49bcb431451dcd07d35ea313bb6607e9fb16 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Wed, 8 Dec 2021 17:53:59 +0100 Subject: [PATCH 5/5] ebpf support groundwork --- internal/cgroups/cgroups.go | 17 ++++++--- pkg/ebpf/c/kevent.h | 12 +++--- pkg/ebpf/c/kprobe.c | 6 +-- pkg/ebpf/c/maps.h | 7 ++++ pkg/ebpf/c/types.h | 19 ++++++++-- pkg/kevent/ktypes/ktypes_linux.go | 10 ++++- pkg/kevent/sequencer_linux.go | 13 +++++++ pkg/kstream/kstreamc.go | 3 ++ pkg/kstream/kstreamc_linux.go | 50 +++++++++++++++++-------- pkg/kstream/kstreamc_windows.go | 4 +- pkg/kstream/maps.go | 61 +++++++++++++++++++++++++++++-- 11 files changed, 162 insertions(+), 40 deletions(-) diff --git a/internal/cgroups/cgroups.go b/internal/cgroups/cgroups.go index 8f53bc96b..8f1cf18cf 100644 --- a/internal/cgroups/cgroups.go +++ b/internal/cgroups/cgroups.go @@ -20,22 +20,27 @@ package cgroups import ( "io/ioutil" + "os" "regexp" ) -// cgroupContainerRegexp represents the regular expression for matching the cgroup path pertaning -// to one of the container engines or orchestration platforms. +// cgroupContainerRegexp represents the regular expression for matching the process' cgroup +// hierarchy pertaning to container engines and orchestration platforms. // // docker - regular Docker containers // lxc - Linux containers -// kubepods - the cgroup path for Kubernetes-managed containers -// ecs - the cgroup path for ECS (Elastic Container service) containers +// kubepods - Kubernetes-managed containers +// ecs - ECS (Elastic Container service) containers // libpod - podman containers // crio - crio runtime containers +// var cgroupContainerRegexp = regexp.MustCompile("docker|lxc|kubepods|ecs|libpod|crio") -// InContainer determines whether the current process is running in a container. -var InContainer = matchesContainerCgroup() +// IsContainerized determines whether the current process is running in a container. +var IsContainerized = matchesContainerCgroup() + +// IsInKubernetes determines whether the current process is running in Kubernetes pod. +var IsInKubernetes = IsContainerized && os.Getenv("KUBERNETES_PORT_443_TCP") != "" func matchesContainerCgroup() bool { f, err := ioutil.ReadFile("/proc/self/cgroup") diff --git a/pkg/ebpf/c/kevent.h b/pkg/ebpf/c/kevent.h index c72810816..8d36edc00 100644 --- a/pkg/ebpf/c/kevent.h +++ b/pkg/ebpf/c/kevent.h @@ -17,10 +17,10 @@ */ struct kevent_header { - u64 ts; /* timestamp in nanoseconds from epoch */ - u32 pid; /* process identifier that produced the event */ - u32 tid; /* thread identifier that produced the event */ - u32 cpu; /* logical core on which the event was generated */ - u32 nparams; /* number of event parameters */ - u16 type; /* event type which corresponds to syscall id */ + u64 ts; /* Timestamp in nanoseconds from epoch */ + u32 pid; /* Process identifier that produced the event */ + u32 tid; /* Thread identifier that produced the event */ + u32 cpu; /* Logical core on which the event was generated */ + u32 nparams; /* Number of event parameters */ + u16 type; /* Event type which maps to syscall id */ }; diff --git a/pkg/ebpf/c/kprobe.c b/pkg/ebpf/c/kprobe.c index 3e90bba07..57c729e90 100644 --- a/pkg/ebpf/c/kprobe.c +++ b/pkg/ebpf/c/kprobe.c @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include /* In Linux 5.4 asm_inline was introduced, but it's not supported by clang. @@ -70,7 +70,7 @@ SEC("raw_tracepoint/sys_exit") int sys_exit_tracepoint(struct sys_exit_args *ctx) { struct pt_regs *regs = (struct pt_regs *)ctx->regs; - long id = _READ(regs->orig_ax); + long id = _READ(regs->orig_ax); tail_call(ctx, id); return 0; @@ -89,7 +89,7 @@ int sys_read(struct sys_exit_args *ctx) { struct pt_regs *regs = (struct pt_regs *)ctx->regs; - long id = _READ(regs->orig_ax); + long id = _READ(regs->orig_ax); char *buf = bpf_map_lookup_elem(&buffer_area, &cpu); if (buf == NULL) { diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h index 6a8e6edbf..63f2901f4 100644 --- a/pkg/ebpf/c/maps.h +++ b/pkg/ebpf/c/maps.h @@ -44,6 +44,13 @@ struct bpf_map_def SEC("maps/discarders") discarders = { .max_entries = 32000, }; +struct bpf_map_def SEC("maps/kparspecs") kparspecs = { + .type = BPF_MAP_TYPE_LRU_HASH, + .key_size = sizeof(u32), + .value_size = sizeof(struct kpars_value), + .max_entries = sizeof(u16), +}; + struct bpf_map_def SEC("maps/buffer_area") buffer_area = { .type = BPF_MAP_TYPE_ARRAY, .key_size = sizeof(u32), diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index 6126800ed..6f21ba150 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -16,6 +16,19 @@ * limitations under the License. */ - struct discarder_key { - char comm[TASK_COMM_LEN]; - }; \ No newline at end of file +#define MAX_KPARS (1 << 5) /* Max number of event parameters */ +#define MAX_KPAR_NAME 32 /* Max size of the parameter name */ + +struct discarder_key { + char comm[TASK_COMM_LEN]; +}; + +struct kpar_spec { + char name[MAX_KPAR_NAME]; + u16 type; +}; + +struct kpars_value { + u32 nparams; + struct kpar_spec specs[MAX_KPARS]; +}; diff --git a/pkg/kevent/ktypes/ktypes_linux.go b/pkg/kevent/ktypes/ktypes_linux.go index 7488a0afb..c3ed4a734 100644 --- a/pkg/kevent/ktypes/ktypes_linux.go +++ b/pkg/kevent/ktypes/ktypes_linux.go @@ -18,7 +18,10 @@ package ktypes -import "math" +import ( + "math" + "strings" +) // Ktype represents the kernel event type which usually maps to syscall identifier type Ktype uint16 @@ -46,3 +49,8 @@ func (k Ktype) String() string { func (k Ktype) RawID() uint32 { return uint32(k) } func (k Ktype) Hash() uint32 { return 0 } + +// FromProg creates the ktype from eBPF program name. +func FromProg(prog string) Ktype { + return KeventNameToKtype(strings.TrimPrefix(prog, "sys_")) +} diff --git a/pkg/kevent/sequencer_linux.go b/pkg/kevent/sequencer_linux.go index 91ca05474..ea5108409 100644 --- a/pkg/kevent/sequencer_linux.go +++ b/pkg/kevent/sequencer_linux.go @@ -18,7 +18,20 @@ package kevent +import "sync/atomic" + type Sequencer struct { + seq uint64 } func NewSequencer() *Sequencer { return &Sequencer{} } + +// Increment increments the sequence number atomically. +func (s *Sequencer) Increment() { + atomic.AddUint64(&s.seq, 1) +} + +// Get returns the current sequence number. +func (s *Sequencer) Get() uint64 { + return atomic.LoadUint64(&s.seq) +} diff --git a/pkg/kstream/kstreamc.go b/pkg/kstream/kstreamc.go index 1aca959b3..c1d6d3979 100644 --- a/pkg/kstream/kstreamc.go +++ b/pkg/kstream/kstreamc.go @@ -29,6 +29,9 @@ import ( var ( // failedKevents counts the number of kevents that failed to process grouped by error failedKevents = expvar.NewMap("kstream.kevents.failures") + + blacklistedKevents = expvar.NewMap("kstream.blacklist.dropped.kevents") + blacklistedProcs = expvar.NewMap("kstream.blacklist.dropped.procs") ) // Consumer is the interface all kernel event stream consumers have to satisfy. diff --git a/pkg/kstream/kstreamc_linux.go b/pkg/kstream/kstreamc_linux.go index aeda8aed2..dee24e394 100644 --- a/pkg/kstream/kstreamc_linux.go +++ b/pkg/kstream/kstreamc_linux.go @@ -25,7 +25,6 @@ import ( "expvar" "fmt" "strconv" - "strings" "time" "github.com/cilium/ebpf" @@ -118,18 +117,22 @@ func NewConsumer(config *config.Config) (Consumer, error) { if err != nil { return nil, fmt.Errorf("unable to load kprobe: %w", err) } - maps := Maps(objs.Maps) - if err := maps.VerifyMaps(); err != nil { + maps, err := ToMaps(objs.Maps) + if err != nil { return nil, err } + var ( + keventsBlacklist = makeKeventBlacklist(config.Kstream.BlacklistKevents) + commsBlacklist = makeImageBlacklist(config.Kstream.BlacklistImages) + ) + // populate tracer programs. This step consists of // traversing the collection of ebpf programs and // indexing the ktype number to the corresponding // program file descriptor. - keventsBlacklist := makeKeventBlacklist(config.Kstream.BlacklistKevents) for _, progSpec := range spec.Programs { - ktype := ktypes.KeventNameToKtype(strings.TrimPrefix(progSpec.Name, "sys_")) + ktype := ktypes.FromProg(progSpec.Name) if ktype == ktypes.UnknownKtype || keventsBlacklist.has(ktype) { continue } @@ -148,21 +151,31 @@ func NewConsumer(config *config.Config) (Consumer, error) { // Any event that is originated by the process image // present in the discarders map is dropped in the raw // syscall tracepoint hook - comms := append(config.Kstream.BlacklistImages, procfs.SelfComm()) - for _, comm := range comms { + for _, comm := range commsBlacklist { key := NewDiscarderKey(comm) if err := maps.Put(Discarders, key, key); err != nil { discarderFailedInsertions.Add(err.Error(), 1) } } + // populate kpar specs map + for _, kevtInfo := range ktypes.GetKtypesMeta() { + ktype := ktypes.KeventNameToKtype(kevtInfo.Name) + if ktype == ktypes.UnknownKtype { + continue + } + if err := maps.Put(KparSpecs, ktype.RawID(), NewKparsValue(kevtInfo.Kpars)); err != nil { + return nil, err + } + } kconsumer := &kstreamConsumer{ - objs: objs, - spec: spec, - maps: maps, - config: config, - kevts: make(chan *kevent.Kevent), - errs: make(chan error, 1000), + objs: objs, + spec: spec, + maps: maps, + config: config, + kevts: make(chan *kevent.Kevent), + errs: make(chan error, 1000), + sequencer: kevent.NewSequencer(), } return kconsumer, nil @@ -170,7 +183,7 @@ func NewConsumer(config *config.Config) (Consumer, error) { // OpenKstream attaches the eBPF program to the raw tracepoint for // intercepting all syscall exit events and polls the perf ring buffer -// for incoming events. +// for raw samples. func (k *kstreamConsumer) OpenKstream() error { watermark := k.config.Kstream.Watermark perCPUBuffer := k.config.Kstream.RingBufferSize @@ -229,6 +242,7 @@ func (k kstreamConsumer) CloseKstream() error { if err := k.tracepoint.Close(); err != nil { return err } + // release prog collection k.objs.Close() return k.perfReader.Close() } @@ -251,7 +265,7 @@ func (k kstreamConsumer) processKevent(rawSample []byte) error { return nil } kevt := kevent.New( - 1, + k.sequencer.Get(), header.Pid, header.Tid, uint8(header.CPU), @@ -261,6 +275,8 @@ func (k kstreamConsumer) processKevent(rawSample []byte) error { ) k.kevts <- kevt + k.sequencer.Increment() + return nil } @@ -284,3 +300,7 @@ func makeKeventBlacklist(kevents []string) blacklist { } return keventsBlacklist } + +func makeImageBlacklist(images []string) []string { + return append(images, procfs.SelfComm()) +} diff --git a/pkg/kstream/kstreamc_windows.go b/pkg/kstream/kstreamc_windows.go index 5082cdcc5..0156633d1 100644 --- a/pkg/kstream/kstreamc_windows.go +++ b/pkg/kstream/kstreamc_windows.go @@ -59,9 +59,7 @@ var ( // deferredEnqueued counts the number of deferred events deferredEnqueued = expvar.NewInt("kstream.deferred.kevents.enqueued") // failedKparams counts the number of kernel event parameters that failed to process - failedKparams = expvar.NewInt("kstream.kevent.param.failures") - blacklistedKevents = expvar.NewMap("kstream.blacklist.dropped.kevents") - blacklistedProcs = expvar.NewMap("kstream.blacklist.dropped.procs") + failedKparams = expvar.NewInt("kstream.kevent.param.failures") upstreamCancellations = expvar.NewInt("kstream.upstream.cancellations") diff --git a/pkg/kstream/maps.go b/pkg/kstream/maps.go index e61aa6e50..d2bebf978 100644 --- a/pkg/kstream/maps.go +++ b/pkg/kstream/maps.go @@ -22,11 +22,14 @@ package kstream import ( + "encoding/binary" "fmt" "github.com/cilium/ebpf" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" ) +// MapType is the type alias for possible map types in the eBPF program type MapType uint16 const ( @@ -36,20 +39,43 @@ const ( // contains the index of the CPU to look up, masked // with BPF_F_INDEX_MASK. Perf MapType = iota + // Tracers is the BPF_MAP_TYPE_PROG_ARRAY map type where the key is + // the event type identifier and the value represents the file + // descriptor of the eBPF program for the tail call Tracers + // Discarders is the BPF_MAP_TYPE_LRU_HASH map type that stores + // discriminants used to drop the event in kernel space. Discarders + KparSpecs ) +// maxKpars represents the maximum number of parameters in the kevent +const maxKpars = 1 << 5 + +// maxKparName is the max size of the parameter name +const maxKparName = 32 + var ebpfMaps = [...]string{ "perf", "tracers", "discarders", + "kparspecs", } type DiscarderKey struct { Comm [16]byte } +type KparSpec struct { + Name [maxKparName]byte + Type uint16 +} + +type KparsValue struct { + Nparams uint32 + Kpars [maxKpars]KparSpec +} + func (d *DiscarderKey) MarshalBinary() ([]byte, error) { return d.Comm[:], nil } @@ -59,6 +85,18 @@ func (d *DiscarderKey) UnmarshalBinary(buf []byte) error { return nil } +func (k KparsValue) MarshalBinary() ([]byte, error) { + b := make([]byte, 4+((2+maxKparName)*maxKpars)) + binary.LittleEndian.PutUint32(b, k.Nparams) + offset := 0 + for i := range k.Kpars { + copy(b[4+offset:], k.Kpars[i].Name[:]) + binary.LittleEndian.PutUint16(b[4+maxKparName+offset:], k.Kpars[i].Type) + offset += 2 + } + return b, nil +} + func (m MapType) String() string { switch m { case Perf: @@ -67,19 +105,24 @@ func (m MapType) String() string { return "tracers" case Discarders: return "discarders" + case KparSpecs: + return "kparspecs" } return "" } +// Maps represents the type alias that stores well-known ebpf maps type Maps map[string]*ebpf.Map -func (maps Maps) VerifyMaps() error { +// ToMaps converts the raw to aliased map type and +// checks the presence of the mandatory map definitions. +func ToMaps(maps map[string]*ebpf.Map) (Maps, error) { for _, mapName := range ebpfMaps { if _, ok := maps[mapName]; !ok { - return fmt.Errorf("missing map %s", mapName) + return nil, fmt.Errorf("missing map %s", mapName) } } - return nil + return Maps(maps), nil } func (maps Maps) GetMap(m MapType) *ebpf.Map { @@ -95,3 +138,15 @@ func NewDiscarderKey(proc string) *DiscarderKey { copy(comm[:], proc) return &DiscarderKey{comm} } + +func NewKparsValue(kpars []ktypes.KparInfo) *KparsValue { + kparsValue := &KparsValue{ + Nparams: uint32(len(kpars)), + } + for i, kpar := range kpars { + var kparName [maxKparName]byte + copy(kparName[:], kpar.Name) + kparsValue.Kpars[i] = KparSpec{Name: kparName, Type: uint16(kpar.Type)} + } + return kparsValue +}