diff --git a/daemon/procmon/activepids.go b/daemon/procmon/activepids.go index b7f079fa04..9899aa92fd 100644 --- a/daemon/procmon/activepids.go +++ b/daemon/procmon/activepids.go @@ -33,10 +33,9 @@ func MonitorProcEvents(stop <-chan struct{}) { proc := NewProcessWithParent(int(ev.PID), int(ev.TGID), "") log.Debug("[procmon exec event] %d, pid:%d tgid:%d %s, %s -> %s\n", ev.TimeStamp, ev.PID, ev.TGID, proc.Comm, proc.Path, proc.Parent.Path) - if _, needsUpdate, found := EventsCache.IsInStore(int(ev.PID), proc); found { + if item, needsUpdate, found := EventsCache.IsInStore(int(ev.PID), proc); found { if needsUpdate { - EventsCache.ComputeChecksums(proc) - EventsCache.UpdateItem(proc) + EventsCache.Update(&item.Proc, proc) } log.Debug("[procmon exec event inCache] %d, pid:%d tgid:%d\n", ev.TimeStamp, ev.PID, ev.TGID) continue diff --git a/daemon/procmon/cache_events.go b/daemon/procmon/cache_events.go index 6c416734ea..4bd921d69a 100644 --- a/daemon/procmon/cache_events.go +++ b/daemon/procmon/cache_events.go @@ -11,13 +11,11 @@ var ( // EventsCache is the cache of processes EventsCache *EventsStore eventsCacheTicker *time.Ticker + // When we receive an Exit event, we'll delete it from cache. // This TTL defines how much time we retain a PID on cache, before we receive // an Exit event. - pidTTL = 3600 // seconds - // the 2nd cache of items is by path. - // - pathTTL = 3600 * 24 // 1 day + pidTTL = 20 // seconds ) func init() { @@ -37,8 +35,8 @@ type ProcessEvent struct { // ExecEventItem represents an item of the cache type ExecEventItem struct { - sync.RWMutex - Proc *Process + //sync.RWMutex + Proc Process LastSeen int64 TTL int32 } @@ -52,9 +50,7 @@ func (e *ExecEventItem) isValid() bool { //EventsStore is the cache of exec events type EventsStore struct { - eventByPID map[int]*ExecEventItem - // a path will have multiple pids, hashes will be computed only once by path - eventByPath map[string]*ExecEventItem + eventByPID map[int]ExecEventItem checksums map[string]uint mu *sync.RWMutex checksumsEnabled bool @@ -68,10 +64,9 @@ func NewEventsStore() *EventsStore { eventsCacheTicker = time.NewTicker(10 * time.Second) return &EventsStore{ - mu: &sync.RWMutex{}, - checksums: make(map[string]uint, 500), - eventByPID: make(map[int]*ExecEventItem, 500), - eventByPath: make(map[string]*ExecEventItem, 500), + mu: &sync.RWMutex{}, + checksums: make(map[string]uint, 500), + eventByPID: make(map[int]ExecEventItem, 500), } } @@ -80,82 +75,161 @@ func NewEventsStore() *EventsStore { // or reused existing ones otherwise. func (e *EventsStore) Add(proc *Process) { log.Debug("[cache] EventsStore.Add() %d, %s", proc.ID, proc.Path) - // add the item to cache ASAP + // Add the item to cache ASAP, // then calculate the checksums if needed. e.UpdateItem(proc) if e.GetComputeChecksums() { - e.ComputeChecksums(proc) - e.UpdateItem(proc) + if e.ComputeChecksums(proc) { + e.UpdateItem(proc) + } } + log.Debug("[cache] EventsStore.Add() finished") } // UpdateItem updates a cache item func (e *EventsStore) UpdateItem(proc *Process) { - log.Debug("[cache] updateItem() adding to events store (total: %d), pid: %d, paths: %s", e.Len(), proc.ID, proc.Path) + log.Debug("[cache] updateItem() updating events store (total: %d), pid: %d, path: %s", e.Len(), proc.ID, proc.Path) if proc.Path == "" { return } e.mu.Lock() - ev := &ExecEventItem{ - Proc: proc, + ev := ExecEventItem{ + Proc: *proc, LastSeen: time.Now().UnixNano(), } e.eventByPID[proc.ID] = ev - e.eventByPath[proc.Path] = ev e.mu.Unlock() } -// IsInStore checks if a PID is in the store. -// If the PID is in cache, we may need to update it if the PID -// is reusing the PID of the parent. -func (e *EventsStore) IsInStore(key int, proc *Process) (item *ExecEventItem, needsUpdate bool, found bool) { - item, found = e.IsInStoreByPID(key) - if !found { +// ReplaceItem replaces an existing process with a new one. +func (e *EventsStore) ReplaceItem(oldProc, newProc *Process) { + log.Debug("[event inCache, replacement] new: %d, %s -> inCache: %d -> %s", newProc.ID, newProc.Path, oldProc.ID, oldProc.Path) + // Note: in rare occasions, the process being replaced is the older one. + // if oldProc.Starttime > newProc.Starttime {} + // + newProc.PPID = oldProc.ID + e.UpdateItem(newProc) + + if newProc.ChecksumsCount() == 0 { + e.ComputeChecksums(newProc) + e.UpdateItem(newProc) + } + + if len(oldProc.Tree) == 0 { + oldProc.GetParent() + oldProc.BuildTree() + e.UpdateItem(newProc) + } + + // TODO: work on improving the process tree (specially with forks/clones*) + if len(newProc.Tree) == 0 { + newProc.Parent = oldProc + newProc.BuildTree() + e.UpdateItem(newProc) + } +} + +// Update ... +func (e *EventsStore) Update(oldProc, proc *Process) { + log.Debug("[cache Update old] %d in cache -> %s", oldProc.ID, oldProc.Path) + + update := false + updateOld := false + + // forked process. Update cache. + // execEvent -> pid: 12345, /usr/bin/exec-wrapper + // execEvent -> pid: 12345, /usr/bin/telnet + if proc != nil && (proc.ID == oldProc.ID && proc.Path != oldProc.Path) { + e.ReplaceItem(oldProc, proc) return } - log.Debug("[cache] Event found by PID: %d, %s", key, item.Proc.Path) + + if len(oldProc.Tree) == 0 { + oldProc.GetParent() + oldProc.BuildTree() + updateOld = true + } + + if proc != nil && (len(oldProc.Tree) > 0 && len(proc.Tree) == 0 && oldProc.ID == proc.ID) { + proc.Tree = oldProc.Tree + update = true + } + + if updateOld { + log.Debug("[cache] Update end, updating oldProc: %d, %s, %v", oldProc.ID, oldProc.Path, oldProc.Tree) + e.UpdateItem(oldProc) + } + if update { + log.Debug("[cache] Update end, updating newProc: %d, %s, %v", proc.ID, proc.Path, proc.Tree) + e.UpdateItem(proc) + } +} + +func (e *EventsStore) needsUpdate(cachedProc, proc *Process) bool { + cachedProc.RLock() + defer cachedProc.RUnlock() // check if this PID has replaced the PPID: // systemd, pid:1234 -> curl, pid:1234 -> curl (i.e.: pid 1234) opens x.x.x.x:443 // Without this, we would display for example "systemd is connecting to x.x.x.x:443", // instead of "curl is connecting to ..." // The previous pid+path will still exist as parent of the new child, in proc.Parent - if proc != nil && proc.Path != "" && item.Proc.Path != proc.Path { - log.Debug("[event inCache, replacement] new: %d, %s -> inCache: %d -> %s", proc.ID, proc.Path, item.Proc.ID, item.Proc.Path) - //e.UpdateItem(proc) - needsUpdate = true + if proc != nil && (proc.ID == cachedProc.ID && proc.Path != cachedProc.Path) { + return true } - return -} + sumsCount := cachedProc.ChecksumsCount() -// IsInStoreByPID checks if a pid exists in cache. -func (e *EventsStore) IsInStoreByPID(key int) (item *ExecEventItem, found bool) { - e.mu.RLock() - item, found = e.eventByPID[key] - e.mu.RUnlock() - return + if proc != nil && sumsCount > 0 && cachedProc.IsAlive() { + return false + } + + if cachedProc != nil && sumsCount == 0 { + return true + } + + if proc != nil && len(proc.Tree) == 0 { + return true + } + if cachedProc != nil && len(cachedProc.Tree) == 0 { + return true + } + + return false } -// IsInStoreByPath checks if a process exists in cache by path. -func (e *EventsStore) IsInStoreByPath(path string) (item *ExecEventItem, found bool) { - if path == "" || path == KernelConnection { +// IsInStore checks if a PID is in the store. +// If the PID is in cache, we may need to update it if the PID +// is reusing the PID of the parent. +func (e *EventsStore) IsInStore(key int, proc *Process) (item ExecEventItem, needsUpdate, found bool) { + + item, found = e.IsInStoreByPID(key) + if !found { return } - e.mu.RLock() - item, found = e.eventByPath[path] - e.mu.RUnlock() - if found { - log.Debug("[cache] event found by path: %s", path) + if found && e.needsUpdate(&item.Proc, proc) { + needsUpdate = true + return } + + log.Debug("[cache] Event found by PID: %d, %s", key, item.Proc.Path) + return } -// Delete an item from cache -func (e *EventsStore) Delete(key int) { +// IsInStoreByPID checks if a pid exists in cache. +func (e *EventsStore) IsInStoreByPID(key int) (item ExecEventItem, found bool) { e.mu.Lock() - delete(e.eventByPID, key) - e.mu.Unlock() + defer e.mu.Unlock() + item, found = e.eventByPID[key] + + if !found { + return + } + + item.LastSeen = time.Now().UnixNano() + + return } // Len returns the number of items in cache. @@ -165,90 +239,48 @@ func (e *EventsStore) Len() int { return len(e.eventByPID) } -// DeleteOldItems deletes items that have exceeded the TTL -func (e *EventsStore) DeleteOldItems() { +// Delete schedules an item to be deleted from cache. +func (e *EventsStore) Delete(key int) { e.mu.Lock() defer e.mu.Unlock() - log.Debug("[cache] deleting old events, total byPID: %d, byPath: %d", len(e.eventByPID), len(e.eventByPath)) - for k, item := range e.eventByPID { - if item.Proc.IsAlive() == false { - log.Debug("[cache] deleting old PID: %d -> %s", k, item.Proc.Path) - delete(e.eventByPID, k) - } + ev, found := e.eventByPID[key] + if !found { + return } - for path, item := range e.eventByPath { - if item.Proc.IsAlive() == false { - log.Debug("[cache] deleting old path: %d -> %s", item.Proc.ID, item.Proc.Path) - delete(e.eventByPath, path) - } + if !ev.Proc.IsAlive() { + delete(e.eventByPID, key) } } -// ------------------------------------------------------------------------- -// TODO: Move to its own package. -// A hashing service than runs in background, and accepts paths to hash -// and returns the hashes for different algorithms (configurables) - -// ComputeChecksums decides if we need to compute the checksum of a process or not. -// We don't recalculate hashes during the life of the process. -func (e *EventsStore) ComputeChecksums(proc *Process) { - if !e.checksumsEnabled { - return - } - log.Debug("[cache] reuseChecksums %d, %s", proc.ID, proc.Path) - - // XXX: why double check if the PID is in cache? - // reuseChecksums is called from Add(), and before calling Add() we check if - // the PID is in cache. - // The problem is that we don't intercept some events (fork, clone*, dup*), - // and because of this sometimes we don't receive the event of the parent. - item, _, found := e.IsInStore(proc.ID, proc) - if !found { - log.Debug("cache.reuseChecksums() %d not inCache, %s", proc.ID, proc.Path) - - // if parent path and current path are equal, and the parent is alive, see if we have the hash of the parent path - if !proc.IsChild() { - proc.ComputeChecksums(e.checksums) - log.Debug("[cache] reuseChecksums() pid not in cache, not child of parent: %d, %s - %d - %v", proc.ID, proc.Path, proc.Starttime, proc.Checksums) - return - } - - // parent path is nil or paths differ or parent is not alive - // compute new checksums - log.Debug("[cache] reuseChecksums() proc is child, proc: %d, %d, %s parent: %d, %d, %s", proc.Starttime, proc.ID, proc.Path, proc.Parent.Starttime, proc.Parent.ID, proc.Parent.Path) - pit, found := e.IsInStoreByPath(proc.Parent.Path) - if !found { - //log.Info("cache.reuseChecksums() cache.add() pid not found byPath: %d, %s, parent: %d, %s", proc.ID, proc.Path, proc.Parent.ID, proc.Parent.Path) - proc.ComputeChecksums(e.checksums) - return - } +// DeleteOldItems deletes items that have exited and exceeded the TTL. +// Keeping them in cache for a short period of time sometimes helps to +// link some connections to processes. +// Alived processes are not deleted. +func (e *EventsStore) DeleteOldItems() { + e.mu.Lock() + defer e.mu.Unlock() - // if the parent path is in cache reuse the checksums - log.Debug("[cache] reuseChecksums() inCache, found by parent path: %d:%s, parent alive: %v, %d:%s", pit.Proc.ID, pit.Proc.Path, proc.Parent.IsAlive(), proc.Parent.ID, proc.Parent.Path) - if len(pit.Proc.Checksums) == 0 { - proc.ComputeChecksums(e.checksums) - return + log.Debug("[cache] deleting old events, total byPID: %d", len(e.eventByPID)) + for k, item := range e.eventByPID { + if !item.isValid() && !item.Proc.IsAlive() { + delete(e.eventByPID, k) } - log.Debug("[cache] reuseCheckums() reusing checksums: %v", pit.Proc.Checksums) - proc.Checksums = pit.Proc.Checksums - return } +} - // pid found in cache - // we should check other parameters to see if the pid is really the same process - // proc//maps - item.Proc.RLock() - checksumsNum := len(item.Proc.Checksums) - item.Proc.RUnlock() - if checksumsNum > 0 && (item.Proc.IsAlive() && item.Proc.Path == proc.Path) { - log.Debug("[cache] reuseChecksums() cached PID alive, already hashed: %v, %s new: %s", item.Proc.Checksums, item.Proc.Path, proc.Path) - proc.Checksums = item.Proc.Checksums - return - } - log.Debug("[cache] reuseChecksums() PID found inCache, computing hashes: %s new: %s - hashes: |%v<>%v|", item.Proc.Path, proc.Path, item.Proc.Checksums, proc.Checksums) +// ComputeChecksums obtains the checksums of the process +func (e *EventsStore) ComputeChecksums(proc *Process) bool { + e.mu.RLock() + defer e.mu.RUnlock() + if !e.checksumsEnabled || proc != nil && proc.IsAlive() && proc.ChecksumsCount() > 0 { + log.Debug("[cache] ComputeChecksums, already hashed: %s -> %v", proc.Path, proc.Checksums) + return false + } proc.ComputeChecksums(e.checksums) + + return true } // AddChecksumHash adds a new hash algorithm to compute checksums @@ -279,12 +311,12 @@ func (e *EventsStore) SetComputeChecksums(compute bool) { if !compute { for _, item := range e.eventByPID { // XXX: reset saved checksums? or keep them in cache? - item.Proc.Checksums = make(map[string]string) + item.Proc.ResetChecksums() } return } for _, item := range e.eventByPID { - if len(item.Proc.Checksums) == 0 { + if item.Proc.ChecksumsCount() == 0 { item.Proc.ComputeChecksums(e.checksums) } } diff --git a/daemon/procmon/details.go b/daemon/procmon/details.go index 600a78fe65..1dacc09144 100644 --- a/daemon/procmon/details.go +++ b/daemon/procmon/details.go @@ -33,25 +33,15 @@ func (p *Process) GetParent() { return } - // ReadFile + parse = ~40us - data, err := ioutil.ReadFile(p.pathStat) - if err != nil { - return - } - var ppid int - var state string - // https://lore.kernel.org/lkml/tog7cb$105a$1@ciao.gmane.io/T/ - parts := bytes.Split(data, []byte(")")) - data = parts[len(parts)-1] - _, err = fmt.Sscanf(string(data), "%s %d", &state, &ppid) - if err != nil || ppid == 0 { + p.ReadPPID() + if p.PPID == 0 { return } // TODO: see how we can reuse this object and the ppid, to save some iterations. // right now it opens the can of leaks. p.mu.Lock() - p.Parent = NewProcessEmpty(ppid, "") + p.Parent = NewProcessEmpty(p.PPID, "") p.mu.Unlock() p.Parent.ReadPath() @@ -64,6 +54,12 @@ func (p *Process) BuildTree() { if len(p.Tree) > 0 { return } + // Adding this process to the tree, not to loose track of it. + p.Tree = append(p.Tree, + &protocol.StringInt{ + Key: p.Path, Value: uint32(p.ID), + }, + ) for pp := p.Parent; pp != nil; pp = pp.Parent { // add the parents in reverse order, so when we iterate over them with the rules // the first item is the most direct parent of the process. @@ -116,6 +112,26 @@ func (p *Process) GetExtraInfo() error { return nil } +// ReadPPID obtains the pid of the parent process +func (p *Process) ReadPPID() { + // ReadFile + parse = ~40us + data, err := ioutil.ReadFile(p.pathStat) + if err != nil { + p.PPID = 0 + return + } + + var state string + // https://lore.kernel.org/lkml/tog7cb$105a$1@ciao.gmane.io/T/ + parts := bytes.Split(data, []byte(")")) + data = parts[len(parts)-1] + _, err = fmt.Sscanf(string(data), "%s %d", &state, &p.PPID) + if err != nil || p.PPID == 0 { + p.PPID = 0 + return + } +} + // ReadComm reads the comm name from ProcFS /proc//comm func (p *Process) ReadComm() error { if p.Comm != "" { @@ -380,12 +396,33 @@ func (p *Process) IsAlive() bool { // IsChild determines if this process is child of its parent func (p *Process) IsChild() bool { - return p.Parent != nil && p.Parent.Path == p.Path && p.Parent.IsAlive() //&& proc.Starttime != proc.Parent.Starttime + return (p.Parent != nil && p.Parent.Path == p.Path && p.Parent.IsAlive()) || + core.Exists(fmt.Sprint("/proc/", p.PPID, "/task/", p.ID)) + +} + +// ChecksumsCount returns the number of checksums of this process. +func (p *Process) ChecksumsCount() int { + p.mu.RLock() + defer p.mu.RUnlock() + return len(p.Checksums) +} + +// ResetChecksums initializes checksums +func (p *Process) ResetChecksums() { + p.mu.Lock() + p.Checksums = make(map[string]string) + p.mu.Unlock() } // ComputeChecksums calculates the checksums of a the process path to the binary. // Users may want to use different hashing alogrithms. func (p *Process) ComputeChecksums(hashes map[string]uint) { + if p.IsAlive() && len(p.Checksums) > 0 { + log.Debug("process.ComputeChecksums() already hashed: %d, path: %s, %v", p.ID, p.Path, p.Checksums) + return + } + for hash := range hashes { p.ComputeChecksum(hash) } @@ -425,8 +462,8 @@ func (p *Process) ComputeChecksum(algo string) { } i := uint8(0) - for i = 0; i < 2; i++ { - log.Debug("[hashing %s], path %d: %s", algo, i, paths[i]) + for i = 0; i < 3; i++ { + log.Debug("[hashing %s], path %d: %s -> %s", algo, i, paths[i], p.Path) start := time.Now() h.Reset() @@ -441,9 +478,9 @@ func (p *Process) ComputeChecksum(algo string) { log.Debug("[hashing] Unable to dump process memory: %s", err) continue } - p.Lock() + p.mu.Lock() p.Checksums[algo] = hex.EncodeToString(h.Sum(code)) - p.Unlock() + p.mu.Unlock() log.Debug("[hashing] memory region hashed, elapsed: %v ,Hash: %s, %s\n", time.Since(start), p.Checksums[algo], paths[i]) code = nil break @@ -454,9 +491,9 @@ func (p *Process) ComputeChecksum(algo string) { log.Debug("[hashing %s] Error copying data: %s", algo, err) continue } - p.Lock() + p.mu.Lock() p.Checksums[algo] = hex.EncodeToString(h.Sum(nil)) - p.Unlock() + p.mu.Unlock() log.Debug("[hashing] elapsed: %v ,Hash: %s, %s\n", time.Since(start), p.Checksums[algo], paths[i]) break @@ -536,10 +573,6 @@ func (p *Process) dumpFileImage(filePath string) ([]byte, error) { mappings = nil //fmt.Printf(">>> READ MEM, regions size: %d, elfCode: %d\n", size, len(elfCode)) - //if fInfo, err := os.Stat(filePath); err == nil { - // fmt.Printf("\t>>> on disk: %d\n", fInfo.Size()) - //} - if err != nil { return nil, err } diff --git a/daemon/procmon/ebpf/cache.go b/daemon/procmon/ebpf/cache.go index 85b0531e32..de0e900cf6 100644 --- a/daemon/procmon/ebpf/cache.go +++ b/daemon/procmon/ebpf/cache.go @@ -46,7 +46,7 @@ func (i *ebpfCacheItem) isValid() bool { func NewEbpfCache() *ebpfCacheType { ebpfCacheTicker = time.NewTicker(1 * time.Minute) return &ebpfCacheType{ - Items: make(map[interface{}]*ebpfCacheItem, 0), + Items: make(map[interface{}]*ebpfCacheItem, 500), mu: &sync.RWMutex{}, } } @@ -83,6 +83,17 @@ func (e *ebpfCacheType) update(key interface{}, item *ebpfCacheItem) { e.Items[key] = item } +func (e *ebpfCacheType) updateByPid(proc *procmon.Process) { + e.mu.Lock() + defer e.mu.Unlock() + for k, item := range e.Items { + if proc.ID == item.Proc.ID { + e.update(k, item) + } + } + +} + func (e *ebpfCacheType) Len() int { e.mu.RLock() defer e.mu.RUnlock() diff --git a/daemon/procmon/ebpf/events.go b/daemon/procmon/ebpf/events.go index d2840eb606..85c48298e4 100644 --- a/daemon/procmon/ebpf/events.go +++ b/daemon/procmon/ebpf/events.go @@ -161,16 +161,18 @@ func streamEventsWorker(id int, chn chan []byte, lost chan uint64, kernelEvents log.Debug("Lost ebpf events: %d", l) case d := <-chn: if err := binary.Read(bytes.NewBuffer(d), hostByteOrder, &event); err != nil { - log.Error("[eBPF events #%d] error: %s", id, err) - } else { - switch event.Type { - case EV_TYPE_EXEC, EV_TYPE_EXECVEAT: - processExecEvent(&event) - - case EV_TYPE_SCHED_EXIT: - processExitEvent(&event) - } + log.Debug("[eBPF events #%d] error: %s", id, err) + continue } + + switch event.Type { + case EV_TYPE_EXEC, EV_TYPE_EXECVEAT: + processExecEvent(&event) + + case EV_TYPE_SCHED_EXIT: + processExitEvent(&event) + } + } } @@ -178,13 +180,46 @@ Exit: log.Debug("perfMap goroutine exited #%d", id) } +// processExecEvent parses an execEevent to Process, saves or reuses it to +// cache, and decides if it needs to be updated. +func processExecEvent(event *execEvent) { + proc := event2process(event) + if proc == nil { + return + } + log.Debug("[eBPF exec event] type: %d, ppid: %d, pid: %d, %s -> %s", event.Type, event.PPID, event.PID, proc.Path, proc.Args) + itemParent, pfound := procmon.EventsCache.IsInStoreByPID(proc.PPID) + if pfound { + proc.Parent = &itemParent.Proc + proc.Tree = itemParent.Proc.Tree + } + + item, needsUpdate, found := procmon.EventsCache.IsInStore(int(event.PID), proc) + if !found { + procmon.EventsCache.Add(proc) + getProcDetails(event, proc) + procmon.EventsCache.UpdateItem(proc) + ebpfCache.updateByPid(proc) + return + } + + if found && needsUpdate { + procmon.EventsCache.Update(&item.Proc, proc) + ebpfCache.updateByPid(&item.Proc) + } + + // from now on use cached Process + log.Debug("[eBPF event inCache] -> %d, %s", event.PID, item.Proc.Path) +} + +// event2process creates a new Process from execEvent func event2process(event *execEvent) (proc *procmon.Process) { proc = procmon.NewProcessEmpty(int(event.PID), byteArrayToString(event.Comm[:])) proc.UID = int(event.UID) - // trust process path received from kernel + // NOTE: this is the absolute path executed, but no the real path to the binary. - // if it's executed from a chroot, the absolute path willa be /chroot/path/usr/bin/blabla - // if it's from a container, the absolute path will be /proc//root/usr/bin/blabla + // if it's executed from a chroot, the absolute path will be /chroot/path/usr/bin/blabla + // if it's from a container, the real absolute path will be /proc//root/usr/bin/blabla path := byteArrayToString(event.Filename[:]) if path != "" { proc.SetPath(path) @@ -193,6 +228,8 @@ func event2process(event *execEvent) (proc *procmon.Process) { return nil } } + proc.ReadPPID() + if event.ArgsPartial == 0 { for i := 0; i < int(event.ArgsCount); i++ { proc.Args = append(proc.Args, byteArrayToString(event.Args[i][:])) @@ -201,45 +238,18 @@ func event2process(event *execEvent) (proc *procmon.Process) { } else { proc.ReadCmdline() } - proc.GetParent() - proc.BuildTree() - proc.ReadCwd() - proc.ReadEnv() - log.Debug("[eBPF exec event] ppid: %d, pid: %d, %s -> %s", event.PPID, event.PID, proc.Path, proc.Args) return } -func processExecEvent(event *execEvent) { - proc := event2process(event) - if proc == nil { - return - } - // TODO: store multiple executions with the same pid but different paths: - // forks, execves... execs from chroots, containers, etc. - if item, needsUpdate, found := procmon.EventsCache.IsInStore(int(event.PID), proc); found { - if needsUpdate { - // when a process is replaced in memory, it'll be found in cache by PID, - // but the new process's details will be empty - proc.Parent = item.Proc - procmon.EventsCache.ComputeChecksums(proc) - procmon.EventsCache.UpdateItem(proc) - } - log.Debug("[eBPF event inCache] -> %d, %v", event.PID, item.Proc.Checksums) - return - } - procmon.EventsCache.Add(proc) +func getProcDetails(event *execEvent, proc *procmon.Process) { + proc.GetParent() + proc.BuildTree() + proc.ReadCwd() + proc.ReadEnv() } func processExitEvent(event *execEvent) { log.Debug("[eBPF exit event] pid: %d, ppid: %d", event.PID, event.PPID) - ev, _, found := procmon.EventsCache.IsInStore(int(event.PID), nil) - if !found { - return - } - log.Debug("[eBPF exit event inCache] pid: %d, tgid: %d", event.PID, event.PPID) - if ev.Proc.IsAlive() == false { - procmon.EventsCache.Delete(int(event.PID)) - log.Debug("[ebpf exit event] deleting DEAD pid: %d", event.PID) - } + procmon.EventsCache.Delete(int(event.PID)) } diff --git a/daemon/procmon/ebpf/find.go b/daemon/procmon/ebpf/find.go index d53e8ce24d..df9be76340 100644 --- a/daemon/procmon/ebpf/find.go +++ b/daemon/procmon/ebpf/find.go @@ -160,11 +160,9 @@ func findConnProcess(value *networkEventT, connKey string) (proc *procmon.Proces // Use socket's UID. A process may have dropped privileges. // This is the UID that we've always used. - if ev, _, found := procmon.EventsCache.IsInStore(int(value.Pid), nil); found { - ev.Lock() + if ev, found := procmon.EventsCache.IsInStoreByPID(int(value.Pid)); found { ev.Proc.UID = int(value.UID) - ev.Unlock() - proc = ev.Proc + proc = &ev.Proc log.Debug("[ebpf conn] not in cache, but in execEvents: %s, %d -> %s -> %s", connKey, proc.ID, proc.Path, proc.Args) return } diff --git a/daemon/procmon/parse.go b/daemon/procmon/parse.go index 205f9fcb9f..ce209848e8 100644 --- a/daemon/procmon/parse.go +++ b/daemon/procmon/parse.go @@ -98,7 +98,7 @@ func FindProcess(pid int, interceptUnknown bool) *Process { } if ev, _, found := EventsCache.IsInStore(pid, nil); found { - return ev.Proc + return &ev.Proc } proc := NewProcessEmpty(pid, "") diff --git a/daemon/procmon/process.go b/daemon/procmon/process.go index e7f14586d7..f8174195ab 100644 --- a/daemon/procmon/process.go +++ b/daemon/procmon/process.go @@ -123,6 +123,7 @@ func NewProcessEmpty(pid int, comm string) *Process { mu: &sync.RWMutex{}, Starttime: time.Now().UnixNano(), ID: pid, + PPID: 0, Comm: comm, Args: make([]string, 0), Env: make(map[string]string), @@ -235,8 +236,8 @@ func SetMonitorMethod(newMonitorMethod string) { // GetMonitorMethod configures a new method for parsing connections. func GetMonitorMethod() string { - lock.Lock() - defer lock.Unlock() + lock.RLock() + defer lock.RUnlock() return monitorMethod } diff --git a/daemon/ui/notifications.go b/daemon/ui/notifications.go index 22be0847f2..8df853441d 100644 --- a/daemon/ui/notifications.go +++ b/daemon/ui/notifications.go @@ -63,7 +63,7 @@ func (c *Client) monitorProcessDetails(pid int, stream protocol.UI_Notifications p := &procmon.Process{} item, found := procmon.EventsCache.IsInStoreByPID(pid) if found { - newProc := *item.Proc + newProc := item.Proc p = &newProc if len(p.Tree) == 0 { p.GetParent()