diff --git a/intel/module.go b/intel/module.go index ceec6b64..81f3efc5 100644 --- a/intel/module.go +++ b/intel/module.go @@ -9,5 +9,5 @@ import ( var Module *modules.Module func init() { - Module = modules.Register("intel", nil, nil, nil, "geoip", "filterlists", "customlists") + Module = modules.Register("intel", nil, nil, nil, "geoip", "filterlists") } diff --git a/netquery/manager.go b/netquery/manager.go index fb244526..16dcbe0e 100644 --- a/netquery/manager.go +++ b/netquery/manager.go @@ -197,7 +197,8 @@ func convertConnection(conn *network.Connection) (*Conn, error) { } extraData := map[string]interface{}{ - "pid": conn.ProcessContext.PID, + "pid": conn.ProcessContext.PID, + "processCreatedAt": conn.ProcessContext.CreatedAt, } if conn.TunnelContext != nil { diff --git a/network/connection.go b/network/connection.go index a1af8c06..f9b2379d 100644 --- a/network/connection.go +++ b/network/connection.go @@ -26,7 +26,7 @@ import ( type FirewallHandler func(conn *Connection, pkt packet.Packet) // ProcessContext holds additional information about the process -// that iniated a connection. +// that initiated a connection. type ProcessContext struct { // ProcessName is the name of the process. ProcessName string @@ -38,6 +38,8 @@ type ProcessContext struct { CmdLine string // PID is the process identifier. PID int + // CreatedAt the time when the process was created. + CreatedAt int64 // Profile is the ID of the main profile that // is applied to the process. Profile string @@ -223,6 +225,7 @@ func getProcessContext(ctx context.Context, proc *process.Process) ProcessContex BinaryPath: proc.Path, CmdLine: proc.CmdLine, PID: proc.Pid, + CreatedAt: proc.CreatedAt, } // Get local profile. diff --git a/network/database.go b/network/database.go index 20c4cbfb..159ba1d3 100644 --- a/network/database.go +++ b/network/database.go @@ -45,7 +45,7 @@ func makeKey(pid int, scope, id string) string { return fmt.Sprintf("network:tree/%d/%s/%s", pid, scope, id) } -func parseDBKey(key string) (pid int, scope, id string, ok bool) { +func parseDBKey(key string) (processKey string, scope, id string, ok bool) { // Split into segments. segments := strings.Split(key, "/") @@ -65,27 +65,18 @@ func parseDBKey(key string) (pid int, scope, id string, ok bool) { // TODO: For queries, also prefixes of these values are valid. default: // Unknown scope. - return 0, "", "", false + return "", "", "", false } fallthrough case 2: - var err error - if segments[1] == "" { - pid = process.UndefinedProcessID - } else { - pid, err = strconv.Atoi(segments[1]) - if err != nil { - return 0, "", "", false - } - } - - return pid, scope, id, true + processKey = segments[1] + return processKey, scope, id, true case 1: // This is a valid query prefix, but not process ID was given. - return process.UndefinedProcessID, "", "", true + return "", "", "", true default: - return 0, "", "", false + return "", "", "", false } } @@ -93,7 +84,7 @@ func parseDBKey(key string) (pid int, scope, id string, ok bool) { func (s *StorageInterface) Get(key string) (record.Record, error) { // Parse key and check if valid. pid, scope, id, ok := parseDBKey(strings.TrimPrefix(key, "network:")) - if !ok || pid == process.UndefinedProcessID { + if !ok || pid == "" { return nil, storage.ErrNotFound } @@ -135,7 +126,7 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) { return } - if pid == process.UndefinedProcessID { + if pid == "" { // processes for _, proc := range process.All() { func() { diff --git a/network/iphelper/iphelper.go b/network/iphelper/iphelper.go index 5fa1ea12..2256434b 100644 --- a/network/iphelper/iphelper.go +++ b/network/iphelper/iphelper.go @@ -11,7 +11,7 @@ import ( ) var ( - errInvalid = errors.New("IPHelper not initialzed or broken") + errInvalid = errors.New("IPHelper not initialized or broken") ) // IPHelper represents a subset of the Windows iphlpapi.dll. diff --git a/process/database.go b/process/database.go index 62df8f09..b457d069 100644 --- a/process/database.go +++ b/process/database.go @@ -15,7 +15,7 @@ import ( const processDatabaseNamespace = "network:tree" var ( - processes = make(map[int]*Process) + processes = make(map[string]*Process) processesLock sync.RWMutex dbController *database.Controller @@ -25,11 +25,11 @@ var ( ) // GetProcessFromStorage returns a process from the internal storage. -func GetProcessFromStorage(pid int) (*Process, bool) { +func GetProcessFromStorage(key string) (*Process, bool) { processesLock.RLock() defer processesLock.RUnlock() - p, ok := processes[pid] + p, ok := processes[key] return p, ok } @@ -55,11 +55,11 @@ func (p *Process) Save() { if !p.KeyIsSet() { // set key - p.SetKey(fmt.Sprintf("%s/%d", processDatabaseNamespace, p.Pid)) + p.SetKey(fmt.Sprintf("%s/%s", processDatabaseNamespace, getProcessKey(int32(p.Pid), p.CreatedAt))) // save processesLock.Lock() - processes[p.Pid] = p + processes[p.key] = p processesLock.Unlock() } @@ -75,7 +75,7 @@ func (p *Process) Delete() { // delete from internal storage processesLock.Lock() - delete(processes, p.Pid) + delete(processes, p.key) processesLock.Unlock() // propagate delete diff --git a/process/process.go b/process/process.go index 9f3ad2e4..777a6aa9 100644 --- a/process/process.go +++ b/process/process.go @@ -6,7 +6,6 @@ import ( "fmt" "path/filepath" "runtime" - "strconv" "strings" "sync" "time" @@ -31,18 +30,23 @@ type Process struct { // Process attributes. // Don't change; safe for concurrent access. - Name string - UserID int - UserName string - UserHome string - Pid int - ParentPid int - Path string - ExecName string - Cwd string - CmdLine string - FirstArg string - Env map[string]string + Name string + UserID int + UserName string + UserHome string + Pid int + CreatedAt int64 + ParentPid int + ParentCreatedAt int64 + Path string + ExecName string + Cwd string + CmdLine string + FirstArg string + Env map[string]string + + // unique process identifier ("Pid-CreatedAt") + key string // Profile attributes. // Once set, these don't change; safe for concurrent access. @@ -156,8 +160,21 @@ func (p *Process) String() string { func GetOrFindProcess(ctx context.Context, pid int) (*Process, error) { log.Tracer(ctx).Tracef("process: getting process for PID %d", pid) - p, err, _ := getProcessSingleInflight.Do(strconv.Itoa(pid), func() (interface{}, error) { - return loadProcess(ctx, pid) + // Get pid and created time for identification. + pInfo, err := processInfo.NewProcessWithContext(ctx, int32(pid)) + if err != nil { + return nil, err + } + + createdTime, err := pInfo.CreateTimeWithContext(ctx) + if err != nil { + return nil, err + } + + key := getProcessKey(int32(pid), createdTime) + + p, err, _ := getProcessSingleInflight.Do(key, func() (interface{}, error) { + return loadProcess(ctx, key, pInfo) }) if err != nil { return nil, err @@ -169,8 +186,8 @@ func GetOrFindProcess(ctx context.Context, pid int) (*Process, error) { return p.(*Process), nil // nolint:forcetypeassert // Can only be a *Process. } -func loadProcess(ctx context.Context, pid int) (*Process, error) { - switch pid { +func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (*Process, error) { + switch pInfo.Pid { case UnidentifiedProcessID: return GetUnidentifiedProcess(ctx), nil case UnsolicitedProcessID: @@ -179,19 +196,24 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) { return GetSystemProcess(ctx), nil } - process, ok := GetProcessFromStorage(pid) + // Get created time of process. The value should be cached. + createdAt, _ := pInfo.CreateTimeWithContext(ctx) + + process, ok := GetProcessFromStorage(getProcessKey(pInfo.Pid, createdAt)) if ok { return process, nil } // Create new a process object. process = &Process{ - Pid: pid, + Pid: int(pInfo.Pid), + CreatedAt: createdAt, FirstSeen: time.Now().Unix(), + key: key, } // Get process information from the system. - pInfo, err := processInfo.NewProcessWithContext(ctx, int32(pid)) + pInfo, err := processInfo.NewProcessWithContext(ctx, pInfo.Pid) if err != nil { return nil, err } @@ -202,7 +224,7 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) { var uids []int32 uids, err = pInfo.UidsWithContext(ctx) if err != nil { - return nil, fmt.Errorf("failed to get UID for p%d: %w", pid, err) + return nil, fmt.Errorf("failed to get UID for p%d: %w", pInfo.Pid, err) } process.UserID = int(uids[0]) } @@ -210,23 +232,30 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) { // Username process.UserName, err = pInfo.UsernameWithContext(ctx) if err != nil { - return nil, fmt.Errorf("process: failed to get Username for p%d: %w", pid, err) + return nil, fmt.Errorf("process: failed to get Username for p%d: %w", pInfo.Pid, err) } // TODO: User Home // new.UserHome, err = - // PPID + // Parent process id ppid, err := pInfo.PpidWithContext(ctx) if err != nil { - return nil, fmt.Errorf("failed to get PPID for p%d: %w", pid, err) + return nil, fmt.Errorf("failed to get PPID for p%d: %w", pInfo.Pid, err) } process.ParentPid = int(ppid) + // Parent created at time + parentCreatedAt, err := pInfo.CreateTimeWithContext(ctx) + if err != nil { + return nil, err + } + process.ParentCreatedAt = parentCreatedAt + // Path process.Path, err = pInfo.ExeWithContext(ctx) if err != nil { - return nil, fmt.Errorf("failed to get Path for p%d: %w", pid, err) + return nil, fmt.Errorf("failed to get Path for p%d: %w", pInfo.Pid, err) } // remove linux " (deleted)" suffix for deleted files if onLinux { @@ -247,13 +276,13 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) { // Command line arguments process.CmdLine, err = pInfo.CmdlineWithContext(ctx) if err != nil { - return nil, fmt.Errorf("failed to get Cmdline for p%d: %w", pid, err) + return nil, fmt.Errorf("failed to get Cmdline for p%d: %w", pInfo.Pid, err) } // Name process.Name, err = pInfo.NameWithContext(ctx) if err != nil { - return nil, fmt.Errorf("failed to get Name for p%d: %w", pid, err) + return nil, fmt.Errorf("failed to get Name for p%d: %w", pInfo.Pid, err) } if process.Name == "" { process.Name = process.ExecName @@ -262,7 +291,7 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) { // Get all environment variables env, err := pInfo.EnvironWithContext(ctx) if err != nil { - return nil, fmt.Errorf("failed to get the environment for p%d: %w", pid, err) + return nil, fmt.Errorf("failed to get the environment for p%d: %w", pInfo.Pid, err) } // Split env variables in key and value. process.Env = make(map[string]string, len(env)) @@ -283,6 +312,11 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) { return process, nil } +// Builds a unique identifier for a processes. +func getProcessKey(pid int32, createdTime int64) string { + return fmt.Sprintf("%d-%d", pid, createdTime) +} + // MatchingData returns the matching data for the process. func (p *Process) MatchingData() *MatchingData { return &MatchingData{p} diff --git a/process/special.go b/process/special.go index 7d2c3e93..6ecd2d0b 100644 --- a/process/special.go +++ b/process/special.go @@ -2,7 +2,6 @@ package process import ( "context" - "strconv" "time" "golang.org/x/sync/singleflight" @@ -39,29 +38,35 @@ func init() { var ( // unidentifiedProcess is used for non-attributed outgoing connections. unidentifiedProcess = &Process{ - UserID: UnidentifiedProcessID, - UserName: "Unknown", - Pid: UnidentifiedProcessID, - ParentPid: UnidentifiedProcessID, - Name: profile.UnidentifiedProfileName, + UserID: UnidentifiedProcessID, + UserName: "Unknown", + Pid: UnidentifiedProcessID, + CreatedAt: 1, + ParentPid: UnidentifiedProcessID, + ParentCreatedAt: 1, + Name: profile.UnidentifiedProfileName, } // unsolicitedProcess is used for non-attributed incoming connections. unsolicitedProcess = &Process{ - UserID: UnsolicitedProcessID, - UserName: "Unknown", - Pid: UnsolicitedProcessID, - ParentPid: UnsolicitedProcessID, - Name: profile.UnsolicitedProfileName, + UserID: UnsolicitedProcessID, + UserName: "Unknown", + Pid: UnsolicitedProcessID, + CreatedAt: 1, + ParentPid: UnsolicitedProcessID, + ParentCreatedAt: 1, + Name: profile.UnsolicitedProfileName, } // systemProcess is used to represent the Kernel. systemProcess = &Process{ - UserID: SystemProcessID, - UserName: "Kernel", - Pid: SystemProcessID, - ParentPid: SystemProcessID, - Name: profile.SystemProfileName, + UserID: SystemProcessID, + UserName: "Kernel", + Pid: SystemProcessID, + CreatedAt: 1, + ParentPid: SystemProcessID, + ParentCreatedAt: 1, + Name: profile.SystemProfileName, } getSpecialProcessSingleInflight singleflight.Group @@ -83,9 +88,9 @@ func GetSystemProcess(ctx context.Context) *Process { } func getSpecialProcess(ctx context.Context, template *Process) *Process { - p, _, _ := getSpecialProcessSingleInflight.Do(strconv.Itoa(template.Pid), func() (interface{}, error) { + p, _, _ := getSpecialProcessSingleInflight.Do(template.key, func() (interface{}, error) { // Check if we have already loaded the special process. - process, ok := GetProcessFromStorage(template.Pid) + process, ok := GetProcessFromStorage(template.key) if ok { return process, nil }