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

[CWS] add kill action to windows #25513

Merged
merged 2 commits into from
May 14, 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
4 changes: 1 addition & 3 deletions pkg/security/probe/actions.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
//go:generate go run github.com/mailru/easyjson/easyjson -gen_build_flags=-mod=mod -no_std_marshalers -build_tags linux $GOFILE
//go:generate go run github.com/mailru/easyjson/easyjson -gen_build_flags=-mod=mod -no_std_marshalers $GOFILE

// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build linux

// Package probe holds probe related files
package probe

Expand Down
3 changes: 0 additions & 3 deletions pkg/security/probe/actions_easyjson.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions pkg/security/probe/probe_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
package probe

import (
"time"

"github.com/DataDog/datadog-agent/comp/core/workloadmeta"
"github.com/DataDog/datadog-agent/pkg/security/config"
"github.com/DataDog/datadog-agent/pkg/util/optional"
Expand All @@ -19,8 +17,6 @@ const (
EBPFOrigin = "ebpf"
// EBPFLessOrigin eBPF less origin
EBPFLessOrigin = "ebpfless"

defaultKillActionFlushDelay = 2 * time.Second
)

// NewProbe instantiates a new runtime security agent probe
Expand Down
33 changes: 29 additions & 4 deletions pkg/security/probe/probe_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type WindowsProbe struct {
// discarders
discardedPaths *lru.Cache[string, struct{}]
discardedBasenames *lru.Cache[string, struct{}]

// actions
processKiller *ProcessKiller
}

type renameState struct {
Expand Down Expand Up @@ -474,6 +477,9 @@ func (p *WindowsProbe) Start() error {
}

p.DispatchEvent(ev)

// flush pending kill actions
p.processKiller.FlushPendingReports()
}
}()
return p.pm.Start()
Expand Down Expand Up @@ -535,11 +541,15 @@ func (p *WindowsProbe) handleProcessStop(ev *model.Event, stop *procmon.ProcessS
log.Errorf("unable to resolve pid %d", pid)
return false
}
pce.ExitTime = time.Now()
ev.Exit.Process = &pce.Process
// use ProcessCacheEntry process context as process context
ev.ProcessCacheEntry = pce
ev.ProcessContext = &pce.ProcessContext

// update kill action reports
p.processKiller.HandleProcessExited(ev)

p.Resolvers.ProcessResolver.DequeueExited()
return true
}
Expand Down Expand Up @@ -649,7 +659,7 @@ func (p *WindowsProbe) setProcessContext(pid uint32, event *model.Event) error {
err := backoff.Retry(func() error {
pce := p.Resolvers.ProcessResolver.GetEntry(pid)
if pce == nil {
return fmt.Errorf("Could not resolve process for Process: %v", pid)
return fmt.Errorf("could not resolve process for Process: %v", pid)
}
event.ProcessCacheEntry = pce
event.ProcessContext = &pce.ProcessContext
Expand All @@ -661,7 +671,6 @@ func (p *WindowsProbe) setProcessContext(pid uint32, event *model.Event) error {

// DispatchEvent sends an event to the probe event handler
func (p *WindowsProbe) DispatchEvent(event *model.Event) {

traceEvent("Dispatching event %s", func() ([]byte, model.EventType, error) {
eventJSON, err := serializers.MarshalEvent(event, nil)
return eventJSON, event.GetEventType(), err
Expand All @@ -672,7 +681,6 @@ func (p *WindowsProbe) DispatchEvent(event *model.Event) {

// send event to specific event handlers, like the event monitor consumers, subsequently
p.probe.sendEventToSpecificEventTypeHandlers(event)

}

// Snapshot runs the different snapshot functions of the resolvers that
Expand Down Expand Up @@ -803,6 +811,8 @@ func NewWindowsProbe(probe *Probe, config *config.Config, opts Opts) (*WindowsPr

discardedPaths: discardedPaths,
discardedBasenames: discardedBasenames,

processKiller: NewProcessKiller(),
}

p.Resolvers, err = resolvers.NewResolvers(config, p.statsdClient, probe.scrubber)
Expand Down Expand Up @@ -880,7 +890,22 @@ func (p *WindowsProbe) NewEvent() *model.Event {
}

// HandleActions executes the actions of a triggered rule
func (p *WindowsProbe) HandleActions(_ *eval.Context, _ *rules.Rule) {}
func (p *WindowsProbe) HandleActions(ctx *eval.Context, rule *rules.Rule) {
ev := ctx.Event.(*model.Event)

for _, action := range rule.Definition.Actions {
if !action.IsAccepted(ctx) {
continue
}

switch {
case action.Kill != nil:
p.processKiller.KillAndReport(action.Kill.Scope, action.Kill.Signal, ev, func(pid uint32, sig uint32) error {
return p.processKiller.KillFromUserspace(pid, sig, ev)
})
}
}
}

// AddDiscarderPushedCallback add a callback to the list of func that have to be called when a discarder is pushed to kernel
func (p *WindowsProbe) AddDiscarderPushedCallback(_ DiscarderPushedCallback) {}
Expand Down
52 changes: 11 additions & 41 deletions pkg/security/probe/process_killer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,24 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build linux
//go:build linux || windows

// Package probe holds probe related files
package probe

import (
"errors"
"fmt"
"slices"
"sync"
"syscall"
"time"

psutil "github.com/shirou/gopsutil/v3/process"

"github.com/DataDog/datadog-agent/pkg/security/secl/model"
"github.com/DataDog/datadog-agent/pkg/security/seclog"
"github.com/DataDog/datadog-agent/pkg/security/utils"
"github.com/DataDog/datadog-agent/pkg/util/log"
)

const (
userSpaceKillWithinMillis = 2000
defaultKillActionFlushDelay = 2 * time.Second
)

// ProcessKiller defines a process killer structure
Expand Down Expand Up @@ -83,50 +78,25 @@ func (p *ProcessKiller) HandleProcessExited(event *model.Event) {
})
}

// KillFromUserspace tries to kill from userspace
func (p *ProcessKiller) KillFromUserspace(pid uint32, sig uint32, ev *model.Event) error {
proc, err := psutil.NewProcess(int32(pid))
if err != nil {
return errors.New("process not found in procfs")
}

name, err := proc.Name()
if err != nil {
return errors.New("process not found in procfs")
}

createdAt, err := proc.CreateTime()
if err != nil {
return errors.New("process not found in procfs")
}
evCreatedAt := ev.ProcessContext.ExecTime.UnixMilli()

within := uint64(evCreatedAt) >= uint64(createdAt-userSpaceKillWithinMillis) && uint64(evCreatedAt) <= uint64(createdAt+userSpaceKillWithinMillis)

if !within || ev.ProcessContext.Comm != name {
return fmt.Errorf("not sharing the same namespace: %s/%s", ev.ProcessContext.Comm, name)
}

return syscall.Kill(int(pid), syscall.Signal(sig))
}

// KillAndReport kill and report
func (p *ProcessKiller) KillAndReport(scope string, signal string, ev *model.Event, killFnc func(pid uint32, sig uint32) error) {
entry, exists := ev.ResolveProcessCacheEntry()
if !exists {
return
}

var pids []uint32

if entry.ContainerID != "" && scope == "container" {
pids = entry.GetContainerPIDs()
scope = "container"
} else {
pids = []uint32{ev.ProcessContext.Pid}
switch scope {
case "container", "process":
default:
scope = "process"
}

pids, err := p.getPids(scope, ev, entry)
if err != nil {
log.Errorf("unable to kill: %s", err)
return
}

sig := model.SignalConstants[signal]

killedAt := time.Now()
Expand Down
59 changes: 59 additions & 0 deletions pkg/security/probe/process_killer_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

// Package probe holds probe related files
package probe

import (
"errors"
"fmt"
"syscall"

psutil "github.com/shirou/gopsutil/v3/process"

"github.com/DataDog/datadog-agent/pkg/security/secl/model"
)

const (
userSpaceKillWithinMillis = 2000
)

// KillFromUserspace tries to kill from userspace
func (p *ProcessKiller) KillFromUserspace(pid uint32, sig uint32, ev *model.Event) error {
proc, err := psutil.NewProcess(int32(pid))
if err != nil {
return errors.New("process not found in procfs")
}

name, err := proc.Name()
if err != nil {
return errors.New("process not found in procfs")
julien-lebot marked this conversation as resolved.
Show resolved Hide resolved
}

createdAt, err := proc.CreateTime()
if err != nil {
return errors.New("process not found in procfs")
}
evCreatedAt := ev.ProcessContext.ExecTime.UnixMilli()

within := uint64(evCreatedAt) >= uint64(createdAt-userSpaceKillWithinMillis) && uint64(evCreatedAt) <= uint64(createdAt+userSpaceKillWithinMillis)

if !within || ev.ProcessContext.Comm != name {
return fmt.Errorf("not sharing the same namespace: %s/%s", ev.ProcessContext.Comm, name)
}

return syscall.Kill(int(pid), syscall.Signal(sig))
}

func (p *ProcessKiller) getPids(scope string, ev *model.Event, entry *model.ProcessCacheEntry) ([]uint32, error) {
var pids []uint32

if entry.ContainerID != "" && scope == "container" {
pids = entry.GetContainerPIDs()
} else {
pids = []uint32{ev.ProcessContext.Pid}
}
return pids, nil
}
29 changes: 29 additions & 0 deletions pkg/security/probe/process_killer_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

// Package probe holds probe related files
package probe

import (
"errors"

"github.com/DataDog/datadog-agent/pkg/security/secl/model"
"github.com/DataDog/datadog-agent/pkg/util/winutil"
)

// KillFromUserspace tries to kill from userspace
func (p *ProcessKiller) KillFromUserspace(pid uint32, sig uint32, _ *model.Event) error {
if sig != model.SIGKILL {
return nil
}
return winutil.KillProcess(int(pid), 0)
}

func (p *ProcessKiller) getPids(scope string, ev *model.Event, _ *model.ProcessCacheEntry) ([]uint32, error) {
if scope == "container" {
return nil, errors.New("container scope not supported")
}
return []uint32{ev.ProcessContext.Pid}, nil
}
2 changes: 1 addition & 1 deletion pkg/security/secl/model/consts_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build !linux
//go:build !linux && !windows

package model

Expand Down
33 changes: 33 additions & 0 deletions pkg/security/secl/model/consts_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

// Package model holds model related files
package model

const (
// SIGKILL id for the kill action
SIGKILL = iota + 1
)

var (
errorConstants = map[string]int{}
openFlagsConstants = map[string]int{}
fileModeConstants = map[string]int{}
inodeModeConstants = map[string]int{}
// KernelCapabilityConstants list of kernel capabilities
KernelCapabilityConstants = map[string]uint64{}
unlinkFlagsConstants = map[string]int{}
ptraceConstants = map[string]uint32{}
ptraceArchConstants = map[string]uint32{}
protConstants = map[string]uint64{}
mmapFlagConstants = map[string]uint64{}
mmapFlagArchConstants = map[string]uint64{}
addressFamilyConstants = map[string]uint16{}

// SignalConstants list of signals
SignalConstants = map[string]int{
"SIGKILL": SIGKILL,
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build linux

// Package utils holds utils related files
package utils

Expand Down
18 changes: 18 additions & 0 deletions pkg/security/utils/proc_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build !linux

// Package utils holds utils related files
package utils

import (
"os"
)

// Getpid returns the current process ID in the host namespace
func Getpid() uint32 {
return uint32(os.Getpid())
}
Loading
Loading