Skip to content

Commit

Permalink
feat(ksymbols): restore lazy ksyms implementtion
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
NDStrahilevitz committed May 29, 2024
1 parent f922536 commit 0d01802
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 46 deletions.
25 changes: 4 additions & 21 deletions pkg/ebpf/probes/probe_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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{
Expand Down
1 change: 1 addition & 0 deletions pkg/ebpf/probes/probes.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,5 @@ const (
ExecuteAtFinishedARM
ExecuteFinishedCompatARM
ExecuteAtFinishedCompatARM
NoProbe = 10000
)
31 changes: 30 additions & 1 deletion pkg/ebpf/probes/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down
178 changes: 154 additions & 24 deletions pkg/ebpf/tracee.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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()
Expand All @@ -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{
Expand All @@ -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()
Expand Down

0 comments on commit 0d01802

Please sign in to comment.