x-pack/auditbeat/processors/sessionmd/processdb/db.go (645 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
//go:build linux
package processdb
import (
"context"
"encoding/base64"
"errors"
"fmt"
"os"
"path"
"slices"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/elastic/beats/v7/auditbeat/helper/tty"
"github.com/elastic/beats/v7/libbeat/common/capabilities"
"github.com/elastic/beats/v7/x-pack/auditbeat/processors/sessionmd/procfs"
"github.com/elastic/beats/v7/x-pack/auditbeat/processors/sessionmd/timeutils"
"github.com/elastic/beats/v7/x-pack/auditbeat/processors/sessionmd/types"
"github.com/elastic/elastic-agent-libs/logp"
"github.com/elastic/elastic-agent-libs/monitoring"
)
type EntryType string
const (
Init EntryType = "init"
Sshd EntryType = "sshd"
Ssm EntryType = "ssm"
Container EntryType = "container"
Terminal EntryType = "terminal"
EntryConsole EntryType = "console"
EntryUnknown EntryType = "unknown"
)
var containerRuntimes = [...]string{
"containerd-shim",
"runc",
"conmon",
}
// "filtered" executables are executables that relate to internal
// implementation details of entry mechanisms. The set of circumstances under
// which they can become an entry leader are reduced compared to other binaries
// (see implementation and unit tests).
var filteredExecutables = [...]string{
"runc",
"containerd-shim",
"calico-node",
"check-status",
"conmon",
}
const (
retryCount = 2
)
type Process struct {
PIDs types.PIDInfo
Creds types.CredInfo
CTTY tty.TTYDev
Argv []string
Cwd string
Filename string
ExitCode int32
// procfsLookupFail is true if procfs couldn't find a matching PID in /proc.
procfsLookupFail bool
insertTime time.Time
}
var (
bootID string
pidNsInode uint64
initError error
once sync.Once
)
func readBootID() (string, error) {
bootID, err := os.ReadFile("/proc/sys/kernel/random/boot_id")
if err != nil {
panic(fmt.Sprintf("could not read /proc/sys/kernel/random/boot_id: %v", err))
}
return strings.TrimRight(string(bootID), "\n"), nil
}
func readPIDNsInode() (uint64, error) {
var ret uint64
pidNsInodeRaw, err := os.Readlink("/proc/self/ns/pid")
if err != nil {
panic(fmt.Sprintf("could not read /proc/self/ns/pid: %v", err))
}
if _, err = fmt.Sscanf(pidNsInodeRaw, "pid:[%d]", &ret); err != nil {
panic(fmt.Sprintf("could not parse contents of /proc/self/ns/pid (%s): %v", pidNsInodeRaw, err))
}
return ret, nil
}
func pidInfoFromProto(p types.PIDInfo) types.PIDInfo {
return types.PIDInfo{
StartTimeNS: p.StartTimeNS,
Tid: p.Tid,
Tgid: p.Tgid,
Vpid: p.Vpid,
Ppid: p.Ppid,
Pgid: p.Pgid,
Sid: p.Sid,
}
}
func credInfoFromProto(p types.CredInfo) types.CredInfo {
return types.CredInfo{
Ruid: p.Ruid,
Rgid: p.Rgid,
Euid: p.Euid,
Egid: p.Egid,
Suid: p.Suid,
Sgid: p.Sgid,
CapPermitted: p.CapPermitted,
CapEffective: p.CapEffective,
}
}
func ttyTermiosFromProto(p tty.TTYTermios) tty.TTYTermios {
return tty.TTYTermios{
CIflag: p.CIflag,
COflag: p.COflag,
CLflag: p.CLflag,
CCflag: p.CCflag,
}
}
func ttyWinsizeFromProto(p tty.TTYWinsize) tty.TTYWinsize {
return tty.TTYWinsize{
Rows: p.Rows,
Cols: p.Cols,
}
}
func ttyDevFromProto(p tty.TTYDev) tty.TTYDev {
return tty.TTYDev{
Major: p.Major,
Minor: p.Minor,
Winsize: ttyWinsizeFromProto(p.Winsize),
Termios: ttyTermiosFromProto(p.Termios),
}
}
func initialize() {
var err error
bootID, err = readBootID()
if err != nil {
initError = err
return
}
pidNsInode, err = readPIDNsInode()
if err != nil {
initError = err
}
}
type DB struct {
mutex sync.RWMutex
logger *logp.Logger
processes map[uint32]Process
entryLeaders map[uint32]EntryType
entryLeaderRelationships map[uint32]uint32
procfs procfs.Reader
stopChan chan struct{}
// map of processes that we can remove during the next reaper run, if the exit event is older than `removalCandidateTimeout`
removalMap map[uint32]removalCandidate
ctx context.Context
// used for metrics reporting
stats *Stats
// knobs for the reaper thread follows
// how often the reaper checks for expired or orphaned events.
// A negative value disables the reaper.
reaperPeriod time.Duration
// Tells the reaper to remove orphaned process exec events.
// If true, exec events for which no /proc entry can be found will be removed after their insertion time has passed `orphanTimeout`.
// If disabled, the reaper will only remove exec events if they are matched with a exit event.
reapProcesses bool
// The duration after which we'll reap an orphaned process exec event for which no /proc data exists. Measured from the time the event is inserted.
processReapAfter time.Duration
}
// NewDB creates a new DB for tracking processes.
//
// - metrics: monitoring registry for exporting DB metrics
// - reader: handler for /proc data and events.
// - reaperPeriod: tells the reaper to update its tracking of deat and orphaned processes every at every `n` period.
// - reapProcesses: optionally tell the reaper to also reap orphan processes from the DB, if no matching exit event can be found.
// May result in data loss if the DB in under load and events do not arrive in a timely fashion.
func NewDB(ctx context.Context, metrics *monitoring.Registry, reader procfs.Reader, logger *logp.Logger, reaperPeriod time.Duration, reapProcesses bool) (*DB, error) {
once.Do(initialize)
if initError != nil {
return &DB{}, initError
}
db := DB{
logger: logger.Named("processdb"),
processes: make(map[uint32]Process),
entryLeaders: make(map[uint32]EntryType),
entryLeaderRelationships: make(map[uint32]uint32),
procfs: reader,
stopChan: make(chan struct{}),
removalMap: make(map[uint32]removalCandidate),
reaperPeriod: reaperPeriod,
stats: NewStats(metrics),
reapProcesses: reapProcesses,
processReapAfter: time.Minute * 10,
ctx: ctx,
}
if db.reaperPeriod > 0 {
logger.Infof("starting processDB reaper with interval %s", db.reaperPeriod)
}
if db.reapProcesses {
logger.Info("WARNING: reaping orphaned processes. May result in data loss.")
}
db.startReaper()
return &db, nil
}
func (db *DB) calculateEntityIDv1(pid uint32, startTime time.Time) string {
return base64.StdEncoding.EncodeToString(
[]byte(
fmt.Sprintf("%d__%s__%d__%d",
pidNsInode,
bootID,
uint64(pid),
uint64(startTime.Unix()),
),
),
)
}
// `path.Base` returns a '.' for empty strings, this just special cases that
// situation to return an empty string
func basename(pathStr string) string {
if pathStr == "" {
return ""
}
return path.Base(pathStr)
}
func (db *DB) InsertFork(fork types.ProcessForkEvent) {
db.mutex.Lock()
defer db.mutex.Unlock()
pid := fork.ChildPIDs.Tgid
ppid := fork.ParentPIDs.Tgid
if entry, ok := db.processes[ppid]; ok {
entry.PIDs = pidInfoFromProto(fork.ChildPIDs)
entry.Creds = credInfoFromProto(fork.Creds)
db.processes[pid] = entry
if entryPID, ok := db.entryLeaderRelationships[ppid]; ok {
db.entryLeaderRelationships[pid] = entryPID
}
} else {
db.processes[pid] = Process{
PIDs: pidInfoFromProto(fork.ChildPIDs),
Creds: credInfoFromProto(fork.Creds),
}
}
}
func (db *DB) InsertProcess(process Process) {
db.mutex.Lock()
defer db.mutex.Unlock()
db.insertProcess(process)
}
func (db *DB) insertProcess(process Process) {
pid := process.PIDs.Tgid
db.processes[pid] = process
entryLeaderPID := db.evaluateEntryLeader(process)
if entryLeaderPID != nil {
db.entryLeaderRelationships[pid] = *entryLeaderPID
db.logger.Debugf("%v name: %s, entry_leader: %d, entry_type: %s", process.PIDs, process.Filename, *entryLeaderPID, string(db.entryLeaders[*entryLeaderPID]))
} else {
db.logger.Debugf("%v name: %s, NO ENTRY LEADER", process.PIDs, process.Filename)
}
}
// InsertExec adds an exec event
func (db *DB) InsertExec(exec types.ProcessExecEvent) {
db.mutex.Lock()
defer db.mutex.Unlock()
proc := Process{
PIDs: pidInfoFromProto(exec.PIDs),
Creds: credInfoFromProto(exec.Creds),
CTTY: ttyDevFromProto(exec.CTTY),
Argv: exec.Argv,
Cwd: exec.CWD,
Filename: exec.Filename,
procfsLookupFail: exec.ProcfsLookupFail,
insertTime: time.Now(),
}
if proc.procfsLookupFail {
db.stats.procfsLookupFail.Add(1)
}
// check to see if an orphaned exit event maps to this exec event.
// the out-of-order problem where we get the exit before the exec usually happens under load.
// if we don't track orphaned processes like this, we'll never scrub them from the DB.
if evt, ok := db.removalMap[proc.PIDs.Tgid]; ok {
proc.ExitCode = evt.exitCode
db.stats.resolvedOrphanExits.Add(1)
db.logger.Debugf("resolved orphan exit for pid %d", proc.PIDs.Tgid)
evt.startTime = proc.PIDs.StartTimeNS
db.removalMap[proc.PIDs.Tgid] = evt
}
db.processes[exec.PIDs.Tgid] = proc
entryLeaderPID := db.evaluateEntryLeader(proc)
if entryLeaderPID != nil {
db.entryLeaderRelationships[exec.PIDs.Tgid] = *entryLeaderPID
}
}
func (db *DB) createEntryLeader(pid uint32, entryType EntryType) {
db.entryLeaders[pid] = entryType
db.logger.Debugf("created entry leader %d: %s, name: %s", pid, string(entryType), db.processes[pid].Filename)
}
// pid returned is a pointer type because it is possible no matching PID is found.
func (db *DB) evaluateEntryLeader(p Process) *uint32 {
pid := p.PIDs.Tgid
// init never has an entry leader or meta type
if p.PIDs.Tgid == 1 {
db.logger.Debugf("entry_eval %d: process is init, no entry type", p.PIDs.Tgid)
return nil
}
// kernel threads also never have an entry leader or meta type kthreadd
// (always pid 2) is the parent of all kernel threads, by filtering pid ==
// 2 || ppid == 2, we get rid of all of them
if p.PIDs.Tgid == 2 || p.PIDs.Ppid == 2 {
db.logger.Debugf("entry_eval %d: kernel threads never an entry type (parent is pid 2)", p.PIDs.Tgid)
return nil
}
// could be an entry leader
if p.PIDs.Tgid == p.PIDs.Sid {
ttyType := tty.GetTTYType(p.CTTY.Major, p.CTTY.Minor)
procBasename := basename(p.Filename)
switch {
case ttyType == tty.TTY:
db.createEntryLeader(pid, Terminal)
db.logger.Debugf("entry_eval %d: entry type is terminal", p.PIDs.Tgid)
return &pid
case ttyType == tty.TTYConsole && procBasename == "login":
db.createEntryLeader(pid, EntryConsole)
db.logger.Debugf("entry_eval %d: entry type is console", p.PIDs.Tgid)
return &pid
case p.PIDs.Ppid == 1:
db.createEntryLeader(pid, Init)
db.logger.Debugf("entry_eval %d: entry type is init", p.PIDs.Tgid)
return &pid
case !isFilteredExecutable(procBasename):
if parent, ok := db.processes[p.PIDs.Ppid]; ok {
parentBasename := basename(parent.Filename)
if ttyType == tty.Pts && parentBasename == "ssm-session-worker" {
db.createEntryLeader(pid, Ssm)
db.logger.Debugf("entry_eval %d: entry type is ssm", p.PIDs.Tgid)
return &pid
} else if parentBasename == "sshd" && procBasename != "sshd" {
// TODO: get ip from env vars
db.createEntryLeader(pid, Sshd)
db.logger.Debugf("entry_eval %d: entry type is sshd", p.PIDs.Tgid)
return &pid
} else if isContainerRuntime(parentBasename) {
db.createEntryLeader(pid, Container)
db.logger.Debugf("entry_eval %d: entry type is container", p.PIDs.Tgid)
return &pid
}
}
default:
db.logger.Debugf("entry_eval %d: is a filtered executable: %s", p.PIDs.Tgid, procBasename)
}
}
// if not a session leader or was not determined to be an entry leader, get
// it via parent, session leader, group leader (in that order)
relations := []struct {
pid uint32
name string
}{
{
pid: p.PIDs.Ppid,
name: "parent",
},
{
pid: p.PIDs.Sid,
name: "session_leader",
},
{
pid: p.PIDs.Pgid,
name: "group_leader",
},
}
for _, relation := range relations {
if entry, ok := db.entryLeaderRelationships[relation.pid]; ok {
entryType := db.entryLeaders[entry]
db.logger.Debugf("entry_eval %d: got entry_leader: %d (%s), from relative: %d (%s)", p.PIDs.Tgid, entry, string(entryType), relation.pid, relation.name)
return &entry
} else {
db.logger.Debugf("entry_eval %d: failed to find relative: %d (%s)", p.PIDs.Tgid, relation.pid, relation.name)
}
}
// if it's a session leader, then make it its own entry leader with unknown
// entry type
if p.PIDs.Tgid == p.PIDs.Sid {
db.createEntryLeader(pid, EntryUnknown)
db.logger.Debugf("entry_eval %d: this is a session leader and no relative has an entry leader. entry type is unknown", p.PIDs.Tgid)
return &pid
}
db.logger.Debugf("entry_eval %d: this is not a session leader and no relative has an entry leader, entry_leader will be unset", p.PIDs.Tgid)
return nil
}
// InsertSetsid adds a set SID event
func (db *DB) InsertSetsid(setsid types.ProcessSetsidEvent) {
db.mutex.Lock()
defer db.mutex.Unlock()
if entry, ok := db.processes[setsid.PIDs.Tgid]; ok {
entry.PIDs = pidInfoFromProto(setsid.PIDs)
db.processes[setsid.PIDs.Tgid] = entry
} else {
db.processes[setsid.PIDs.Tgid] = Process{
PIDs: pidInfoFromProto(setsid.PIDs),
}
}
}
// InsertExit adds a process exit event
func (db *DB) InsertExit(exit types.ProcessExitEvent) {
db.mutex.Lock()
defer db.mutex.Unlock()
pid := exit.PIDs.Tgid
newRemoval := removalCandidate{
pid: pid,
exitTime: time.Now(),
exitCode: exit.ExitCode,
}
process, ok := db.processes[pid]
if !ok {
newRemoval.orphanTime = time.Now()
db.logger.Debugf("pid %v for exit event not found in db, adding as orphan", pid)
} else {
// If we already have the process, add our exit info
process.ExitCode = exit.ExitCode
db.processes[pid] = process
newRemoval.startTime = process.PIDs.StartTimeNS
}
db.removalMap[pid] = newRemoval
}
func fullProcessFromDBProcess(p Process) types.Process {
reducedPrecisionStartTime := timeutils.ReduceTimestampPrecision(p.PIDs.StartTimeNS)
interactive := tty.InteractiveFromTTY(p.CTTY)
ret := types.Process{
PID: p.PIDs.Tgid,
Start: timeutils.TimeFromNsSinceBoot(reducedPrecisionStartTime),
Name: basename(p.Filename),
Executable: p.Filename,
Args: p.Argv,
WorkingDirectory: p.Cwd,
Interactive: &interactive,
}
euid := p.Creds.Euid
egid := p.Creds.Egid
ret.User.ID = strconv.FormatUint(uint64(euid), 10)
username, ok := getUserName(ret.User.ID)
if ok {
ret.User.Name = username
}
ret.Group.ID = strconv.FormatUint(uint64(egid), 10)
groupname, ok := getGroupName(ret.Group.ID)
if ok {
ret.Group.Name = groupname
}
ret.Thread.Capabilities.Permitted, _ = capabilities.FromUint64(p.Creds.CapPermitted)
ret.Thread.Capabilities.Effective, _ = capabilities.FromUint64(p.Creds.CapEffective)
ret.TTY.CharDevice.Major = uint16(p.CTTY.Major)
ret.TTY.CharDevice.Minor = uint16(p.CTTY.Minor)
ret.ExitCode = p.ExitCode
return ret
}
func fillParent(process *types.Process, parent Process) {
reducedPrecisionStartTime := timeutils.ReduceTimestampPrecision(parent.PIDs.StartTimeNS)
interactive := tty.InteractiveFromTTY(parent.CTTY)
euid := parent.Creds.Euid
egid := parent.Creds.Egid
process.Parent.PID = parent.PIDs.Tgid
process.Parent.Start = timeutils.TimeFromNsSinceBoot(reducedPrecisionStartTime)
process.Parent.Name = basename(parent.Filename)
process.Parent.Executable = parent.Filename
process.Parent.Args = parent.Argv
process.Parent.WorkingDirectory = parent.Cwd
process.Parent.Interactive = &interactive
process.Parent.User.ID = strconv.FormatUint(uint64(euid), 10)
username, ok := getUserName(process.Parent.User.ID)
if ok {
process.Parent.User.Name = username
}
process.Parent.Group.ID = strconv.FormatUint(uint64(egid), 10)
groupname, ok := getGroupName(process.Parent.Group.ID)
if ok {
process.Parent.Group.Name = groupname
}
}
func fillGroupLeader(process *types.Process, groupLeader Process) {
reducedPrecisionStartTime := timeutils.ReduceTimestampPrecision(groupLeader.PIDs.StartTimeNS)
interactive := tty.InteractiveFromTTY(groupLeader.CTTY)
euid := groupLeader.Creds.Euid
egid := groupLeader.Creds.Egid
process.GroupLeader.PID = groupLeader.PIDs.Tgid
process.GroupLeader.Start = timeutils.TimeFromNsSinceBoot(reducedPrecisionStartTime)
process.GroupLeader.Name = basename(groupLeader.Filename)
process.GroupLeader.Executable = groupLeader.Filename
process.GroupLeader.Args = groupLeader.Argv
process.GroupLeader.WorkingDirectory = groupLeader.Cwd
process.GroupLeader.Interactive = &interactive
process.GroupLeader.User.ID = strconv.FormatUint(uint64(euid), 10)
username, ok := getUserName(process.GroupLeader.User.ID)
if ok {
process.GroupLeader.User.Name = username
}
process.GroupLeader.Group.ID = strconv.FormatUint(uint64(egid), 10)
groupname, ok := getGroupName(process.GroupLeader.Group.ID)
if ok {
process.GroupLeader.Group.Name = groupname
}
}
func fillSessionLeader(process *types.Process, sessionLeader Process) {
reducedPrecisionStartTime := timeutils.ReduceTimestampPrecision(sessionLeader.PIDs.StartTimeNS)
interactive := tty.InteractiveFromTTY(sessionLeader.CTTY)
euid := sessionLeader.Creds.Euid
egid := sessionLeader.Creds.Egid
process.SessionLeader.PID = sessionLeader.PIDs.Tgid
process.SessionLeader.Start = timeutils.TimeFromNsSinceBoot(reducedPrecisionStartTime)
process.SessionLeader.Name = basename(sessionLeader.Filename)
process.SessionLeader.Executable = sessionLeader.Filename
process.SessionLeader.Args = sessionLeader.Argv
process.SessionLeader.WorkingDirectory = sessionLeader.Cwd
process.SessionLeader.Interactive = &interactive
process.SessionLeader.User.ID = strconv.FormatUint(uint64(euid), 10)
username, ok := getUserName(process.SessionLeader.User.ID)
if ok {
process.SessionLeader.User.Name = username
}
process.SessionLeader.Group.ID = strconv.FormatUint(uint64(egid), 10)
groupname, ok := getGroupName(process.SessionLeader.Group.ID)
if ok {
process.SessionLeader.Group.Name = groupname
}
}
func fillEntryLeader(process *types.Process, entryType EntryType, entryLeader Process) {
reducedPrecisionStartTime := timeutils.ReduceTimestampPrecision(entryLeader.PIDs.StartTimeNS)
interactive := tty.InteractiveFromTTY(entryLeader.CTTY)
euid := entryLeader.Creds.Euid
egid := entryLeader.Creds.Egid
process.EntryLeader.PID = entryLeader.PIDs.Tgid
process.EntryLeader.Start = timeutils.TimeFromNsSinceBoot(reducedPrecisionStartTime)
process.EntryLeader.Name = basename(entryLeader.Filename)
process.EntryLeader.Executable = entryLeader.Filename
process.EntryLeader.Args = entryLeader.Argv
process.EntryLeader.WorkingDirectory = entryLeader.Cwd
process.EntryLeader.Interactive = &interactive
process.EntryLeader.User.ID = strconv.FormatUint(uint64(euid), 10)
username, ok := getUserName(process.EntryLeader.User.ID)
if ok {
process.EntryLeader.User.Name = username
}
process.EntryLeader.Group.ID = strconv.FormatUint(uint64(egid), 10)
groupname, ok := getGroupName(process.EntryLeader.Group.ID)
if ok {
process.EntryLeader.Group.Name = groupname
}
process.EntryLeader.EntryMeta.Type = string(entryType)
}
func (db *DB) setEntityID(process *types.Process) {
if process.PID != 0 && process.Start != nil {
process.EntityID = db.calculateEntityIDv1(process.PID, *process.Start)
}
if process.Parent.PID != 0 && process.Parent.Start != nil {
process.Parent.EntityID = db.calculateEntityIDv1(process.Parent.PID, *process.Parent.Start)
}
if process.GroupLeader.PID != 0 && process.GroupLeader.Start != nil {
process.GroupLeader.EntityID = db.calculateEntityIDv1(process.GroupLeader.PID, *process.GroupLeader.Start)
}
if process.SessionLeader.PID != 0 && process.SessionLeader.Start != nil {
process.SessionLeader.EntityID = db.calculateEntityIDv1(process.SessionLeader.PID, *process.SessionLeader.Start)
}
if process.EntryLeader.PID != 0 && process.EntryLeader.Start != nil {
process.EntryLeader.EntityID = db.calculateEntityIDv1(process.EntryLeader.PID, *process.EntryLeader.Start)
}
}
func setSameAsProcess(process *types.Process) {
if process.GroupLeader.PID != 0 && process.GroupLeader.Start != nil {
sameAsProcess := process.PID == process.GroupLeader.PID
process.GroupLeader.SameAsProcess = &sameAsProcess
}
if process.SessionLeader.PID != 0 && process.SessionLeader.Start != nil {
sameAsProcess := process.PID == process.SessionLeader.PID
process.SessionLeader.SameAsProcess = &sameAsProcess
}
if process.EntryLeader.PID != 0 && process.EntryLeader.Start != nil {
sameAsProcess := process.PID == process.EntryLeader.PID
process.EntryLeader.SameAsProcess = &sameAsProcess
}
}
func (db *DB) HasProcess(pid uint32) bool {
db.mutex.RLock()
defer db.mutex.RUnlock()
_, ok := db.processes[pid]
return ok
}
func (db *DB) GetProcess(pid uint32) (types.Process, error) {
db.mutex.RLock()
defer db.mutex.RUnlock()
process, ok := db.processes[pid]
if !ok {
db.stats.failedToFindProcessCount.Add(1)
return types.Process{}, errors.New("process not found")
}
db.stats.servedProcessCount.Add(1)
ret := fullProcessFromDBProcess(process)
if process.PIDs.Ppid != 0 {
for i := 0; i < retryCount; i++ {
if parent, ok := db.processes[process.PIDs.Ppid]; ok {
fillParent(&ret, parent)
break
}
}
}
if process.PIDs.Pgid != 0 {
for i := 0; i < retryCount; i++ {
if groupLeader, ok := db.processes[process.PIDs.Pgid]; ok {
fillGroupLeader(&ret, groupLeader)
break
}
}
}
if process.PIDs.Sid != 0 {
for i := 0; i < retryCount; i++ {
if sessionLeader, ok := db.processes[process.PIDs.Sid]; ok {
fillSessionLeader(&ret, sessionLeader)
break
}
}
}
if entryLeaderPID, foundEntryLeaderPID := db.entryLeaderRelationships[process.PIDs.Tgid]; foundEntryLeaderPID {
if entryLeader, foundEntryLeader := db.processes[entryLeaderPID]; foundEntryLeader {
// if there is an entry leader then there is a matching member in the entryLeaders table
fillEntryLeader(&ret, db.entryLeaders[entryLeaderPID], entryLeader)
} else {
db.logger.Debugf("failed to find entry leader entry %d for %d (%s)", entryLeaderPID, pid, db.processes[pid].Filename)
}
} else {
db.logger.Debugf("failed to find entry leader for %d (%s)", pid, db.processes[pid].Filename)
db.stats.entryLeaderLookupFail.Add(1)
}
db.setEntityID(&ret)
setSameAsProcess(&ret)
return ret, nil
}
func (db *DB) GetEntryType(pid uint32) (EntryType, error) {
db.mutex.RLock()
defer db.mutex.RUnlock()
if entryType, ok := db.entryLeaders[pid]; ok {
return entryType, nil
}
return EntryUnknown, nil
}
func (db *DB) ScrapeProcfs() []uint32 {
db.mutex.Lock()
defer db.mutex.Unlock()
procs, err := db.procfs.GetAllProcesses()
if err != nil {
db.logger.Errorf("failed to get processes from procfs: %v", err)
return make([]uint32, 0)
}
// sorting the slice to make sure that parents, session leaders, group
// leaders come first in the queue
sort.Slice(procs, func(i, j int) bool {
return procs[i].PIDs.Tgid == procs[j].PIDs.Ppid ||
procs[i].PIDs.Tgid == procs[j].PIDs.Sid ||
procs[i].PIDs.Tgid == procs[j].PIDs.Pgid
})
pids := make([]uint32, 0)
for _, procInfo := range procs {
process := Process{
PIDs: pidInfoFromProto(procInfo.PIDs),
Creds: credInfoFromProto(procInfo.Creds),
CTTY: ttyDevFromProto(procInfo.CTTY),
Argv: procInfo.Argv,
Cwd: procInfo.Cwd,
Filename: procInfo.Filename,
}
db.insertProcess(process)
pids = append(pids, process.PIDs.Tgid)
}
return pids
}
func stringStartsWithEntryInList(str string, list []string) bool {
for _, entry := range list {
if strings.HasPrefix(str, entry) {
return true
}
}
return false
}
func isContainerRuntime(executable string) bool {
return slices.ContainsFunc(containerRuntimes[:], func(s string) bool {
return strings.HasPrefix(executable, s)
})
}
func isFilteredExecutable(executable string) bool {
return stringStartsWithEntryInList(executable, filteredExecutables[:])
}
func (db *DB) Close() {
close(db.stopChan)
}