Fix process identification key

This commit is contained in:
Vladimir Stoilov 2023-03-30 14:43:38 +02:00
parent b9488c1a8f
commit 834282cb0e
8 changed files with 107 additions and 73 deletions

View file

@ -9,5 +9,5 @@ import (
var Module *modules.Module var Module *modules.Module
func init() { func init() {
Module = modules.Register("intel", nil, nil, nil, "geoip", "filterlists", "customlists") Module = modules.Register("intel", nil, nil, nil, "geoip", "filterlists")
} }

View file

@ -197,7 +197,8 @@ func convertConnection(conn *network.Connection) (*Conn, error) {
} }
extraData := map[string]interface{}{ extraData := map[string]interface{}{
"pid": conn.ProcessContext.PID, "pid": conn.ProcessContext.PID,
"processCreatedAt": conn.ProcessContext.CreatedAt,
} }
if conn.TunnelContext != nil { if conn.TunnelContext != nil {

View file

@ -26,7 +26,7 @@ import (
type FirewallHandler func(conn *Connection, pkt packet.Packet) type FirewallHandler func(conn *Connection, pkt packet.Packet)
// ProcessContext holds additional information about the process // ProcessContext holds additional information about the process
// that iniated a connection. // that initiated a connection.
type ProcessContext struct { type ProcessContext struct {
// ProcessName is the name of the process. // ProcessName is the name of the process.
ProcessName string ProcessName string
@ -38,6 +38,8 @@ type ProcessContext struct {
CmdLine string CmdLine string
// PID is the process identifier. // PID is the process identifier.
PID int PID int
// CreatedAt the time when the process was created.
CreatedAt int64
// Profile is the ID of the main profile that // Profile is the ID of the main profile that
// is applied to the process. // is applied to the process.
Profile string Profile string
@ -223,6 +225,7 @@ func getProcessContext(ctx context.Context, proc *process.Process) ProcessContex
BinaryPath: proc.Path, BinaryPath: proc.Path,
CmdLine: proc.CmdLine, CmdLine: proc.CmdLine,
PID: proc.Pid, PID: proc.Pid,
CreatedAt: proc.CreatedAt,
} }
// Get local profile. // Get local profile.

View file

@ -45,7 +45,7 @@ func makeKey(pid int, scope, id string) string {
return fmt.Sprintf("network:tree/%d/%s/%s", pid, scope, id) 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. // Split into segments.
segments := strings.Split(key, "/") 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. // TODO: For queries, also prefixes of these values are valid.
default: default:
// Unknown scope. // Unknown scope.
return 0, "", "", false return "", "", "", false
} }
fallthrough fallthrough
case 2: case 2:
var err error processKey = segments[1]
if segments[1] == "" { return processKey, scope, id, true
pid = process.UndefinedProcessID
} else {
pid, err = strconv.Atoi(segments[1])
if err != nil {
return 0, "", "", false
}
}
return pid, scope, id, true
case 1: case 1:
// This is a valid query prefix, but not process ID was given. // This is a valid query prefix, but not process ID was given.
return process.UndefinedProcessID, "", "", true return "", "", "", true
default: 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) { func (s *StorageInterface) Get(key string) (record.Record, error) {
// Parse key and check if valid. // Parse key and check if valid.
pid, scope, id, ok := parseDBKey(strings.TrimPrefix(key, "network:")) pid, scope, id, ok := parseDBKey(strings.TrimPrefix(key, "network:"))
if !ok || pid == process.UndefinedProcessID { if !ok || pid == "" {
return nil, storage.ErrNotFound return nil, storage.ErrNotFound
} }
@ -135,7 +126,7 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
return return
} }
if pid == process.UndefinedProcessID { if pid == "" {
// processes // processes
for _, proc := range process.All() { for _, proc := range process.All() {
func() { func() {

View file

@ -11,7 +11,7 @@ import (
) )
var ( 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. // IPHelper represents a subset of the Windows iphlpapi.dll.

View file

@ -15,7 +15,7 @@ import (
const processDatabaseNamespace = "network:tree" const processDatabaseNamespace = "network:tree"
var ( var (
processes = make(map[int]*Process) processes = make(map[string]*Process)
processesLock sync.RWMutex processesLock sync.RWMutex
dbController *database.Controller dbController *database.Controller
@ -25,11 +25,11 @@ var (
) )
// GetProcessFromStorage returns a process from the internal storage. // GetProcessFromStorage returns a process from the internal storage.
func GetProcessFromStorage(pid int) (*Process, bool) { func GetProcessFromStorage(key string) (*Process, bool) {
processesLock.RLock() processesLock.RLock()
defer processesLock.RUnlock() defer processesLock.RUnlock()
p, ok := processes[pid] p, ok := processes[key]
return p, ok return p, ok
} }
@ -55,11 +55,11 @@ func (p *Process) Save() {
if !p.KeyIsSet() { if !p.KeyIsSet() {
// set key // 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 // save
processesLock.Lock() processesLock.Lock()
processes[p.Pid] = p processes[p.key] = p
processesLock.Unlock() processesLock.Unlock()
} }
@ -75,7 +75,7 @@ func (p *Process) Delete() {
// delete from internal storage // delete from internal storage
processesLock.Lock() processesLock.Lock()
delete(processes, p.Pid) delete(processes, p.key)
processesLock.Unlock() processesLock.Unlock()
// propagate delete // propagate delete

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -31,18 +30,23 @@ type Process struct {
// Process attributes. // Process attributes.
// Don't change; safe for concurrent access. // Don't change; safe for concurrent access.
Name string Name string
UserID int UserID int
UserName string UserName string
UserHome string UserHome string
Pid int Pid int
ParentPid int CreatedAt int64
Path string ParentPid int
ExecName string ParentCreatedAt int64
Cwd string Path string
CmdLine string ExecName string
FirstArg string Cwd string
Env map[string]string CmdLine string
FirstArg string
Env map[string]string
// unique process identifier ("Pid-CreatedAt")
key string
// Profile attributes. // Profile attributes.
// Once set, these don't change; safe for concurrent access. // 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) { func GetOrFindProcess(ctx context.Context, pid int) (*Process, error) {
log.Tracer(ctx).Tracef("process: getting process for PID %d", pid) log.Tracer(ctx).Tracef("process: getting process for PID %d", pid)
p, err, _ := getProcessSingleInflight.Do(strconv.Itoa(pid), func() (interface{}, error) { // Get pid and created time for identification.
return loadProcess(ctx, pid) 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 { if err != nil {
return nil, err 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. return p.(*Process), nil // nolint:forcetypeassert // Can only be a *Process.
} }
func loadProcess(ctx context.Context, pid int) (*Process, error) { func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (*Process, error) {
switch pid { switch pInfo.Pid {
case UnidentifiedProcessID: case UnidentifiedProcessID:
return GetUnidentifiedProcess(ctx), nil return GetUnidentifiedProcess(ctx), nil
case UnsolicitedProcessID: case UnsolicitedProcessID:
@ -179,19 +196,24 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) {
return GetSystemProcess(ctx), nil 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 { if ok {
return process, nil return process, nil
} }
// Create new a process object. // Create new a process object.
process = &Process{ process = &Process{
Pid: pid, Pid: int(pInfo.Pid),
CreatedAt: createdAt,
FirstSeen: time.Now().Unix(), FirstSeen: time.Now().Unix(),
key: key,
} }
// Get process information from the system. // Get process information from the system.
pInfo, err := processInfo.NewProcessWithContext(ctx, int32(pid)) pInfo, err := processInfo.NewProcessWithContext(ctx, pInfo.Pid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -202,7 +224,7 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) {
var uids []int32 var uids []int32
uids, err = pInfo.UidsWithContext(ctx) uids, err = pInfo.UidsWithContext(ctx)
if err != nil { 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]) process.UserID = int(uids[0])
} }
@ -210,23 +232,30 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) {
// Username // Username
process.UserName, err = pInfo.UsernameWithContext(ctx) process.UserName, err = pInfo.UsernameWithContext(ctx)
if err != nil { 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 // TODO: User Home
// new.UserHome, err = // new.UserHome, err =
// PPID // Parent process id
ppid, err := pInfo.PpidWithContext(ctx) ppid, err := pInfo.PpidWithContext(ctx)
if err != nil { 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) process.ParentPid = int(ppid)
// Parent created at time
parentCreatedAt, err := pInfo.CreateTimeWithContext(ctx)
if err != nil {
return nil, err
}
process.ParentCreatedAt = parentCreatedAt
// Path // Path
process.Path, err = pInfo.ExeWithContext(ctx) process.Path, err = pInfo.ExeWithContext(ctx)
if err != nil { 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 // remove linux " (deleted)" suffix for deleted files
if onLinux { if onLinux {
@ -247,13 +276,13 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) {
// Command line arguments // Command line arguments
process.CmdLine, err = pInfo.CmdlineWithContext(ctx) process.CmdLine, err = pInfo.CmdlineWithContext(ctx)
if err != nil { 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 // Name
process.Name, err = pInfo.NameWithContext(ctx) process.Name, err = pInfo.NameWithContext(ctx)
if err != nil { 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 == "" { if process.Name == "" {
process.Name = process.ExecName process.Name = process.ExecName
@ -262,7 +291,7 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) {
// Get all environment variables // Get all environment variables
env, err := pInfo.EnvironWithContext(ctx) env, err := pInfo.EnvironWithContext(ctx)
if err != nil { 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. // Split env variables in key and value.
process.Env = make(map[string]string, len(env)) process.Env = make(map[string]string, len(env))
@ -283,6 +312,11 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) {
return process, nil 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. // MatchingData returns the matching data for the process.
func (p *Process) MatchingData() *MatchingData { func (p *Process) MatchingData() *MatchingData {
return &MatchingData{p} return &MatchingData{p}

View file

@ -2,7 +2,6 @@ package process
import ( import (
"context" "context"
"strconv"
"time" "time"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
@ -39,29 +38,35 @@ func init() {
var ( var (
// unidentifiedProcess is used for non-attributed outgoing connections. // unidentifiedProcess is used for non-attributed outgoing connections.
unidentifiedProcess = &Process{ unidentifiedProcess = &Process{
UserID: UnidentifiedProcessID, UserID: UnidentifiedProcessID,
UserName: "Unknown", UserName: "Unknown",
Pid: UnidentifiedProcessID, Pid: UnidentifiedProcessID,
ParentPid: UnidentifiedProcessID, CreatedAt: 1,
Name: profile.UnidentifiedProfileName, ParentPid: UnidentifiedProcessID,
ParentCreatedAt: 1,
Name: profile.UnidentifiedProfileName,
} }
// unsolicitedProcess is used for non-attributed incoming connections. // unsolicitedProcess is used for non-attributed incoming connections.
unsolicitedProcess = &Process{ unsolicitedProcess = &Process{
UserID: UnsolicitedProcessID, UserID: UnsolicitedProcessID,
UserName: "Unknown", UserName: "Unknown",
Pid: UnsolicitedProcessID, Pid: UnsolicitedProcessID,
ParentPid: UnsolicitedProcessID, CreatedAt: 1,
Name: profile.UnsolicitedProfileName, ParentPid: UnsolicitedProcessID,
ParentCreatedAt: 1,
Name: profile.UnsolicitedProfileName,
} }
// systemProcess is used to represent the Kernel. // systemProcess is used to represent the Kernel.
systemProcess = &Process{ systemProcess = &Process{
UserID: SystemProcessID, UserID: SystemProcessID,
UserName: "Kernel", UserName: "Kernel",
Pid: SystemProcessID, Pid: SystemProcessID,
ParentPid: SystemProcessID, CreatedAt: 1,
Name: profile.SystemProfileName, ParentPid: SystemProcessID,
ParentCreatedAt: 1,
Name: profile.SystemProfileName,
} }
getSpecialProcessSingleInflight singleflight.Group getSpecialProcessSingleInflight singleflight.Group
@ -83,9 +88,9 @@ func GetSystemProcess(ctx context.Context) *Process {
} }
func getSpecialProcess(ctx context.Context, template *Process) *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. // Check if we have already loaded the special process.
process, ok := GetProcessFromStorage(template.Pid) process, ok := GetProcessFromStorage(template.key)
if ok { if ok {
return process, nil return process, nil
} }