Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix: Use shared string maps in kprobe-multi #1582

Merged
merged 3 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contrib/tester-progs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ uprobe-test-1
uprobe-test-2
lseek-pipe
threads-tester
bench-reader
threads-exit
6 changes: 3 additions & 3 deletions pkg/selectors/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -1258,16 +1258,16 @@ func parseSelector(
//
// For some examples, see kernel_test.go
func InitKernelSelectors(selectors []v1alpha1.KProbeSelector, args []v1alpha1.KProbeArg, actionArgTable *idtable.Table) ([4096]byte, error) {
kernelSelectors, err := InitKernelSelectorState(selectors, args, actionArgTable, nil)
kernelSelectors, err := InitKernelSelectorState(selectors, args, actionArgTable, nil, nil)
if err != nil {
return [4096]byte{}, err
}
return kernelSelectors.e, nil
}

func InitKernelSelectorState(selectors []v1alpha1.KProbeSelector, args []v1alpha1.KProbeArg,
actionArgTable *idtable.Table, listReader ValueReader) (*KernelSelectorState, error) {
kernelSelectors := NewKernelSelectorState(listReader)
actionArgTable *idtable.Table, listReader ValueReader, maps *KernelSelectorMaps) (*KernelSelectorState, error) {
kernelSelectors := NewKernelSelectorState(listReader, maps)

WriteSelectorUint32(kernelSelectors, uint32(len(selectors)))
soff := make([]uint32, len(selectors))
Expand Down
4 changes: 2 additions & 2 deletions pkg/selectors/kernel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func TestParseMatchArg(t *testing.T) {
}

arg1 := &v1alpha1.ArgSelector{Index: 1, Operator: "Equal", Values: []string{"foobar"}}
k := &KernelSelectorState{off: 0}
k := NewKernelSelectorState(nil, nil)
expected1 := []byte{
0x01, 0x00, 0x00, 0x00, // Index == 1
0x03, 0x00, 0x00, 0x00, // operator == equal
Expand Down Expand Up @@ -318,7 +318,7 @@ func TestParseMatchArg(t *testing.T) {
expected3 := append(length, expected1[:]...)
expected3 = append(expected3, expected2[:]...)
arg12 := []v1alpha1.ArgSelector{*arg1, *arg2}
ks := &KernelSelectorState{off: 0}
ks := NewKernelSelectorState(nil, nil)
if err := ParseMatchArgs(ks, arg12, sig); err != nil || bytes.Equal(expected3, ks.e[0:ks.off]) == false {
t.Errorf("parseMatchArgs: error %v expected:\n%v\nbytes:\n%v\nparsing %v\n", err, expected3, ks.e[0:k.off], arg3)
}
Expand Down
50 changes: 29 additions & 21 deletions pkg/selectors/selectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ type KernelLPMTrieStringPostfix struct {
data [StringPostfixMaxLength]byte
}

type KernelSelectorMaps struct {
// stringMaps are used to populate string and char buf matches
stringMaps StringMapLists
// stringPrefixMaps are used to populate string and char buf prefix matches
stringPrefixMaps []map[KernelLPMTrieStringPrefix]struct{}
// stringPostfixMaps are used to populate string and char buf postfix matches
stringPostfixMaps []map[KernelLPMTrieStringPostfix]struct{}
}

type KernelSelectorState struct {
off uint32 // offset into encoding
e [4096]byte // kernel encoding of selectors
Expand All @@ -92,20 +101,19 @@ type KernelSelectorState struct {
newBinVals map[uint32]string // these should be added in the names_map

listReader ValueReader
// stringMaps are used to populate string and char buf matches
stringMaps StringMapLists

// stringPrefixMaps are used to populate string and char buf prefix matches
stringPrefixMaps []map[KernelLPMTrieStringPrefix]struct{}
// stringPostfixMaps are used to populate string and char buf postfix matches
stringPostfixMaps []map[KernelLPMTrieStringPostfix]struct{}
maps *KernelSelectorMaps
}

func NewKernelSelectorState(listReader ValueReader) *KernelSelectorState {
func NewKernelSelectorState(listReader ValueReader, maps *KernelSelectorMaps) *KernelSelectorState {
if maps == nil {
maps = &KernelSelectorMaps{}
}
return &KernelSelectorState{
matchBinaries: make(map[int]*MatchBinariesMappings),
newBinVals: make(map[uint32]string),
listReader: listReader,
maps: maps,
}
}

Expand Down Expand Up @@ -165,15 +173,15 @@ func (k *KernelSelectorState) Addr6Maps() []map[KernelLPMTrie6]struct{} {
}

func (k *KernelSelectorState) StringMaps(subMap int) []map[[MaxStringMapsSize]byte]struct{} {
return k.stringMaps[subMap]
return k.maps.stringMaps[subMap]
}

func (k *KernelSelectorState) StringPrefixMaps() []map[KernelLPMTrieStringPrefix]struct{} {
return k.stringPrefixMaps
return k.maps.stringPrefixMaps
}

func (k *KernelSelectorState) StringPostfixMaps() []map[KernelLPMTrieStringPostfix]struct{} {
return k.stringPostfixMaps
return k.maps.stringPostfixMaps
}

// ValueMapsMaxEntries returns the maximum entries over all maps
Expand Down Expand Up @@ -212,7 +220,7 @@ func (k *KernelSelectorState) Addr6MapsMaxEntries() int {
// StringMapsMaxEntries returns the maximum entries over all maps inside a particular map of map
func (k *KernelSelectorState) StringMapsMaxEntries(subMap int) int {
maxEntries := 1
for _, vm := range k.stringMaps[subMap] {
for _, vm := range k.maps.stringMaps[subMap] {
if l := len(vm); l > maxEntries {
maxEntries = l
}
Expand All @@ -223,7 +231,7 @@ func (k *KernelSelectorState) StringMapsMaxEntries(subMap int) int {
// StringPrefixMapsMaxEntries returns the maximum entries over all maps
func (k *KernelSelectorState) StringPrefixMapsMaxEntries() int {
maxEntries := 1
for _, vm := range k.stringPrefixMaps {
for _, vm := range k.maps.stringPrefixMaps {
if l := len(vm); l > maxEntries {
maxEntries = l
}
Expand All @@ -234,7 +242,7 @@ func (k *KernelSelectorState) StringPrefixMapsMaxEntries() int {
// StringPostfixMapsMaxEntries returns the maximum entries over all maps
func (k *KernelSelectorState) StringPostfixMapsMaxEntries() int {
maxEntries := 1
for _, vm := range k.stringPostfixMaps {
for _, vm := range k.maps.stringPostfixMaps {
if l := len(vm); l > maxEntries {
maxEntries = l
}
Expand Down Expand Up @@ -402,8 +410,8 @@ func (k *KernelSelectorState) insertStringMaps(stringMaps SelectorStringMaps) [S

for subMap := 0; subMap < StringMapsNumSubMaps; subMap++ {
if len(stringMaps[subMap]) > 0 {
mapid = uint32(len(k.stringMaps[subMap]))
k.stringMaps[subMap] = append(k.stringMaps[subMap], stringMaps[subMap])
mapid = uint32(len(k.maps.stringMaps[subMap]))
k.maps.stringMaps[subMap] = append(k.maps.stringMaps[subMap], stringMaps[subMap])
} else {
mapid = 0xffffffff
}
Expand All @@ -414,13 +422,13 @@ func (k *KernelSelectorState) insertStringMaps(stringMaps SelectorStringMaps) [S
}

func (k *KernelSelectorState) newStringPrefixMap() (uint32, map[KernelLPMTrieStringPrefix]struct{}) {
mapid := len(k.stringPrefixMaps)
k.stringPrefixMaps = append(k.stringPrefixMaps, map[KernelLPMTrieStringPrefix]struct{}{})
return uint32(mapid), k.stringPrefixMaps[mapid]
mapid := len(k.maps.stringPrefixMaps)
k.maps.stringPrefixMaps = append(k.maps.stringPrefixMaps, map[KernelLPMTrieStringPrefix]struct{}{})
return uint32(mapid), k.maps.stringPrefixMaps[mapid]
}

func (k *KernelSelectorState) newStringPostfixMap() (uint32, map[KernelLPMTrieStringPostfix]struct{}) {
mapid := len(k.stringPostfixMaps)
k.stringPostfixMaps = append(k.stringPostfixMaps, map[KernelLPMTrieStringPostfix]struct{}{})
return uint32(mapid), k.stringPostfixMaps[mapid]
mapid := len(k.maps.stringPostfixMaps)
k.maps.stringPostfixMaps = append(k.maps.stringPostfixMaps, map[KernelLPMTrieStringPostfix]struct{}{})
return uint32(mapid), k.maps.stringPostfixMaps[mapid]
}
10 changes: 7 additions & 3 deletions pkg/sensors/tracing/generickprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ func createGenericKprobeSensor(
var maps []*program.Map
var multiIDs, multiRetIDs []idtable.EntryID
var useMulti bool
var selMaps *selectors.KernelSelectorMaps

// use multi kprobe only if:
// - it's not disabled by user
Expand All @@ -518,6 +519,9 @@ func createGenericKprobeSensor(
}

addedKprobeIndices := []int{}
if useMulti {
selMaps = &selectors.KernelSelectorMaps{}
}
for i := range kprobes {
syms, syscall, err := getKprobeSymbols(kprobes[i].Call, kprobes[i].Syscall, lists)
if err != nil {
Expand All @@ -528,7 +532,7 @@ func createGenericKprobeSensor(
kprobes[i].Syscall = syscall

for idx := range syms {
out, err := addKprobe(syms[idx], &kprobes[i], &in)
out, err := addKprobe(syms[idx], &kprobes[i], &in, selMaps)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -581,7 +585,7 @@ func createGenericKprobeSensor(
// addKprobe will, amongst other things, create a generic kprobe entry and add
// it to the genericKprobeTable. The caller should make sure that this entry is
// properly removed on kprobe unload.
func addKprobe(funcName string, f *v1alpha1.KProbeSpec, in *addKprobeIn) (out *addKprobeOut, err error) {
func addKprobe(funcName string, f *v1alpha1.KProbeSpec, in *addKprobeIn, selMaps *selectors.KernelSelectorMaps) (out *addKprobeOut, err error) {
var argSigPrinters []argPrinters
var argReturnPrinters []argPrinters
var setRetprobe bool
Expand Down Expand Up @@ -747,7 +751,7 @@ func addKprobe(funcName string, f *v1alpha1.KProbeSpec, in *addKprobeIn) (out *a
}

// Parse Filters into kernel filter logic
kprobeEntry.loadArgs.selectors, err = selectors.InitKernelSelectorState(f.Selectors, f.Args, &kprobeEntry.actionArgs, nil)
kprobeEntry.loadArgs.selectors, err = selectors.InitKernelSelectorState(f.Selectors, f.Args, &kprobeEntry.actionArgs, nil, selMaps)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/sensors/tracing/generictracepoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ func (tp *genericTracepoint) InitKernelSelectors(lists []v1alpha1.ListSpec) erro
}
}

selectors, err := selectors.InitKernelSelectorState(selSelectors, selArgs, &tp.actionArgs, &listReader{lists})
selectors, err := selectors.InitKernelSelectorState(selSelectors, selArgs, &tp.actionArgs, &listReader{lists}, nil)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/sensors/tracing/genericuprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func createGenericUprobeSensor(
}

// Parse Filters into kernel filter logic
uprobeSelectorState, err := selectors.InitKernelSelectorState(spec.Selectors, args, nil, nil)
uprobeSelectorState, err := selectors.InitKernelSelectorState(spec.Selectors, args, nil, nil, nil)
if err != nil {
return nil, err
}
Expand Down
118 changes: 116 additions & 2 deletions pkg/sensors/tracing/kprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3083,15 +3083,25 @@ func openFile(t *testing.T, file string) int {
}

// reads 32 bytes from a file, this will trigger a sys_read.
func readFile(t *testing.T, file string) int {
func readFile(t *testing.T, file string) {
fd := openFile(t, file)
var readBytes = make([]byte, 32)
i, errno := syscall.Read(fd, readBytes)
if i < 0 {
t.Logf("syscall.Read failed: %s\n", errno)
t.Fatal()
}
return fd
}

func readMmapFile(t *testing.T, file string) {
fd := openFile(t, file)
data, errno := syscall.Mmap(fd, 0, 32, syscall.PROT_READ, syscall.MAP_FILE|syscall.MAP_SHARED)
if errno != nil {
t.Logf("syscall.Mmap failed: %s\n", errno)
t.Fatal()
}
defer syscall.Munmap(data)
// no need to copy data as we capture mmap call
}

func createFdInstallChecker(fd int, filename string) *ec.ProcessKprobeChecker {
Expand Down Expand Up @@ -5510,3 +5520,107 @@ spec:
err = jsonchecker.JsonTestCheck(t, checker)
assert.NoError(t, err)
}

func TestKprobeMultiMatcArgs(t *testing.T) {
if !kernels.EnableLargeProgs() {
t.Skip("Older kernels do not support matchArgs for more than one arguments")
}

tracingPolicy := `
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "file-monitoring-filtered"
spec:
kprobes:
- call: "security_file_permission"
syscall: false
return: true
args:
- index: 0
type: "file" # (struct file *) used for getting the path
- index: 1
type: "int" # 0x04 is MAY_READ, 0x02 is MAY_WRITE
returnArg:
index: 0
type: "int"
returnArgAction: "Post"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/etc/passwd"
- index: 1
operator: "Equal"
values:
- "4" # MAY_READ
- call: "security_mmap_file"
syscall: false
return: true
args:
- index: 0
type: "file" # (struct file *) used for getting the path
- index: 1
type: "uint32" # the prot flags PROT_READ(0x01), PROT_WRITE(0x02), PROT_EXEC(0x04)
- index: 2
type: "nop" # the mmap flags (i.e. MAP_SHARED, ...)
returnArg:
index: 0
type: "int"
returnArgAction: "Post"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/etc/shadow"
- index: 1
operator: "Equal"
values:
- "1" # MAY_READ
`

var doneWG, readyWG sync.WaitGroup
defer doneWG.Wait()

ctx, cancel := context.WithTimeout(context.Background(), tus.Conf().CmdWaitTime)
defer cancel()

createCrdFile(t, tracingPolicy)

obs, err := observertesthelper.GetDefaultObserverWithFile(t, ctx, testConfigFile, tus.Conf().TetragonLib, observertesthelper.WithMyPid())
if err != nil {
t.Fatalf("GetDefaultObserverWithFile error: %s", err)
}
observertesthelper.LoopEvents(ctx, t, &doneWG, &readyWG, obs)
readyWG.Wait()

// read with system call /etc/passwd
readFile(t, "/etc/passwd")

// read with mmap /etc/shadow
readMmapFile(t, "/etc/shadow")

kpCheckersRead := ec.NewProcessKprobeChecker("").
WithFunctionName(sm.Full("security_file_permission")).
WithArgs(ec.NewKprobeArgumentListMatcher().
WithOperator(lc.Ordered).
WithValues(
ec.NewKprobeArgumentChecker().WithFileArg(ec.NewKprobeFileChecker().WithPath(sm.Full("/etc/passwd"))),
ec.NewKprobeArgumentChecker().WithIntArg(4),
))

kpCheckersMmap := ec.NewProcessKprobeChecker("").
WithFunctionName(sm.Full("security_mmap_file")).
WithArgs(ec.NewKprobeArgumentListMatcher().
WithOperator(lc.Ordered).
WithValues(
ec.NewKprobeArgumentChecker().WithFileArg(ec.NewKprobeFileChecker().WithPath(sm.Full("/etc/shadow"))),
ec.NewKprobeArgumentChecker().WithUintArg(1),
))

checker := ec.NewUnorderedEventChecker(kpCheckersRead, kpCheckersMmap)
err = jsonchecker.JsonTestCheck(t, checker)
assert.NoError(t, err)
}