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

tetragon: Add map ownership #2945

Merged
merged 7 commits into from
Sep 30, 2024
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: 1 addition & 1 deletion bpf/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ PROCESS = bpf_execve_event.o bpf_execve_event_v53.o bpf_fork.o bpf_exit.o bpf_ge
bpf_loader.o \
bpf_cgroup.o \
bpf_enforcer.o bpf_multi_enforcer.o bpf_fmodret_enforcer.o \
bpf_map_test_p1.o bpf_map_test_p2.o
bpf_map_test_p1.o bpf_map_test_p2.o bpf_map_test_p3.o

CGROUP = bpf_cgroup_mkdir.o bpf_cgroup_rmdir.o bpf_cgroup_release.o
BPFTEST = bpf_lseek.o
Expand Down
22 changes: 22 additions & 0 deletions bpf/process/bpf_map_test_p3.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright Authors of Cilium */

#include "vmlinux.h"
#include "api.h"
#include "bpf_tracing.h"
#include "bpf_helpers.h"
#include "compiler.h"

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, int);
__type(value, unsigned long);
} m1 SEC(".maps");

__attribute__((section("kprobe/wake_up_new_task"), used)) int
BPF_KPROBE(p2)
{
map_lookup_elem(&m1, &(unsigned long){ 0 });
return 0;
}
18 changes: 14 additions & 4 deletions pkg/observer/observertesthelper/observer_test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,22 @@ func GetDefaultObserverWithFile(tb testing.TB, ctx context.Context, file, lib st
return GetDefaultObserverWithWatchers(tb, ctx, b, opts...)
}

func GetDefaultSensorsWithBase(tb testing.TB, b *sensors.Sensor, file, lib string, opts ...TestOption) ([]*sensors.Sensor, error) {
opts = append(opts, WithConfig(file))
opts = append(opts, WithLib(lib))

return getDefaultSensors(tb, b, opts...)
}

func GetDefaultSensorsWithFile(tb testing.TB, file, lib string, opts ...TestOption) ([]*sensors.Sensor, error) {
opts = append(opts, WithConfig(file))
opts = append(opts, WithLib(lib))

b := base.GetInitialSensor()
return getDefaultSensors(tb, b, opts...)
}

func getDefaultSensors(tb testing.TB, initialSensor *sensors.Sensor, opts ...TestOption) ([]*sensors.Sensor, error) {
option.Config.BpfDir = bpf.MapPrefixPath()

testutils.CaptureLog(tb, logger.GetLogger().(*logrus.Logger))
Expand Down Expand Up @@ -327,13 +339,11 @@ func GetDefaultSensorsWithFile(tb testing.TB, file, lib string, opts ...TestOpti
}
}

base := base.GetInitialSensor()

if err = loadSensors(tb, base, sens); err != nil {
if err = loadSensors(tb, initialSensor, sens); err != nil {
return nil, err
}

sens = append(sens, base)
sens = append(sens, initialSensor)
ret := make([]*sensors.Sensor, 0, len(sens))
for _, si := range sens {
if s, ok := si.(*sensors.Sensor); ok {
Expand Down
2 changes: 1 addition & 1 deletion pkg/sensors/config/confmap/confmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func UpdateConfMap(mapDir string, v *TetragonConfValue) error {
return err
}

m, err := program.LoadOrCreatePinnedMap(mapPath, mapSpec)
m, err := program.LoadOrCreatePinnedMap(mapPath, mapSpec, configMap.IsOwner())
if err != nil {
return err
}
Expand Down
42 changes: 31 additions & 11 deletions pkg/sensors/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,6 @@ func (s *Sensor) setMapPinPath(m *program.Map) {
func (s *Sensor) loadMaps(bpfDir string) error {
l := logger.GetLogger()
for _, m := range s.Maps {
// Even if the map already exists we need to setup proper PinPath
// so the loader can have access to the map location.
s.setMapPinPath(m)

if m.PinState.IsLoaded() {
l.WithFields(logrus.Fields{
"sensor": s.Name,
Expand All @@ -262,17 +258,41 @@ func (s *Sensor) loadMaps(bpfDir string) error {
return fmt.Errorf("map '%s' not found from '%s'", m.Name, m.Prog.Name)
}

if max, ok := m.GetMaxEntries(); ok {
s.setMapPinPath(m)
pinPath := filepath.Join(bpfDir, m.PinPath)

if m.IsOwner() {
// If map is the owner we set configured max entries
// directly to map spec.
if max, ok := m.GetMaxEntries(); ok {
mapSpec.MaxEntries = max
}

if innerMax, ok := m.GetMaxInnerEntries(); ok {
if innerMs := mapSpec.InnerMap; innerMs != nil {
mapSpec.InnerMap.MaxEntries = innerMax
}
}
} else {
// If map is NOT the owner we follow the max entries
// of the pinned map and update the spec with that.
max, err := program.GetMaxEntriesPinnedMap(pinPath)
if err != nil {
return err
}
mapSpec.MaxEntries = max
}

if innerMax, ok := m.GetMaxInnerEntries(); ok {
if innerMs := mapSpec.InnerMap; innerMs != nil {
mapSpec.InnerMap.MaxEntries = innerMax
// 'm' is not the owner but for some reason requires max
// entries setup, make sure it matches the pinned map.
if max, ok := m.GetMaxEntries(); ok {
if mapSpec.MaxEntries != max {
return fmt.Errorf("failed to load map '%s' max entries mismatch: %d %d",
m.Name, mapSpec.MaxEntries, max)
}
}
}

pinPath := filepath.Join(bpfDir, m.PinPath)
m.SetMaxEntries(int(max))
}

if err := m.LoadOrCreatePinnedMap(pinPath, mapSpec); err != nil {
return fmt.Errorf("failed to load map '%s' for sensor '%s': %w", m.Name, s.Name, err)
Expand Down
94 changes: 79 additions & 15 deletions pkg/sensors/program/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@
// - map is local for program, not shared at all
//
// NOTE Please do not share MapTypeProgram maps, it brings confusion.
//
// Each map declares the ownership of the map. The map can be either
// owner of the map (via MapBuilder* helpers) or as an user (MapUser*
// helpers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the comments!

//
// Map owner object owns the pinned map and when loading it sets (and
// potentially overwrite) the map's spec and its max entries value.
//
// Map user object object is just using the pinned map and follows its
// setup and will fail if the pinned map differs in spec or configured
// max entries value.

package program

Expand Down Expand Up @@ -75,6 +86,11 @@ const (
MapTypeProgram
)

type MapOpts struct {
Type MapType
Owner bool
}

// Map represents BPF maps.
type Map struct {
Name string
Expand All @@ -85,6 +101,7 @@ type Map struct {
Entries MaxEntries
InnerEntries MaxEntries
Type MapType
Owner bool
}

// Map holds pointer to Program object as a source of its ebpf object
Expand All @@ -100,40 +117,64 @@ type Map struct {
// p.PinMap["map2"] = &map2
// ...
// p.PinMap["mapX"] = &mapX
func mapBuilder(name string, ty MapType, lds ...*Program) *Map {
m := &Map{name, "", lds[0], Idle(), nil, MaxEntries{0, false}, MaxEntries{0, false}, ty}
func mapBuilder(name string, ty MapType, owner bool, lds ...*Program) *Map {
m := &Map{name, "", lds[0], Idle(), nil, MaxEntries{0, false}, MaxEntries{0, false}, ty, owner}
for _, ld := range lds {
ld.PinMap[name] = m
}
return m
}

func MapBuilder(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypeGlobal, lds...)
return mapBuilder(name, MapTypeGlobal, true, lds...)
}

func MapBuilderProgram(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypeProgram, lds...)
return mapBuilder(name, MapTypeProgram, true, lds...)
}

func MapBuilderSensor(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypeSensor, lds...)
return mapBuilder(name, MapTypeSensor, true, lds...)
}

func MapBuilderPolicy(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypePolicy, lds...)
return mapBuilder(name, MapTypePolicy, true, lds...)
}

func MapBuilderType(name string, ty MapType, lds ...*Program) *Map {
return mapBuilder(name, ty, lds...)
return mapBuilder(name, ty, true, lds...)
}

func MapBuilderOpts(name string, opts MapOpts, lds ...*Program) *Map {
return mapBuilder(name, opts.Type, opts.Owner, lds...)
}

func MapUser(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypeGlobal, false, lds...)
}

func MapUserProgram(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypeProgram, false, lds...)
}

func MapUserSensor(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypeSensor, false, lds...)
}

func MapUserPolicy(name string, lds ...*Program) *Map {
return mapBuilder(name, MapTypePolicy, false, lds...)
}

func PolicyMapPath(mapDir, policy, name string) string {
return filepath.Join(mapDir, policy, name)
}

func (m *Map) IsOwner() bool {
return m.Owner
}

func (m *Map) Unload() error {
log := logger.GetLogger().WithField("map", m.Name).WithField("pin", m.Name)
log := logger.GetLogger().WithField("map", m.Name).WithField("pin", m.PinPath)
if !m.PinState.IsLoaded() {
log.WithField("count", m.PinState.count).Debug("Refusing to unload map as it is not loaded")
return nil
Expand Down Expand Up @@ -216,7 +257,7 @@ func (m *Map) LoadOrCreatePinnedMap(pinPath string, mapSpec *ebpf.MapSpec) error
m.MapHandle.Close()
}

mh, err := LoadOrCreatePinnedMap(pinPath, mapSpec)
mh, err := LoadOrCreatePinnedMap(pinPath, mapSpec, m.IsOwner())
if err != nil {
return err
}
Expand All @@ -231,11 +272,12 @@ func isValidSubdir(d string) bool {
return dir != "." && dir != ".."
}

func LoadOrCreatePinnedMap(pinPath string, mapSpec *ebpf.MapSpec) (*ebpf.Map, error) {
func LoadOrCreatePinnedMap(pinPath string, mapSpec *ebpf.MapSpec, create bool) (*ebpf.Map, error) {
var err error
// Try to open the pinPath and if it exist use the previously
// pinned map otherwise pin the map and next user will find
// it here.
if _, err := os.Stat(pinPath); err == nil {
if _, err = os.Stat(pinPath); err == nil {
m, err := ebpf.LoadPinnedMap(pinPath, nil)
if err != nil {
return nil, fmt.Errorf("loading pinned map from path '%s' failed: %w", pinPath, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to re-create here if we have create true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, so this leg is when there is already an existing pin and we check if the pin is compatible with what we want to create, if it is, we are done, if not, we remove the pin and create new one

Expand All @@ -244,14 +286,27 @@ func LoadOrCreatePinnedMap(pinPath string, mapSpec *ebpf.MapSpec) (*ebpf.Map, er
logger.GetLogger().WithError(err).WithFields(logrus.Fields{
"path": pinPath,
"map-name": mapSpec.Name,
}).Warn("tetragon, incompatible map found: will delete and recreate")
}).Warn("incompatible map found")
m.Close()
os.Remove(pinPath)
} else {
return m, nil
// If we are creating the map, let's ignore the compatibility error,
// remove the pin and create the map with our spec.
if create {
logger.GetLogger().WithField("map", mapSpec.Name).
Warn("will delete and recreate")
os.Remove(pinPath)
return createPinnedMap(pinPath, mapSpec)
}
return nil, fmt.Errorf("incompatible map '%s'", pinPath)
}
return m, nil
}
if create {
return createPinnedMap(pinPath, mapSpec)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could move this create just after the state(pinPath) failure , and have the rest outside of if , just for readability up to you

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, so it is already in the place after the stat failure.. not sure what you mean

}
return nil, err
}

func createPinnedMap(pinPath string, mapSpec *ebpf.MapSpec) (*ebpf.Map, error) {
// check if PinName has directory portion and create it,
// filepath.Dir returns '.' for filename without dir portion
if dir := filepath.Dir(pinPath); isValidSubdir(dir) {
Expand All @@ -274,6 +329,15 @@ func LoadOrCreatePinnedMap(pinPath string, mapSpec *ebpf.MapSpec) (*ebpf.Map, er
return m, nil
}

func GetMaxEntriesPinnedMap(pinPath string) (uint32, error) {
m, err := ebpf.LoadPinnedMap(pinPath, nil)
if err != nil {
return 0, fmt.Errorf("loading pinned map from path '%s' failed: %w", pinPath, err)
}
defer m.Close()
return m.MaxEntries(), nil
}

func (m *Map) SetMaxEntries(max int) {
m.Entries = MaxEntries{uint32(max), true}
}
Expand Down
Loading
Loading