mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 08:34:40 +01:00
procmon/cache improvements
- Fixed several leaks. - Cache of events reorganized and improved. * items are added faster. * proc details are rebuilt if needed (checksums, proc tree, etc) * proc's tree is reused if we've got the parent in cache. rel: #413
This commit is contained in:
parent
9efaa37098
commit
431e2d3ed9
9 changed files with 290 additions and 206 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -70,8 +66,7 @@ func NewEventsStore() *EventsStore {
|
|||
return &EventsStore{
|
||||
mu: &sync.RWMutex{},
|
||||
checksums: make(map[string]uint, 500),
|
||||
eventByPID: make(map[int]*ExecEventItem, 500),
|
||||
eventByPath: make(map[string]*ExecEventItem, 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)
|
||||
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
|
||||
}
|
||||
|
||||
sumsCount := cachedProc.ChecksumsCount()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if found && e.needsUpdate(&item.Proc, proc) {
|
||||
needsUpdate = true
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("[cache] Event found by PID: %d, %s", key, item.Proc.Path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsInStoreByPath checks if a process exists in cache by path.
|
||||
func (e *EventsStore) IsInStoreByPath(path string) (item *ExecEventItem, found bool) {
|
||||
if path == "" || path == KernelConnection {
|
||||
return
|
||||
}
|
||||
e.mu.RLock()
|
||||
item, found = e.eventByPath[path]
|
||||
e.mu.RUnlock()
|
||||
if found {
|
||||
log.Debug("[cache] event found by path: %s", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete an item from cache
|
||||
func (e *EventsStore) Delete(key int) {
|
||||
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
|
||||
// Delete schedules an item to be deleted from cache.
|
||||
func (e *EventsStore) Delete(key int) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
ev, found := e.eventByPID[key]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
if !ev.Proc.IsAlive() {
|
||||
delete(e.eventByPID, key)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
log.Debug("[cache] deleting old events, total byPID: %d, byPath: %d", len(e.eventByPID), len(e.eventByPath))
|
||||
log.Debug("[cache] deleting old events, total byPID: %d", len(e.eventByPID))
|
||||
for k, item := range e.eventByPID {
|
||||
if item.Proc.IsAlive() == false {
|
||||
log.Debug("[cache] deleting old PID: %d -> %s", k, item.Proc.Path)
|
||||
if !item.isValid() && !item.Proc.IsAlive() {
|
||||
delete(e.eventByPID, k)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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 obtains the checksums of the process
|
||||
func (e *EventsStore) ComputeChecksums(proc *Process) bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
|
||||
// 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
|
||||
if !e.checksumsEnabled || proc != nil && proc.IsAlive() && proc.ChecksumsCount() > 0 {
|
||||
log.Debug("[cache] ComputeChecksums, already hashed: %s -> %v", proc.Path, proc.Checksums)
|
||||
return false
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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] 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/<pid>/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)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/<pid>/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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -161,8 +161,10 @@ 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 {
|
||||
log.Debug("[eBPF events #%d] error: %s", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch event.Type {
|
||||
case EV_TYPE_EXEC, EV_TYPE_EXECVEAT:
|
||||
processExecEvent(&event)
|
||||
|
@ -170,7 +172,7 @@ func streamEventsWorker(id int, chn chan []byte, lost chan uint64, kernelEvents
|
|||
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/<pid>/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/<pid>/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()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getProcDetails(event *execEvent, proc *procmon.Process) {
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue