Skip to content
Open
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
16 changes: 16 additions & 0 deletions pkg/containerprofilemanager/v1/event_reporting.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import (

var procRegex = regexp.MustCompile(`^/proc/\d+`)

// maxIdentifiedCallStacksPerContainer bounds the number of distinct identified
// call stacks retained per container between reporting flushes. Each distinct
// call site produces a distinct (deduped-by-hash) call stack; without a cap a
// workload that launches many distinct executables grows this map unbounded and
// drives the node-agent OOM.
const maxIdentifiedCallStacksPerContainer = 512

// ReportCapability reports a capability event for a container
func (cpm *ContainerProfileManager) ReportCapability(containerID, capability string) {
err := cpm.withContainer(containerID, func(data *containerData) (int, error) {
Expand Down Expand Up @@ -233,6 +240,15 @@ func (cpm *ContainerProfileManager) ReportIdentifiedCallStack(containerID string
return 0, nil
}

// Bound memory: a container that launches many distinct executables (or a
// process-enumeration/fork-churn attack) produces a distinct call stack per
// call site, which would otherwise grow this map unbounded within a
// reporting window and drive the node-agent OOM. The profile only needs a
// representative set, so stop retaining new call stacks past the cap.
if data.callStacks.Len() >= maxIdentifiedCallStacksPerContainer {
return 0, nil
}

// Add to call stacks map
data.callStacks.Set(callStackIdentifier, callStack)
return size.Of(callStack), nil
Expand Down
12 changes: 12 additions & 0 deletions pkg/objectcache/callstackcache/callstackcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import (
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
)

// maxCallStacksInTree bounds the number of distinct call sites (CallIDs) indexed
// in a container's search tree. A container that launches many distinct
// executables produces many distinct call sites; without a cap the tree, trie and
// per-CallID paths grow unbounded and inflate node-agent memory.
const maxCallStacksInTree = 512

// BidirectionalNode extends the call stack node with parent reference
type BidirectionalNode struct {
Frame v1beta1.StackFrame
Expand Down Expand Up @@ -39,6 +45,12 @@ func (t *CallStackSearchTree) AddCallStack(stack v1beta1.IdentifiedCallStack) {
return
}

// Bound memory: stop indexing new call sites past the cap (see
// maxCallStacksInTree). The tree already retains a representative set.
if len(t.Roots) >= maxCallStacksInTree {
return
}

// Get all paths from the call stack
paths := getCallStackPaths(stack.CallStack)
if len(paths) == 0 {
Expand Down
Loading