From 0d01802e1f2ce61d81938e69d9b9a34a090da24b Mon Sep 17 00:00:00 2001 From: Nadav Strahilevitz Date: Wed, 29 May 2024 16:25:30 +0000 Subject: [PATCH] feat(ksymbols): restore lazy ksyms implementtion Use modified ksymbols implementation. The new implementation may take a list of required symbols and addresses to track. If the list is given, symbol scanning will only save those symbols or addresses which were given in the list. If a new symbol is queried, then a rescan is needed. Refactor tracee initialization to find all necessary symbols to track ahead of runtime. --- pkg/ebpf/probes/probe_group.go | 25 +---- pkg/ebpf/probes/probes.go | 1 + pkg/ebpf/probes/trace.go | 31 +++++- pkg/ebpf/tracee.go | 178 ++++++++++++++++++++++++++++----- 4 files changed, 189 insertions(+), 46 deletions(-) diff --git a/pkg/ebpf/probes/probe_group.go b/pkg/ebpf/probes/probe_group.go index 8ca1d1d49f20..48222df44717 100644 --- a/pkg/ebpf/probes/probe_group.go +++ b/pkg/ebpf/probes/probe_group.go @@ -4,7 +4,6 @@ import ( "sync" bpf "github.com/aquasecurity/libbpfgo" - "github.com/aquasecurity/libbpfgo/helpers" "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/logger" @@ -14,8 +13,6 @@ import ( // ProbeGroup // -var kernelSymbolTable *helpers.KernelSymbolTable - // ProbeGroup is a collection of probes. type ProbeGroup struct { probesLock *sync.Mutex // disallow concurrent access to the probe group @@ -33,26 +30,17 @@ func NewProbeGroup(m *bpf.Module, p map[Handle]Probe) *ProbeGroup { } // GetProbe returns a probe type by its handle. -func (p *ProbeGroup) GetProbeType(handle Handle) string { +func (p *ProbeGroup) GetProbeType(handle Handle) ProbeType { p.probesLock.Lock() defer p.probesLock.Unlock() if r, ok := p.probes[handle]; ok { if probe, ok := r.(*TraceProbe); ok { - switch probe.probeType { - case KProbe: - return "kprobe" - case KretProbe: - return "kretprobe" - case Tracepoint: - return "tracepoint" - case RawTracepoint: - return "raw_tracepoint" - } + return probe.probeType } } - return "" + return InvalidProbeType } // Attach attaches a probe's program to its hook, by given handle. @@ -106,12 +94,7 @@ func (p *ProbeGroup) GetProbeByHandle(handle Handle) Probe { } // NewDefaultProbeGroup initializes the default ProbeGroup (TODO: extensions will use probe groups) -func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool, kSyms *helpers.KernelSymbolTable) (*ProbeGroup, error) { - if kSyms == nil { - return nil, errfmt.Errorf("kernel symbol table is nil") - } - kernelSymbolTable = kSyms // keep a reference to the kernel symbol table instead of creating a new one - +func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool) (*ProbeGroup, error) { binaryPath := "/proc/self/exe" allProbes := map[Handle]Probe{ diff --git a/pkg/ebpf/probes/probes.go b/pkg/ebpf/probes/probes.go index 61317f20750c..128a3254161d 100644 --- a/pkg/ebpf/probes/probes.go +++ b/pkg/ebpf/probes/probes.go @@ -148,4 +148,5 @@ const ( ExecuteAtFinishedARM ExecuteFinishedCompatARM ExecuteAtFinishedCompatARM + NoProbe = 10000 ) diff --git a/pkg/ebpf/probes/trace.go b/pkg/ebpf/probes/trace.go index 5a9e577f5cad..25e6ab85cb4e 100644 --- a/pkg/ebpf/probes/trace.go +++ b/pkg/ebpf/probes/trace.go @@ -4,6 +4,7 @@ import ( "strings" bpf "github.com/aquasecurity/libbpfgo" + "github.com/aquasecurity/libbpfgo/helpers" "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/logger" @@ -22,8 +23,23 @@ const ( KretProbe // github.com/iovisor/bcc/blob/master/docs/reference_guide.md#1-kp Tracepoint // github.com/iovisor/bcc/blob/master/docs/reference_guide.md#3-tracep RawTracepoint // github.com/iovisor/bcc/blob/master/docs/reference_guide.md#7-raw-tracep + InvalidProbeType ) +func (t ProbeType) String() string { + switch t { + case KProbe: + return "kprobe" + case KretProbe: + return "kretprobe" + case Tracepoint: + return "tracepoint" + case RawTracepoint: + return "raw_tracepoint" + } + return "" +} + // When attaching a traceProbe, by handle, to its eBPF program: // // Handle == traceProbe (types: rawTracepoint, kprobe, kretprobe) @@ -77,6 +93,19 @@ func (p *TraceProbe) attach(module *bpf.Module, args ...interface{}) error { return errfmt.WrapError(err) } + var ksyms *helpers.KernelSymbolTable + + for _, arg := range args { + switch a := arg.(type) { + case *helpers.KernelSymbolTable: + ksyms = a + } + } + + if ksyms == nil { + return errfmt.Errorf("trace probes needs kernel symbols table argument") + } + // KProbe and KretProbe switch p.probeType { @@ -94,7 +123,7 @@ func (p *TraceProbe) attach(module *bpf.Module, args ...interface{}) error { // kernels it won't fail when trying to attach to a symbol that has // multiple addresses. // - syms, err := kernelSymbolTable.GetSymbolByName(p.eventName) + syms, err := ksyms.GetSymbolByName(p.eventName) if err != nil { goto rollback } diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index 968d372e803a..f43b1d645a92 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -121,6 +121,11 @@ type Tracee struct { policyManager *policyManager // The dependencies of events used by Tracee eventsDependencies *dependencies.Manager + + // Ksymbols needed to be kept alive in table. + // This doens't mean they are required for tracee to function. + // TOOD: remove this in favor of dependency manager nodes + requiredKsyms []string } func (t *Tracee) Stats() *metrics.Stats { @@ -238,6 +243,7 @@ func New(cfg config.Config) (*Tracee, error) { func(id events.ID) events.Dependencies { return events.Core.GetDefinitionByID(id).GetDependencies() }), + requiredKsyms: []string{}, } t.eventsDependencies.SubscribeAdd( @@ -378,27 +384,12 @@ func New(cfg config.Config) (*Tracee, error) { } // Init initialize tracee instance and it's various subsystems, potentially -// performing external system operations to initialize them. NOTE: any -// initialization logic, especially one that causes side effects, should go -// here and not New(). +// performing external system operations to initialize them. +// NOTE: any initialization logic, especially one that causes side effects +// should go here and not New(). func (t *Tracee) Init(ctx gocontext.Context) error { var err error - // Init kernel symbols map - - err = capabilities.GetInstance().Specific( - func() error { - t.kernelSymbols, err = helpers.NewKernelSymbolTable() - return err - }, - cap.SYSLOG, - ) - if err != nil { - return errfmt.WrapError(err) - } - - t.validateKallsymsDependencies() // disable events w/ missing ksyms dependencies - // Initialize buckets cache var mntNSProcs map[int]int @@ -487,6 +478,42 @@ func (t *Tracee) Init(ctx gocontext.Context) error { } } + // Initialize eBPF probes + err = capabilities.GetInstance().EBPF( + func() error { + return t.initBPFProbes() + }, + ) + if err != nil { + t.Close() + return errfmt.WrapError(err) + } + + // Init kernel symbols map + err = t.initKsymTableRequiredSyms() + if err != nil { + return err + } + + fmt.Println("requiered syms", t.requiredKsyms) + + err = capabilities.GetInstance().Specific( + func() error { + t.kernelSymbols, err = helpers.NewKernelSymbolTable( + helpers.WithRequiredSymbols(t.requiredKsyms), + ) + // Cleanup memory in list + t.requiredKsyms = []string{} + return err + }, + cap.SYSLOG, + ) + if err != nil { + return errfmt.WrapError(err) + } + + t.validateKallsymsDependencies() // disable events w/ missing ksyms dependencies + // Initialize eBPF programs and maps err = capabilities.GetInstance().EBPF( @@ -601,11 +628,11 @@ func (t *Tracee) initTailCall(tailCall events.TailCall) error { if events.Core.GetDefinitionByID(events.ID(index)).IsSyscall() { // Optimization: enable enter/exit probes only if at least one syscall is enabled. once.Do(func() { - err := t.probes.Attach(probes.SyscallEnter__Internal) + err := t.probes.Attach(probes.SyscallEnter__Internal, t.kernelSymbols) if err != nil { logger.Errorw("error attaching to syscall enter", "error", err) } - err = t.probes.Attach(probes.SyscallExit__Internal) + err = t.probes.Attach(probes.SyscallExit__Internal, t.kernelSymbols) if err != nil { logger.Errorw("error attaching to syscall enter", "error", err) } @@ -862,6 +889,104 @@ func (t *Tracee) newConfig(cfg *policy.PoliciesConfig, version uint16) *Config { } } +func (t *Tracee) initKsymTableRequiredSyms() error { + // Get all required symbols needed in the table + // 1. all event ksym dependencies + // 2. specific cases (hooked_seq_ops, hooked_symbols, print_mem_dump) + for id := range t.eventsState { + if !events.Core.IsDefined(id) { + return errfmt.Errorf("event %d is not defined", id) + } + + depsNode, ok := t.eventsDependencies.GetEvent(id) + if !ok { + logger.Warnw("failed to extract required ksymbols from event", "event_id", id) + continue + } + // Add directly dependant symbols + deps := depsNode.GetDependencies() + ksyms := deps.GetKSymbols() + ksymNames := make([]string, len(ksyms)) + for _, sym := range ksyms { + ksymNames = append(ksymNames, sym.GetSymbolName()) + } + t.requiredKsyms = append(t.requiredKsyms, ksymNames...) + + // If kprobe/kretprobe, the event name itself is a required symbol + depsProbes := deps.GetProbes() + for _, probeDep := range depsProbes { + probe := t.probes.GetProbeByHandle(probeDep.GetHandle()) + traceProbe, ok := probe.(*probes.TraceProbe) + if !ok { + continue + } + probeType := traceProbe.GetProbeType() + switch probeType { + case probes.KProbe, probes.KretProbe: + t.requiredKsyms = append(t.requiredKsyms, traceProbe.GetEventName()) + } + } + } + + // Specific cases + if _, ok := t.eventsState[events.HookedSeqOps]; ok { + for _, seqName := range derive.NetSeqOps { + t.requiredKsyms = append(t.requiredKsyms, seqName) + } + } + if _, ok := t.eventsState[events.HookedSyscall]; ok { + t.requiredKsyms = append(t.requiredKsyms, events.SyscallPrefix+"ni_syscall", "sys_ni_syscall") + for i, kernelRestrictionArr := range events.SyscallSymbolNames { + syscallName := t.getSyscallNameByKerVer(kernelRestrictionArr) + if syscallName == "" { + logger.Debugw("hooked_syscall (symbol): skipping syscall", "index", i) + continue + } + + t.requiredKsyms = append(t.requiredKsyms, events.SyscallPrefix+syscallName) + } + } + if _, ok := t.eventsState[events.PrintMemDump]; ok { + for it := t.config.Policies.CreateAllIterator(); it.HasNext(); { + p := it.Next() + // This might break in the future if PrintMemDump will become a dependency of another event. + _, isChosen := p.EventsToTrace[events.PrintMemDump] + if !isChosen { + continue + } + printMemDumpFilters := p.DataFilter.GetEventFilters(events.PrintMemDump) + if len(printMemDumpFilters) == 0 { + continue + } + symbolsFilter, ok := printMemDumpFilters["symbol_name"].(*filters.StringFilter) + if symbolsFilter == nil || !ok { + continue + } + + for _, field := range symbolsFilter.Equal() { + symbolSlice := strings.Split(field, ":") + splittedLen := len(symbolSlice) + var name string + if splittedLen == 1 { + name = symbolSlice[0] + } else if splittedLen == 2 { + name = symbolSlice[1] + } else { + continue + } + t.requiredKsyms = append( + t.requiredKsyms, + name, + "sys_"+name, + "__x64_sys_"+name, + "__arm64_sys_"+name, + ) + } + } + } + return nil +} + // getUnavKsymsPerEvtID returns event IDs and symbols that are unavailable to them. func (t *Tracee) getUnavKsymsPerEvtID() map[events.ID][]string { unavSymsPerEvtID := map[events.ID][]string{} @@ -1088,7 +1213,7 @@ func (t *Tracee) attachProbes() error { // Attach probes to their respective eBPF programs or cancel events if a required probe is missing. for probe, evtID := range probesToEvents { - err = t.probes.Attach(probe.GetHandle(), t.cgroups) // attach bpf program to probe + err = t.probes.Attach(probe.GetHandle(), t.cgroups, t.kernelSymbols) // attach bpf program to probe if err != nil { for _, evtID := range evtID { evtName := events.Core.GetDefinitionByID(evtID).GetName() @@ -1112,9 +1237,8 @@ func (t *Tracee) attachProbes() error { return nil } -func (t *Tracee) initBPF() error { +func (t *Tracee) initBPFProbes() error { var err error - // Execute code with higher privileges: ring1 (required) newModuleArgs := bpf.NewModuleArgs{ @@ -1132,11 +1256,17 @@ func (t *Tracee) initBPF() error { // Initialize probes - t.probes, err = probes.NewDefaultProbeGroup(t.bpfModule, t.netEnabled(), t.kernelSymbols) + t.probes, err = probes.NewDefaultProbeGroup(t.bpfModule, t.netEnabled()) if err != nil { return errfmt.WrapError(err) } + return nil +} + +func (t *Tracee) initBPF() error { + var err error + // Load the eBPF object into kernel err = t.bpfModule.BPFLoadObject()