mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Add support for unidentified/system processes/profiles
This commit is contained in:
parent
10ee7fd7db
commit
033dceab5b
16 changed files with 243 additions and 67 deletions
|
@ -233,6 +233,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
|||
if ps.isMe {
|
||||
// approve
|
||||
conn.Accept("internally approved")
|
||||
conn.Hidden = true
|
||||
// finish
|
||||
conn.StopFirewallHandler()
|
||||
issueVerdict(conn, pkt, 0, true)
|
||||
|
|
|
@ -50,6 +50,7 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint:
|
|||
if conn.Process().Pid == os.Getpid() {
|
||||
log.Infof("filter: granting own connection %s", conn)
|
||||
conn.Verdict = network.VerdictAccept
|
||||
conn.Hidden = true
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -75,6 +76,7 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint:
|
|||
log.Warningf("filter: failed to find load local peer process with PID %d: %s", otherPid, err)
|
||||
} else if otherProcess.Pid == conn.Process().Pid {
|
||||
conn.Accept("connection to self")
|
||||
conn.Hidden = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,6 +199,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
|
|||
}
|
||||
}()
|
||||
|
||||
// TODO: this has been obsoleted due to special profiles
|
||||
if conn.Process().Profile() == nil {
|
||||
tracer.Infof("nameserver: failed to find process for request %s, returning NXDOMAIN", conn)
|
||||
returnNXDomain(w, query)
|
||||
|
|
|
@ -41,6 +41,7 @@ type Connection struct { //nolint:maligned // TODO: fix alignment
|
|||
VerdictPermanent bool
|
||||
Inspecting bool
|
||||
Encrypted bool // TODO
|
||||
Hidden bool
|
||||
|
||||
pktQueue chan packet.Packet
|
||||
firewallHandler FirewallHandler
|
||||
|
@ -58,7 +59,7 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, ip net.IP, po
|
|||
proc, err := process.GetProcessByEndpoints(ctx, ip, port, dnsAddress, dnsPort, packet.UDP)
|
||||
if err != nil {
|
||||
log.Warningf("network: failed to find process of dns request for %s: %s", fqdn, err)
|
||||
proc = process.UnknownProcess
|
||||
proc = process.GetUnidentifiedProcess(ctx)
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
|
@ -80,7 +81,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
proc, inbound, err := process.GetProcessByPacket(pkt)
|
||||
if err != nil {
|
||||
log.Warningf("network: failed to find process of packet %s: %s", pkt, err)
|
||||
proc = process.UnknownProcess
|
||||
proc = process.GetUnidentifiedProcess(pkt.Ctx())
|
||||
}
|
||||
|
||||
var scope string
|
||||
|
@ -270,7 +271,11 @@ func (conn *Connection) Save() {
|
|||
|
||||
// delete deletes a link from the storage and propagates the change. Nothing is locked - both the conns map and the connection itself require locking
|
||||
func (conn *Connection) delete() {
|
||||
delete(conns, conn.ID)
|
||||
if conn.ID == "" {
|
||||
delete(dnsConns, strconv.Itoa(conn.process.Pid)+"/"+conn.Scope)
|
||||
} else {
|
||||
delete(conns, conn.ID)
|
||||
}
|
||||
|
||||
conn.Meta().Delete()
|
||||
dbController.PushUpdate(conn)
|
||||
|
|
|
@ -23,7 +23,16 @@ func removeOpenDNSRequest(pid int, fqdn string) {
|
|||
defer openDNSRequestsLock.Unlock()
|
||||
|
||||
key := strconv.Itoa(pid) + "/" + fqdn
|
||||
delete(openDNSRequests, key)
|
||||
_, ok := openDNSRequests[key]
|
||||
if ok {
|
||||
delete(openDNSRequests, key)
|
||||
return
|
||||
}
|
||||
|
||||
// check if there is an open dns request from an unidentified process
|
||||
if pid >= 0 {
|
||||
delete(openDNSRequests, "-1/"+fqdn)
|
||||
}
|
||||
}
|
||||
|
||||
// SaveOpenDNSRequest saves a dns request connection that was allowed to proceed.
|
||||
|
|
|
@ -58,7 +58,7 @@ func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) {
|
|||
func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, err error) {
|
||||
if !enableProcessDetection() {
|
||||
log.Tracer(pkt.Ctx()).Tracef("process: process detection disabled")
|
||||
return UnknownProcess, direction, nil
|
||||
return GetUnidentifiedProcess(pkt.Ctx()), pkt.Info().Direction, nil
|
||||
}
|
||||
|
||||
log.Tracer(pkt.Ctx()).Tracef("process: getting process and profile by packet")
|
||||
|
@ -116,7 +116,7 @@ func GetPidByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remote
|
|||
func GetProcessByEndpoints(ctx context.Context, localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (process *Process, err error) {
|
||||
if !enableProcessDetection() {
|
||||
log.Tracer(ctx).Tracef("process: process detection disabled")
|
||||
return UnknownProcess, nil
|
||||
return GetUnidentifiedProcess(ctx), nil
|
||||
}
|
||||
|
||||
log.Tracer(ctx).Tracef("process: getting process and profile by endpoints")
|
||||
|
|
|
@ -75,11 +75,11 @@ func (p *Process) String() string {
|
|||
func GetOrFindPrimaryProcess(ctx context.Context, pid int) (*Process, error) {
|
||||
log.Tracer(ctx).Tracef("process: getting primary process for PID %d", pid)
|
||||
|
||||
if pid == -1 {
|
||||
return UnknownProcess, nil
|
||||
if pid <= -1 {
|
||||
return GetUnidentifiedProcess(ctx), nil
|
||||
}
|
||||
if pid == 0 {
|
||||
return OSProcess, nil
|
||||
return GetSystemProcess(ctx), nil
|
||||
}
|
||||
|
||||
process, err := loadProcess(ctx, pid)
|
||||
|
@ -88,8 +88,8 @@ func GetOrFindPrimaryProcess(ctx context.Context, pid int) (*Process, error) {
|
|||
}
|
||||
|
||||
for {
|
||||
if process.ParentPid == 0 {
|
||||
return OSProcess, nil
|
||||
if process.ParentPid <= 0 {
|
||||
return process, nil
|
||||
}
|
||||
parentProcess, err := loadProcess(ctx, process.ParentPid)
|
||||
if err != nil {
|
||||
|
@ -121,11 +121,11 @@ func GetOrFindPrimaryProcess(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)
|
||||
|
||||
if pid == -1 {
|
||||
return UnknownProcess, nil
|
||||
if pid <= -1 {
|
||||
return GetUnidentifiedProcess(ctx), nil
|
||||
}
|
||||
if pid == 0 {
|
||||
return OSProcess, nil
|
||||
return GetSystemProcess(ctx), nil
|
||||
}
|
||||
|
||||
p, err := loadProcess(ctx, pid)
|
||||
|
@ -184,11 +184,11 @@ func deduplicateRequest(ctx context.Context, pid int) (finishRequest func()) {
|
|||
}
|
||||
|
||||
func loadProcess(ctx context.Context, pid int) (*Process, error) {
|
||||
if pid == -1 {
|
||||
return UnknownProcess, nil
|
||||
if pid <= -1 {
|
||||
return GetUnidentifiedProcess(ctx), nil
|
||||
}
|
||||
if pid == 0 {
|
||||
return OSProcess, nil
|
||||
return GetSystemProcess(ctx), nil
|
||||
}
|
||||
|
||||
process, ok := GetProcessFromStorage(pid)
|
||||
|
|
|
@ -15,6 +15,8 @@ func (p *Process) GetProfile(ctx context.Context) error {
|
|||
// only find profiles if not already done.
|
||||
if p.profile != nil {
|
||||
log.Tracer(ctx).Trace("process: profile already loaded")
|
||||
// mark profile as used
|
||||
p.profile.MarkUsed()
|
||||
return nil
|
||||
}
|
||||
log.Tracer(ctx).Trace("process: loading profile")
|
||||
|
@ -29,10 +31,8 @@ func (p *Process) GetProfile(ctx context.Context) error {
|
|||
localProfile.Name = p.ExecName
|
||||
}
|
||||
|
||||
// mark as used and save
|
||||
if localProfile.MarkUsed() {
|
||||
_ = localProfile.Save()
|
||||
}
|
||||
// mark profile as used
|
||||
localProfile.MarkUsed()
|
||||
|
||||
p.LocalProfileKey = localProfile.Key()
|
||||
p.profile = profile.NewLayeredProfile(localProfile)
|
||||
|
|
67
process/special.go
Normal file
67
process/special.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
var (
|
||||
// unidentifiedProcess is used when a process cannot be found.
|
||||
unidentifiedProcess = &Process{
|
||||
UserID: -1,
|
||||
UserName: "Unknown",
|
||||
Pid: -1,
|
||||
ParentPid: -1,
|
||||
Name: "Unidentified Processes",
|
||||
}
|
||||
|
||||
// systemProcess is used to represent the Kernel.
|
||||
systemProcess = &Process{
|
||||
UserID: 0,
|
||||
UserName: "Kernel",
|
||||
Pid: 0,
|
||||
ParentPid: 0,
|
||||
Name: "Operating System",
|
||||
}
|
||||
)
|
||||
|
||||
func GetUnidentifiedProcess(ctx context.Context) *Process {
|
||||
return getSpecialProcess(ctx, unidentifiedProcess, profile.GetUnidentifiedProfile)
|
||||
}
|
||||
|
||||
func GetSystemProcess(ctx context.Context) *Process {
|
||||
return getSpecialProcess(ctx, systemProcess, profile.GetSystemProfile)
|
||||
}
|
||||
|
||||
func getSpecialProcess(ctx context.Context, p *Process, getProfile func() *profile.Profile) *Process {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if p.FirstSeen == 0 {
|
||||
p.FirstSeen = time.Now().Unix()
|
||||
}
|
||||
|
||||
// only find profiles if not already done.
|
||||
if p.profile != nil {
|
||||
log.Tracer(ctx).Trace("process: special profile already loaded")
|
||||
// mark profile as used
|
||||
p.profile.MarkUsed()
|
||||
return p
|
||||
}
|
||||
log.Tracer(ctx).Trace("process: loading special profile")
|
||||
|
||||
// get profile
|
||||
localProfile := getProfile()
|
||||
|
||||
// mark profile as used
|
||||
localProfile.MarkUsed()
|
||||
|
||||
p.LocalProfileKey = localProfile.Key()
|
||||
p.profile = profile.NewLayeredProfile(localProfile)
|
||||
|
||||
go p.Save()
|
||||
return p
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package process
|
||||
|
||||
var (
|
||||
// UnknownProcess is used when a process cannot be found.
|
||||
UnknownProcess = &Process{
|
||||
UserID: -1,
|
||||
UserName: "Unknown",
|
||||
Pid: -1,
|
||||
ParentPid: -1,
|
||||
Name: "Unknown Processes",
|
||||
}
|
||||
|
||||
// OSProcess is used to represent the Kernel.
|
||||
OSProcess = &Process{
|
||||
UserID: 0,
|
||||
UserName: "Kernel",
|
||||
Pid: 0,
|
||||
ParentPid: 0,
|
||||
Name: "Operating System",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
UnknownProcess.Save()
|
||||
OSProcess.Save()
|
||||
}
|
|
@ -1,7 +1,14 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
activeProfileCleanerTickDuration = 10 * time.Minute
|
||||
activeProfileCleanerThreshold = 1 * time.Hour
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -38,7 +45,34 @@ func markActiveProfileAsOutdated(scopedID string) {
|
|||
|
||||
profile, ok := activeProfiles[scopedID]
|
||||
if ok {
|
||||
profile.oudated.Set()
|
||||
profile.outdated.Set()
|
||||
delete(activeProfiles, scopedID)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanActiveProfiles(ctx context.Context) error { //nolint:param // need to conform to interface
|
||||
for {
|
||||
select {
|
||||
case <-time.After(activeProfileCleanerTickDuration):
|
||||
|
||||
threshold := time.Now().Add(-activeProfileCleanerThreshold)
|
||||
|
||||
activeProfilesLock.Lock()
|
||||
for id, profile := range activeProfiles {
|
||||
// get last used
|
||||
profile.Lock()
|
||||
lastUsed := profile.lastUsed
|
||||
profile.Unlock()
|
||||
// remove if not used for a while
|
||||
if lastUsed.Before(threshold) {
|
||||
profile.outdated.Set()
|
||||
delete(activeProfiles, id)
|
||||
}
|
||||
}
|
||||
activeProfilesLock.Unlock()
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,8 +70,8 @@ func updateGlobalConfigProfile(ctx context.Context, data interface{}) error {
|
|||
|
||||
// build global profile for reference
|
||||
profile := &Profile{
|
||||
ID: "config",
|
||||
Source: SourceGlobal,
|
||||
ID: "global-config",
|
||||
Source: SourceSpecial,
|
||||
Name: "Global Configuration",
|
||||
Config: make(map[string]interface{}),
|
||||
internalSave: true,
|
||||
|
|
|
@ -42,6 +42,8 @@ func start() error {
|
|||
return err
|
||||
}
|
||||
|
||||
module.StartServiceWorker("clean active profiles", 0, cleanActiveProfiles)
|
||||
|
||||
err = updateGlobalConfigProfile(module.Ctx, nil)
|
||||
if err != nil {
|
||||
log.Warningf("profile: error during loading global profile from configuration: %s", err)
|
||||
|
|
|
@ -123,7 +123,7 @@ func (lp *LayeredProfile) Update() (revisionCounter uint64) {
|
|||
|
||||
var changed bool
|
||||
for i, layer := range lp.layers {
|
||||
if layer.oudated.IsSet() {
|
||||
if layer.outdated.IsSet() {
|
||||
changed = true
|
||||
// update layer
|
||||
newLayer, err := GetProfile(layer.Source, layer.ID)
|
||||
|
@ -170,6 +170,11 @@ func (lp *LayeredProfile) updateCaches() {
|
|||
// TODO: ignore community profiles
|
||||
}
|
||||
|
||||
// MarkUsed marks the localProfile as used.
|
||||
func (lp *LayeredProfile) MarkUsed() {
|
||||
lp.localProfile.MarkUsed()
|
||||
}
|
||||
|
||||
// SecurityLevel returns the highest security level of all layered profiles.
|
||||
func (lp *LayeredProfile) SecurityLevel() uint8 {
|
||||
return uint8(atomic.LoadUint32(lp.securityLevel))
|
||||
|
|
|
@ -19,15 +19,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
lastUsedUpdateThreshold = 1 * time.Hour
|
||||
lastUsedUpdateThreshold = 24 * time.Hour
|
||||
)
|
||||
|
||||
// Profile Sources
|
||||
const (
|
||||
SourceLocal string = "local"
|
||||
SourceLocal string = "local" // local, editable
|
||||
SourceSpecial string = "special" // specials (read-only)
|
||||
SourceCommunity string = "community"
|
||||
SourceEnterprise string = "enterprise"
|
||||
SourceGlobal string = "global"
|
||||
)
|
||||
|
||||
// Default Action IDs
|
||||
|
@ -77,7 +77,8 @@ type Profile struct { //nolint:maligned // not worth the effort
|
|||
filterListIDs []string
|
||||
|
||||
// Lifecycle Management
|
||||
oudated *abool.AtomicBool
|
||||
outdated *abool.AtomicBool
|
||||
lastUsed time.Time
|
||||
|
||||
// Framework
|
||||
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process/actor must be found
|
||||
|
@ -94,7 +95,7 @@ type Profile struct { //nolint:maligned // not worth the effort
|
|||
func (profile *Profile) prepConfig() (err error) {
|
||||
// prepare configuration
|
||||
profile.configPerspective, err = config.NewPerspective(profile.Config)
|
||||
profile.oudated = abool.New()
|
||||
profile.outdated = abool.New()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -156,10 +157,11 @@ func (profile *Profile) parseConfig() error {
|
|||
// New returns a new Profile.
|
||||
func New() *Profile {
|
||||
profile := &Profile{
|
||||
ID: uuid.NewV4().String(),
|
||||
Source: SourceLocal,
|
||||
Created: time.Now().Unix(),
|
||||
Config: make(map[string]interface{}),
|
||||
ID: uuid.NewV4().String(),
|
||||
Source: SourceLocal,
|
||||
Created: time.Now().Unix(),
|
||||
Config: make(map[string]interface{}),
|
||||
internalSave: true,
|
||||
}
|
||||
|
||||
// create placeholders
|
||||
|
@ -190,13 +192,26 @@ func (profile *Profile) Save() error {
|
|||
return profileDB.Put(profile)
|
||||
}
|
||||
|
||||
// MarkUsed marks the profile as used, eventually.
|
||||
func (profile *Profile) MarkUsed() (updated bool) {
|
||||
// MarkUsed marks the profile as used and saves it when it has changed.
|
||||
func (profile *Profile) MarkUsed() {
|
||||
profile.Lock()
|
||||
// lastUsed
|
||||
profile.lastUsed = time.Now()
|
||||
|
||||
// ApproxLastUsed
|
||||
save := false
|
||||
if time.Now().Add(-lastUsedUpdateThreshold).Unix() > profile.ApproxLastUsed {
|
||||
profile.ApproxLastUsed = time.Now().Unix()
|
||||
return true
|
||||
save = true
|
||||
}
|
||||
profile.Unlock()
|
||||
|
||||
if save {
|
||||
err := profile.Save()
|
||||
if err != nil {
|
||||
log.Warningf("profiles: failed to save profile %s after marking as used: %s", profile.ScopedID(), err)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String returns a string representation of the Profile.
|
||||
|
@ -224,8 +239,6 @@ func (profile *Profile) addEndpointyEntry(cfgKey, newEntry string) {
|
|||
endpointList = append(endpointList, newEntry)
|
||||
profile.Config[cfgKey] = endpointList
|
||||
|
||||
// save without full reload
|
||||
profile.internalSave = true
|
||||
profile.Unlock()
|
||||
err := profile.Save()
|
||||
if err != nil {
|
||||
|
@ -233,10 +246,13 @@ func (profile *Profile) addEndpointyEntry(cfgKey, newEntry string) {
|
|||
}
|
||||
|
||||
// reload manually
|
||||
profile.Lock()
|
||||
profile.dataParsed = false
|
||||
err = profile.parseConfig()
|
||||
if err != nil {
|
||||
log.Warningf("profile: failed to parse profile config after adding endpoint: %s", err)
|
||||
}
|
||||
profile.Unlock()
|
||||
}
|
||||
|
||||
// GetProfile loads a profile from the database.
|
||||
|
@ -249,6 +265,7 @@ func GetProfileByScopedID(scopedID string) (*Profile, error) {
|
|||
// check cache
|
||||
profile := getActiveProfile(scopedID)
|
||||
if profile != nil {
|
||||
profile.MarkUsed()
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
|
@ -266,7 +283,6 @@ func GetProfileByScopedID(scopedID string) (*Profile, error) {
|
|||
|
||||
// lock for prepping
|
||||
profile.Lock()
|
||||
defer profile.Unlock()
|
||||
|
||||
// prepare config
|
||||
err = profile.prepConfig()
|
||||
|
@ -280,7 +296,13 @@ func GetProfileByScopedID(scopedID string) (*Profile, error) {
|
|||
log.Warningf("profiles: profile %s has (partly) invalid configuration: %s", profile.ID, err)
|
||||
}
|
||||
|
||||
// mark as internal
|
||||
profile.internalSave = true
|
||||
|
||||
profile.Unlock()
|
||||
|
||||
// mark active
|
||||
profile.MarkUsed()
|
||||
markProfileActive(profile)
|
||||
|
||||
return profile, nil
|
||||
|
|
54
profile/special.go
Normal file
54
profile/special.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
const (
|
||||
unidentifiedProfileID = "_unidentified"
|
||||
systemProfileID = "_system"
|
||||
)
|
||||
|
||||
func GetUnidentifiedProfile() *Profile {
|
||||
// get profile
|
||||
profile, err := GetProfile(SourceLocal, unidentifiedProfileID)
|
||||
if err == nil {
|
||||
return profile
|
||||
}
|
||||
|
||||
// create if not available (or error)
|
||||
profile = New()
|
||||
profile.Name = "Unidentified Processes"
|
||||
profile.Source = SourceLocal
|
||||
profile.ID = unidentifiedProfileID
|
||||
|
||||
// save to db
|
||||
err = profile.Save()
|
||||
if err != nil {
|
||||
log.Warningf("profiles: failed to save %s: %s", profile.ScopedID(), err)
|
||||
}
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
func GetSystemProfile() *Profile {
|
||||
// get profile
|
||||
profile, err := GetProfile(SourceLocal, systemProfileID)
|
||||
if err == nil {
|
||||
return profile
|
||||
}
|
||||
|
||||
// create if not available (or error)
|
||||
profile = New()
|
||||
profile.Name = "Operating System"
|
||||
profile.Source = SourceLocal
|
||||
profile.ID = systemProfileID
|
||||
|
||||
// save to db
|
||||
err = profile.Save()
|
||||
if err != nil {
|
||||
log.Warningf("profiles: failed to save %s: %s", profile.ScopedID(), err)
|
||||
}
|
||||
|
||||
return profile
|
||||
}
|
Loading…
Add table
Reference in a new issue