mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Improve getting process group leader
This commit is contained in:
parent
30fee07a89
commit
425a0bed4c
12 changed files with 217 additions and 156 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -15,8 +15,9 @@ import (
|
|||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/safing/portbase/log"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" bpf ../programs/exec.c
|
||||
|
@ -67,6 +68,8 @@ type Event struct {
|
|||
Comm string `json:"comm"`
|
||||
}
|
||||
|
||||
// Tracer is the exec tracer itself.
|
||||
// It must be closed after use.
|
||||
type Tracer struct {
|
||||
objs bpfObjects
|
||||
tp link.Link
|
||||
|
|
|
@ -1,81 +1,45 @@
|
|||
package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
func registerAPIEndpoints() error {
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Get Process Tag Metadata",
|
||||
Description: "Get information about process tags.",
|
||||
Path: "process/tags",
|
||||
Read: api.PermitUser,
|
||||
BelongsTo: module,
|
||||
StructFunc: handleProcessTagMetadata,
|
||||
Name: "Get Process Tag Metadata",
|
||||
Description: "Get information about process tags.",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "process/by-profile",
|
||||
Parameters: []api.Parameter{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Field: "scopedId",
|
||||
Value: "",
|
||||
Description: "The ID of the profile",
|
||||
},
|
||||
},
|
||||
Read: api.PermitUser,
|
||||
BelongsTo: module,
|
||||
StructFunc: api.StructFunc(func(ar *api.Request) (any, error) {
|
||||
id := ar.URL.Query().Get("scopedId")
|
||||
|
||||
if id == "" {
|
||||
return nil, api.ErrorWithStatus(fmt.Errorf("missing profile id"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
result := FindProcessesByProfile(ar.Context(), id)
|
||||
|
||||
return result, nil
|
||||
}),
|
||||
Description: "Get all running processes for a given profile",
|
||||
Name: "Get Processes by Profile",
|
||||
Description: "Get all recently active processes using the given profile",
|
||||
Path: "process/list/by-profile/{source:[a-z]+}/{id:[A-z0-9-]+}",
|
||||
Read: api.PermitUser,
|
||||
BelongsTo: module,
|
||||
StructFunc: handleGetProcessesByProfile,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "process/by-pid/{pid:[0-9]+}",
|
||||
Parameters: []api.Parameter{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Field: "pid",
|
||||
Value: "",
|
||||
Description: "A PID of a process inside the requested process group",
|
||||
},
|
||||
},
|
||||
Read: api.PermitUser,
|
||||
BelongsTo: module,
|
||||
StructFunc: api.StructFunc(func(ar *api.Request) (i interface{}, err error) {
|
||||
pid, err := strconv.ParseInt(ar.URLVars["pid"], 10, 0)
|
||||
if err != nil {
|
||||
return nil, api.ErrorWithStatus(err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
process, err := GetProcessGroupLeader(ar.Context(), int(pid))
|
||||
if err != nil {
|
||||
return nil, api.ErrorWithStatus(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return process, nil
|
||||
}),
|
||||
Description: "Load a process group leader by a child PID",
|
||||
Name: "Get Process Group Leader By PID",
|
||||
Description: "Load a process group leader by a child PID",
|
||||
Path: "process/group-leader/{pid:[0-9]+}",
|
||||
Read: api.PermitUser,
|
||||
BelongsTo: module,
|
||||
StructFunc: handleGetProcessGroupLeader,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -101,3 +65,35 @@ func handleProcessTagMetadata(ar *api.Request) (i interface{}, err error) {
|
|||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func handleGetProcessesByProfile(ar *api.Request) (any, error) {
|
||||
source := ar.URLVars["source"]
|
||||
id := ar.URLVars["id"]
|
||||
if id == "" || source == "" {
|
||||
return nil, api.ErrorWithStatus(fmt.Errorf("missing profile source/id"), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
result := GetProcessesWithProfile(ar.Context(), profile.ProfileSource(source), id, true)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func handleGetProcessGroupLeader(ar *api.Request) (any, error) {
|
||||
pid, err := strconv.ParseInt(ar.URLVars["pid"], 10, 0)
|
||||
if err != nil {
|
||||
return nil, api.ErrorWithStatus(err, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
process, err := GetOrFindProcess(ar.Context(), int(pid))
|
||||
if err != nil {
|
||||
return nil, api.ErrorWithStatus(err, http.StatusInternalServerError)
|
||||
}
|
||||
err = process.FindProcessGroupLeader(ar.Context())
|
||||
switch {
|
||||
case process.Leader() != nil:
|
||||
return process.Leader(), nil
|
||||
case err != nil:
|
||||
return nil, api.ErrorWithStatus(err, http.StatusInternalServerError)
|
||||
default:
|
||||
return nil, api.ErrorWithStatus(errors.New("leader not found"), http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,17 @@ package process
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
processInfo "github.com/shirou/gopsutil/process"
|
||||
"github.com/tevino/abool"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
const processDatabaseNamespace = "network:tree"
|
||||
|
@ -48,37 +50,33 @@ func All() map[int]*Process {
|
|||
return all
|
||||
}
|
||||
|
||||
func FindProcessesByProfile(ctx context.Context, scopedID string) []*Process {
|
||||
all := All()
|
||||
// GetProcessesWithProfile returns all processes that use the given profile.
|
||||
// If preferProcessGroupLeader is set, it returns the process group leader instead, if available.
|
||||
func GetProcessesWithProfile(ctx context.Context, profileSource profile.ProfileSource, profileID string, preferProcessGroupLeader bool) []*Process {
|
||||
log.Tracer(ctx).Debugf("process: searching for processes belonging to %s", profile.MakeScopedID(profileSource, profileID))
|
||||
|
||||
pids := make([]int, 0, len(all))
|
||||
|
||||
log.Infof("[DEBUG] searchin processes belonging to %s", scopedID)
|
||||
|
||||
for _, p := range all {
|
||||
p.Lock()
|
||||
if p.profile != nil && p.profile.LocalProfile().ScopedID() == scopedID {
|
||||
pids = append(pids, p.Pid)
|
||||
// Get all processes that match the given profile.
|
||||
procs := make([]*Process, 0, 8)
|
||||
for _, p := range All() {
|
||||
lp := p.profile.LocalProfile()
|
||||
if lp != nil && lp.Source == profileSource && lp.ID == profileID {
|
||||
if preferProcessGroupLeader && p.Leader() != nil {
|
||||
procs = append(procs, p.Leader())
|
||||
} else {
|
||||
procs = append(procs, p)
|
||||
}
|
||||
}
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
m := make(map[int]*Process)
|
||||
// Sort and compact.
|
||||
slices.SortFunc[[]*Process, *Process](procs, func(a, b *Process) int {
|
||||
return strings.Compare(a.processKey, b.processKey)
|
||||
})
|
||||
slices.CompactFunc[[]*Process, *Process](procs, func(a, b *Process) bool {
|
||||
return a.processKey == b.processKey
|
||||
})
|
||||
|
||||
for _, pid := range pids {
|
||||
if _, ok := m[pid]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
process, err := GetProcessGroupLeader(ctx, pid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
m[process.Pid] = process
|
||||
}
|
||||
|
||||
return maps.Values(m)
|
||||
return procs
|
||||
}
|
||||
|
||||
// Save saves the process to the internal state and pushes an update.
|
||||
|
|
|
@ -29,6 +29,13 @@ func GetProcessWithProfile(ctx context.Context, pid int) (process *Process, err
|
|||
return GetUnidentifiedProcess(ctx), err
|
||||
}
|
||||
|
||||
// Get process group leader, which is the process "nearest" to the user and
|
||||
// will have more/better information for finding names ans icons, for example.
|
||||
err = process.FindProcessGroupLeader(ctx)
|
||||
if err != nil {
|
||||
log.Warningf("process: failed to get process group leader for %s: %s", process, err)
|
||||
}
|
||||
|
||||
changed, err := process.GetProfile(ctx)
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Errorf("process: failed to get profile for process %s: %s", process, err)
|
||||
|
|
|
@ -30,21 +30,26 @@ type Process struct {
|
|||
// Process attributes.
|
||||
// Don't change; safe for concurrent access.
|
||||
|
||||
Name string
|
||||
UserID int
|
||||
UserName string
|
||||
UserHome string
|
||||
Pid int
|
||||
Pgid int // linux only
|
||||
CreatedAt int64
|
||||
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
|
||||
|
||||
LeaderPid int
|
||||
leader *Process
|
||||
|
||||
Path string
|
||||
ExecName string
|
||||
Cwd string
|
||||
CmdLine string
|
||||
FirstArg string
|
||||
Env map[string]string
|
||||
|
||||
// unique process identifier ("Pid-CreatedAt")
|
||||
processKey string
|
||||
|
@ -92,6 +97,16 @@ func (p *Process) Profile() *profile.LayeredProfile {
|
|||
return p.profile
|
||||
}
|
||||
|
||||
// Leader returns the process group leader that is attached to the process.
|
||||
// This will not trigger a new search for the process group leader, it only
|
||||
// returns existing data.
|
||||
func (p *Process) Leader() *Process {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
return p.leader
|
||||
}
|
||||
|
||||
// IsIdentified returns whether the process has been identified or if it
|
||||
// represents some kind of unidentified process.
|
||||
func (p *Process) IsIdentified() bool {
|
||||
|
@ -213,12 +228,9 @@ func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (*
|
|||
return process, nil
|
||||
}
|
||||
|
||||
pgid, _ := GetProcessGroupID(ctx, int(pInfo.Pid))
|
||||
|
||||
// Create new a process object.
|
||||
process = &Process{
|
||||
Pid: int(pInfo.Pid),
|
||||
Pgid: pgid,
|
||||
FirstSeen: time.Now().Unix(),
|
||||
processKey: key,
|
||||
}
|
||||
|
@ -250,7 +262,7 @@ func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (*
|
|||
// TODO: User Home
|
||||
// new.UserHome, err =
|
||||
|
||||
// Parent process id
|
||||
// Parent process ID
|
||||
ppid, err := pInfo.PpidWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get PPID for p%d: %w", pInfo.Pid, err)
|
||||
|
@ -267,6 +279,19 @@ func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (*
|
|||
process.ParentCreatedAt = parentCreatedAt
|
||||
}
|
||||
|
||||
// Leader process ID
|
||||
// Get process group ID to find group leader, which is the process "nearest"
|
||||
// to the user and will have more/better information for finding names and
|
||||
// icons, for example.
|
||||
leaderPid, err := GetProcessGroupID(ctx, process.Pid)
|
||||
if err != nil {
|
||||
// Fail gracefully.
|
||||
log.Warningf("process: failed to get process group ID for p%d: %s", process.Pid, err)
|
||||
process.LeaderPid = UndefinedProcessID
|
||||
} else {
|
||||
process.LeaderPid = leaderPid
|
||||
}
|
||||
|
||||
// Path
|
||||
process.Path, err = pInfo.ExeWithContext(ctx)
|
||||
if err != nil {
|
||||
|
|
|
@ -10,11 +10,14 @@ import (
|
|||
// SystemProcessID is the PID of the System/Kernel itself.
|
||||
const SystemProcessID = 0
|
||||
|
||||
func GetProcessGroupLeader(ctx context.Context, pid int) (*Process, error) {
|
||||
// On systems other than linux we just return the process with PID == pid
|
||||
return GetOrFindProcess(ctx, pid)
|
||||
// GetProcessGroupLeader returns the process that leads the process group.
|
||||
// Returns nil on unsupported platforms.
|
||||
func (p *Process) FindProcessGroupLeader(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProcessGroupID returns the process group ID of the given PID.
|
||||
// Returns undefined process ID on unsupported platforms.
|
||||
func GetProcessGroupID(ctx context.Context, pid int) (int, error) {
|
||||
return 0
|
||||
return UndefinedProcessID, nil
|
||||
}
|
||||
|
|
|
@ -2,83 +2,95 @@ package process
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
// SystemProcessID is the PID of the System/Kernel itself.
|
||||
const SystemProcessID = 0
|
||||
const (
|
||||
// SystemProcessID is the PID of the System/Kernel itself.
|
||||
SystemProcessID = 0
|
||||
|
||||
func GetProcessGroupLeader(ctx context.Context, pid int) (*Process, error) {
|
||||
pgid, err := GetProcessGroupID(ctx, pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// SystemInitID is the PID of the system init process.
|
||||
SystemInitID = 1
|
||||
)
|
||||
|
||||
// FindProcessGroupLeader returns the process that leads the process group.
|
||||
// Returns nil when process ID is not valid (or virtual).
|
||||
// If the process group leader is found, it is set on the process.
|
||||
// If that process does not exist anymore, then the highest existing parent process is returned.
|
||||
// If an error occurs, the best match is set.
|
||||
func (p *Process) FindProcessGroupLeader(ctx context.Context) error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
// Return the leader if we already have it.
|
||||
if p.leader != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
leader, err := GetOrFindProcess(ctx, pgid)
|
||||
// Check if we have the process group leader PID.
|
||||
if p.LeaderPid == UndefinedProcessID {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return nil if we already are the leader.
|
||||
if p.LeaderPid == p.Pid {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get process leader process.
|
||||
leader, err := GetOrFindProcess(ctx, p.LeaderPid)
|
||||
if err == nil {
|
||||
log.Infof("[DBUG] found leader pid=%d pgid=%d", leader.Pid, leader.Pgid)
|
||||
return leader, nil
|
||||
}
|
||||
|
||||
// this seems like a orphan process group so find the outermost parent
|
||||
// i.e. the first process in the group
|
||||
iter, err := GetOrFindProcess(ctx, pid)
|
||||
if err != nil {
|
||||
log.Infof("[DBUG] failed to get process for pid %d", pid)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is already the leader
|
||||
if iter.Pid == pgid {
|
||||
log.Infof("[DBUG] iter pid=%d pgid=%d is already leader", pid, pgid)
|
||||
return iter, nil
|
||||
p.leader = leader
|
||||
log.Tracer(ctx).Debugf("process: found process leader of %d: pid=%d pgid=%d", p.Pid, leader.Pid, leader.LeaderPid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we can't get the process leader process, it has likely already exited.
|
||||
// In that case, find the highest existing parent process within the process group.
|
||||
var (
|
||||
nextParentPid = p.ParentPid
|
||||
lastParent *Process
|
||||
)
|
||||
for {
|
||||
next, err := GetOrFindProcess(ctx, iter.ParentPid)
|
||||
// Get next parent.
|
||||
parent, err := GetOrFindProcess(ctx, nextParentPid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
p.leader = lastParent
|
||||
return fmt.Errorf("failed to find parent %d: %w", nextParentPid, err)
|
||||
}
|
||||
|
||||
// If the parent process group ID of does not match
|
||||
// the pgid than iter is the first child of the process
|
||||
// group
|
||||
if next.Pgid != pgid {
|
||||
return iter, nil
|
||||
// Check if we are ready to return.
|
||||
switch {
|
||||
case parent.Pid == p.LeaderPid:
|
||||
// Found the process group leader!
|
||||
p.leader = parent
|
||||
return nil
|
||||
|
||||
case parent.LeaderPid != p.LeaderPid:
|
||||
// We are leaving the process group. Return the previous parent.
|
||||
p.leader = lastParent
|
||||
log.Tracer(ctx).Debugf("process: found process leader (highest parent) of %d: pid=%d pgid=%d", p.Pid, parent.Pid, parent.LeaderPid)
|
||||
return nil
|
||||
|
||||
case parent.ParentPid == SystemProcessID,
|
||||
parent.ParentPid == SystemInitID:
|
||||
// Next parent is system or init.
|
||||
// Use current parent.
|
||||
p.leader = parent
|
||||
log.Tracer(ctx).Debugf("process: found process leader (highest parent) of %d: pid=%d pgid=%d", p.Pid, parent.Pid, parent.LeaderPid)
|
||||
return nil
|
||||
}
|
||||
|
||||
iter = next
|
||||
// Check next parent.
|
||||
lastParent = parent
|
||||
nextParentPid = parent.ParentPid
|
||||
}
|
||||
}
|
||||
|
||||
// GetProcessGroupID returns the process group ID of the given PID.
|
||||
func GetProcessGroupID(ctx context.Context, pid int) (int, error) {
|
||||
return syscall.Getpgid(pid)
|
||||
}
|
||||
|
||||
/*
|
||||
func init() {
|
||||
tracer, err := ebpf.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
file, _ := os.Create("/tmp/tracer.json")
|
||||
enc := json.NewEncoder(file)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
defer tracer.Close()
|
||||
for {
|
||||
evt, err := tracer.Read()
|
||||
if err != nil {
|
||||
log.Errorf("failed to read from execve tracer: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = enc.Encode(evt)
|
||||
}
|
||||
}()
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// SystemProcessID is the PID of the System/Kernel itself.
|
||||
const SystemProcessID = 4
|
||||
|
||||
// GetProcessGroupLeader returns the process that leads the process group.
|
||||
// Returns nil on Windows, as it does not have process groups.
|
||||
func (p *Process) FindProcessGroupLeader(ctx context.Context) error {
|
||||
// TODO: Get "main" process of process job object.
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProcessGroupID returns the process group ID of the given PID.
|
||||
// Returns the undefined process ID on Windows, as it does not have process groups.
|
||||
func GetProcessGroupID(ctx context.Context, pid int) (int, error) {
|
||||
return UndefinedProcessID, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue