Merge pull request #39 from safing/feature/unidentified-process-and-profile

Add support for unidentified processes and profiles
This commit is contained in:
Patrick Pacher 2020-04-21 10:15:11 +02:00 committed by GitHub
commit 1f90c05654
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 336 additions and 140 deletions

View file

@ -18,7 +18,7 @@ var (
) )
func init() { func init() {
modules.Register("base", nil, registerDatabases, nil, "database", "config", "random") modules.Register("base", nil, registerDatabases, nil, "database", "config", "rng")
module = modules.Register("core", nil, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui") module = modules.Register("core", nil, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui")
subsystems.Register( subsystems.Register(

View file

@ -233,6 +233,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
if ps.isMe { if ps.isMe {
// approve // approve
conn.Accept("internally approved") conn.Accept("internally approved")
conn.Internal = true
// finish // finish
conn.StopFirewallHandler() conn.StopFirewallHandler()
issueVerdict(conn, pkt, 0, true) issueVerdict(conn, pkt, 0, true)

View file

@ -50,6 +50,7 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint:
if conn.Process().Pid == os.Getpid() { if conn.Process().Pid == os.Getpid() {
log.Infof("filter: granting own connection %s", conn) log.Infof("filter: granting own connection %s", conn)
conn.Verdict = network.VerdictAccept conn.Verdict = network.VerdictAccept
conn.Internal = true
return 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) log.Warningf("filter: failed to find load local peer process with PID %d: %s", otherPid, err)
} else if otherProcess.Pid == conn.Process().Pid { } else if otherProcess.Pid == conn.Process().Pid {
conn.Accept("connection to self") conn.Accept("connection to self")
conn.Internal = true
return return
} }
} }

View file

@ -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 { if conn.Process().Profile() == nil {
tracer.Infof("nameserver: failed to find process for request %s, returning NXDOMAIN", conn) tracer.Infof("nameserver: failed to find process for request %s, returning NXDOMAIN", conn)
returnNXDomain(w, query) returnNXDomain(w, query)

View file

@ -57,8 +57,7 @@ func cleanConnections() (activePIDs map[int]struct{}) {
// Step 2: mark end // Step 2: mark end
activePIDs[conn.process.Pid] = struct{}{} activePIDs[conn.process.Pid] = struct{}{}
conn.Ended = now conn.Ended = now
// "save" conn.Save()
dbController.PushUpdate(conn)
} }
case conn.Ended < deleteOlderThan: case conn.Ended < deleteOlderThan:
// Step 3: delete // Step 3: delete

View file

@ -41,6 +41,7 @@ type Connection struct { //nolint:maligned // TODO: fix alignment
VerdictPermanent bool VerdictPermanent bool
Inspecting bool Inspecting bool
Encrypted bool // TODO Encrypted bool // TODO
Internal bool // Portmaster internal connections are marked in order to easily filter these out in the UI
pktQueue chan packet.Packet pktQueue chan packet.Packet
firewallHandler FirewallHandler 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) proc, err := process.GetProcessByEndpoints(ctx, ip, port, dnsAddress, dnsPort, packet.UDP)
if err != nil { if err != nil {
log.Warningf("network: failed to find process of dns request for %s: %s", fqdn, err) 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() timestamp := time.Now().Unix()
@ -80,7 +81,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
proc, inbound, err := process.GetProcessByPacket(pkt) proc, inbound, err := process.GetProcessByPacket(pkt)
if err != nil { if err != nil {
log.Warningf("network: failed to find process of packet %s: %s", pkt, err) log.Warningf("network: failed to find process of packet %s: %s", pkt, err)
proc = process.UnknownProcess proc = process.GetUnidentifiedProcess(pkt.Ctx())
} }
var scope string var scope string
@ -229,39 +230,31 @@ func (conn *Connection) SaveWhenFinished() {
// Save saves the connection in the storage and propagates the change through the database system. // Save saves the connection in the storage and propagates the change through the database system.
func (conn *Connection) Save() { func (conn *Connection) Save() {
if conn.ID == "" { conn.UpdateMeta()
// dns request if !conn.KeyIsSet() {
if !conn.KeyIsSet() { if conn.ID == "" {
// dns request
// set key
conn.SetKey(fmt.Sprintf("network:tree/%d/%s", conn.process.Pid, conn.Scope)) conn.SetKey(fmt.Sprintf("network:tree/%d/%s", conn.process.Pid, conn.Scope))
conn.UpdateMeta() mapKey := strconv.Itoa(conn.process.Pid) + "/" + conn.Scope
}
// save to internal state // save
// check if it already exists dnsConnsLock.Lock()
mapKey := strconv.Itoa(conn.process.Pid) + "/" + conn.Scope
dnsConnsLock.Lock()
_, ok := dnsConns[mapKey]
if !ok {
dnsConns[mapKey] = conn dnsConns[mapKey] = conn
} dnsConnsLock.Unlock()
dnsConnsLock.Unlock() } else {
// network connection
} else { // set key
// connection
if !conn.KeyIsSet() {
conn.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", conn.process.Pid, conn.Scope, conn.ID)) conn.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", conn.process.Pid, conn.Scope, conn.ID))
conn.UpdateMeta()
}
// save to internal state
// check if it already exists
connsLock.Lock()
_, ok := conns[conn.ID]
if !ok {
conns[conn.ID] = conn
}
connsLock.Unlock()
// save
connsLock.Lock()
conns[conn.ID] = conn
connsLock.Unlock()
}
} }
// notify database controller // notify database controller
@ -270,7 +263,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 // 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() { 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() conn.Meta().Delete()
dbController.PushUpdate(conn) dbController.PushUpdate(conn)

View file

@ -77,7 +77,7 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
if slashes <= 1 { if slashes <= 1 {
// processes // processes
for _, proc := range process.All() { for _, proc := range process.All() {
if strings.HasPrefix(proc.DatabaseKey(), q.DatabaseKeyPrefix()) { if q.Matches(proc) {
it.Next <- proc it.Next <- proc
} }
} }
@ -86,9 +86,9 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
if slashes <= 2 { if slashes <= 2 {
// dns scopes only // dns scopes only
dnsConnsLock.RLock() dnsConnsLock.RLock()
for _, dnsConns := range dnsConns { for _, dnsConn := range dnsConns {
if strings.HasPrefix(dnsConns.DatabaseKey(), q.DatabaseKeyPrefix()) { if q.Matches(dnsConn) {
it.Next <- dnsConns it.Next <- dnsConn
} }
} }
dnsConnsLock.RUnlock() dnsConnsLock.RUnlock()
@ -98,7 +98,7 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
// connections // connections
connsLock.RLock() connsLock.RLock()
for _, conn := range conns { for _, conn := range conns {
if strings.HasPrefix(conn.DatabaseKey(), q.DatabaseKeyPrefix()) { if q.Matches(conn) {
it.Next <- conn it.Next <- conn
} }
} }

View file

@ -5,6 +5,8 @@ import (
"strconv" "strconv"
"sync" "sync"
"time" "time"
"github.com/safing/portmaster/process"
) )
var ( var (
@ -16,6 +18,9 @@ var (
// duration after which DNS requests without a following connection are logged // duration after which DNS requests without a following connection are logged
openDNSRequestLimit = 3 * time.Second openDNSRequestLimit = 3 * time.Second
// scope prefix
unidentifiedProcessScopePrefix = strconv.Itoa(process.UnidentifiedProcessID) + "/"
) )
func removeOpenDNSRequest(pid int, fqdn string) { func removeOpenDNSRequest(pid int, fqdn string) {
@ -23,7 +28,13 @@ func removeOpenDNSRequest(pid int, fqdn string) {
defer openDNSRequestsLock.Unlock() defer openDNSRequestsLock.Unlock()
key := strconv.Itoa(pid) + "/" + fqdn key := strconv.Itoa(pid) + "/" + fqdn
delete(openDNSRequests, key) _, ok := openDNSRequests[key]
if ok {
delete(openDNSRequests, key)
} else if pid != process.UnidentifiedProcessID {
// check if there is an open dns request from an unidentified process
delete(openDNSRequests, unidentifiedProcessScopePrefix+fqdn)
}
} }
// SaveOpenDNSRequest saves a dns request connection that was allowed to proceed. // SaveOpenDNSRequest saves a dns request connection that was allowed to proceed.

View file

@ -53,16 +53,13 @@ func (p *Process) Save() {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
p.UpdateMeta()
if !p.KeyIsSet() { if !p.KeyIsSet() {
// set key
p.SetKey(fmt.Sprintf("%s/%d", processDatabaseNamespace, p.Pid)) p.SetKey(fmt.Sprintf("%s/%d", processDatabaseNamespace, p.Pid))
p.CreateMeta()
}
processesLock.RLock() // save
_, ok := processes[p.Pid]
processesLock.RUnlock()
if !ok {
processesLock.Lock() processesLock.Lock()
processes[p.Pid] = p processes[p.Pid] = p
processesLock.Unlock() processesLock.Unlock()
@ -113,7 +110,9 @@ func CleanProcessStorage(activePIDs map[int]struct{}) {
_, active := activePIDs[p.Pid] _, active := activePIDs[p.Pid]
switch { switch {
case p.Pid <= 0: case p.Pid == UnidentifiedProcessID:
// internal
case p.Pid == SystemProcessID:
// internal // internal
case active: case active:
// process in system process table or recently seen on the network // process in system process table or recently seen on the network

View file

@ -49,7 +49,7 @@ func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) {
case pkt.Info().Protocol == packet.UDP && pkt.Info().Version == packet.IPv6: case pkt.Info().Protocol == packet.UDP && pkt.Info().Version == packet.IPv6:
return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound()) return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
default: default:
return -1, false, errors.New("unsupported protocol for finding process") return UnidentifiedProcessID, false, errors.New("unsupported protocol for finding process")
} }
} }
@ -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) { func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, err error) {
if !enableProcessDetection() { if !enableProcessDetection() {
log.Tracer(pkt.Ctx()).Tracef("process: process detection disabled") 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") log.Tracer(pkt.Ctx()).Tracef("process: getting process and profile by packet")
@ -107,7 +107,7 @@ func GetPidByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remote
case protocol == packet.UDP && ipVersion == packet.IPv6: case protocol == packet.UDP && ipVersion == packet.IPv6:
return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, false) return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, false)
default: default:
return -1, false, errors.New("unsupported protocol for finding process") return UnidentifiedProcessID, false, errors.New("unsupported protocol for finding process")
} }
} }
@ -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) { func GetProcessByEndpoints(ctx context.Context, localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (process *Process, err error) {
if !enableProcessDetection() { if !enableProcessDetection() {
log.Tracer(ctx).Tracef("process: process detection disabled") 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") log.Tracer(ctx).Tracef("process: getting process and profile by endpoints")

View file

@ -9,6 +9,10 @@ import (
"time" "time"
) )
const (
unidentifiedProcessID = -1
)
var ( var (
tcp4Connections []*ConnectionEntry tcp4Connections []*ConnectionEntry
tcp4Listeners []*ConnectionEntry tcp4Listeners []*ConnectionEntry
@ -55,7 +59,7 @@ func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
} }
lock.Unlock() lock.Unlock()
if err != nil { if err != nil {
return -1, pktDirection, err return unidentifiedProcessID, pktDirection, err
} }
// search // search
@ -67,7 +71,7 @@ func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
time.Sleep(waitTime) time.Sleep(waitTime)
} }
return -1, pktDirection, nil return unidentifiedProcessID, pktDirection, nil
} }
// GetTCP6PacketInfo returns the pid of the given IPv6/TCP connection. // GetTCP6PacketInfo returns the pid of the given IPv6/TCP connection.
@ -91,7 +95,7 @@ func GetTCP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
} }
lock.Unlock() lock.Unlock()
if err != nil { if err != nil {
return -1, pktDirection, err return unidentifiedProcessID, pktDirection, err
} }
// search // search
@ -103,7 +107,7 @@ func GetTCP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
time.Sleep(waitTime) time.Sleep(waitTime)
} }
return -1, pktDirection, nil return unidentifiedProcessID, pktDirection, nil
} }
// GetUDP4PacketInfo returns the pid of the given IPv4/UDP connection. // GetUDP4PacketInfo returns the pid of the given IPv4/UDP connection.
@ -127,7 +131,7 @@ func GetUDP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
} }
lock.Unlock() lock.Unlock()
if err != nil { if err != nil {
return -1, pktDirection, err return unidentifiedProcessID, pktDirection, err
} }
// search // search
@ -139,7 +143,7 @@ func GetUDP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
time.Sleep(waitTime) time.Sleep(waitTime)
} }
return -1, pktDirection, nil return unidentifiedProcessID, pktDirection, nil
} }
// GetUDP6PacketInfo returns the pid of the given IPv6/UDP connection. // GetUDP6PacketInfo returns the pid of the given IPv6/UDP connection.
@ -163,7 +167,7 @@ func GetUDP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
} }
lock.Unlock() lock.Unlock()
if err != nil { if err != nil {
return -1, pktDirection, err return unidentifiedProcessID, pktDirection, err
} }
// search // search
@ -175,7 +179,7 @@ func GetUDP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remote
time.Sleep(waitTime) time.Sleep(waitTime)
} }
return -1, pktDirection, nil return unidentifiedProcessID, pktDirection, nil
} }
func search(connections, listeners []*ConnectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16, pktDirection bool) (pid int, direction bool) { //nolint:unparam // TODO: use direction, it may not be used because results caused problems, investigate. func search(connections, listeners []*ConnectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16, pktDirection bool) (pid int, direction bool) { //nolint:unparam // TODO: use direction, it may not be used because results caused problems, investigate.
@ -204,7 +208,7 @@ func search(connections, listeners []*ConnectionEntry, localIP, remoteIP net.IP,
} }
} }
return -1, pktDirection return unidentifiedProcessID, pktDirection
} }
func searchConnections(list []*ConnectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16) (pid int) { func searchConnections(list []*ConnectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16) (pid int) {
@ -218,7 +222,7 @@ func searchConnections(list []*ConnectionEntry, localIP, remoteIP net.IP, localP
} }
} }
return -1 return unidentifiedProcessID
} }
func searchListeners(list []*ConnectionEntry, localIP net.IP, localPort uint16) (pid int) { func searchListeners(list []*ConnectionEntry, localIP net.IP, localPort uint16) (pid int) {
@ -231,7 +235,7 @@ func searchListeners(list []*ConnectionEntry, localIP net.IP, localPort uint16)
} }
} }
return -1 return unidentifiedProcessID
} }
// GetActiveConnectionIDs returns all currently active connection IDs. // GetActiveConnectionIDs returns all currently active connection IDs.

View file

@ -33,7 +33,7 @@ func GetPidOfConnection(localIP net.IP, localPort uint16, protocol uint8) (pid i
} }
} }
if !ok { if !ok {
return -1, NoSocket return unidentifiedProcessID, NoSocket
} }
} }
@ -45,7 +45,7 @@ func GetPidOfConnection(localIP net.IP, localPort uint16, protocol uint8) (pid i
pid, ok = GetPidOfInode(uid, inode) pid, ok = GetPidOfInode(uid, inode)
} }
if !ok { if !ok {
return -1, NoProcess return unidentifiedProcessID, NoProcess
} }
return return
@ -64,7 +64,7 @@ func GetPidOfIncomingConnection(localIP net.IP, localPort uint16, protocol uint8
} }
if !ok { if !ok {
return -1, NoSocket return unidentifiedProcessID, NoSocket
} }
} }
@ -76,7 +76,7 @@ func GetPidOfIncomingConnection(localIP net.IP, localPort uint16, protocol uint8
pid, ok = GetPidOfInode(uid, inode) pid, ok = GetPidOfInode(uid, inode)
} }
if !ok { if !ok {
return -1, NoProcess return unidentifiedProcessID, NoProcess
} }
return return

View file

@ -7,6 +7,10 @@ import (
"net" "net"
) )
const (
unidentifiedProcessID = -1
)
// GetTCP4PacketInfo searches the network state tables for a TCP4 connection // GetTCP4PacketInfo searches the network state tables for a TCP4 connection
func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) { func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
return search(TCP4, localIP, localPort, pktDirection) return search(TCP4, localIP, localPort, pktDirection)
@ -52,11 +56,11 @@ func search(protocol uint8, localIP net.IP, localPort uint16, pktDirection bool)
switch status { switch status {
case NoSocket: case NoSocket:
return -1, direction, errors.New("could not find socket") return unidentifiedProcessID, direction, errors.New("could not find socket")
case NoProcess: case NoProcess:
return -1, direction, errors.New("could not find PID") return unidentifiedProcessID, direction, errors.New("could not find PID")
default: default:
return -1, direction, nil return unidentifiedProcessID, direction, nil
} }
} }

View file

@ -77,7 +77,7 @@ func GetPidOfInode(uid, inode int) (int, bool) { //nolint:gocognit // TODO
} }
} }
return -1, false return unidentifiedProcessID, false
} }
func findSocketFromPid(pid, inode int) bool { func findSocketFromPid(pid, inode int) bool {

View file

@ -100,7 +100,7 @@ func getConnectionSocket(localIP net.IP, localPort uint16, protocol uint8) (int,
socketData, err := os.Open(procFile) socketData, err := os.Open(procFile)
if err != nil { if err != nil {
log.Warningf("process/proc: could not read %s: %s", procFile, err) log.Warningf("process/proc: could not read %s: %s", procFile, err)
return -1, -1, false return unidentifiedProcessID, unidentifiedProcessID, false
} }
defer socketData.Close() defer socketData.Close()
@ -146,7 +146,7 @@ func getConnectionSocket(localIP net.IP, localPort uint16, protocol uint8) (int,
} }
return -1, -1, false return unidentifiedProcessID, unidentifiedProcessID, false
} }
@ -187,7 +187,7 @@ func getListeningSocket(localIP net.IP, localPort uint16, protocol uint8) (uid,
return data[0], data[1], true return data[0], data[1], true
} }
return -1, -1, false return unidentifiedProcessID, unidentifiedProcessID, false
} }
func procDelimiter(c rune) bool { func procDelimiter(c rune) bool {

View file

@ -75,11 +75,11 @@ func (p *Process) String() string {
func GetOrFindPrimaryProcess(ctx context.Context, pid int) (*Process, error) { func GetOrFindPrimaryProcess(ctx context.Context, pid int) (*Process, error) {
log.Tracer(ctx).Tracef("process: getting primary process for PID %d", pid) log.Tracer(ctx).Tracef("process: getting primary process for PID %d", pid)
if pid == -1 { switch pid {
return UnknownProcess, nil case UnidentifiedProcessID:
} return GetUnidentifiedProcess(ctx), nil
if pid == 0 { case SystemProcessID:
return OSProcess, nil return GetSystemProcess(ctx), nil
} }
process, err := loadProcess(ctx, pid) process, err := loadProcess(ctx, pid)
@ -88,8 +88,8 @@ func GetOrFindPrimaryProcess(ctx context.Context, pid int) (*Process, error) {
} }
for { for {
if process.ParentPid == 0 { if process.ParentPid <= 0 {
return OSProcess, nil return process, nil
} }
parentProcess, err := loadProcess(ctx, process.ParentPid) parentProcess, err := loadProcess(ctx, process.ParentPid)
if err != nil { 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) { 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)
if pid == -1 { switch pid {
return UnknownProcess, nil case UnidentifiedProcessID:
} return GetUnidentifiedProcess(ctx), nil
if pid == 0 { case SystemProcessID:
return OSProcess, nil return GetSystemProcess(ctx), nil
} }
p, err := loadProcess(ctx, pid) p, err := loadProcess(ctx, pid)
@ -184,11 +184,12 @@ func deduplicateRequest(ctx context.Context, pid int) (finishRequest func()) {
} }
func loadProcess(ctx context.Context, pid int) (*Process, error) { func loadProcess(ctx context.Context, pid int) (*Process, error) {
if pid == -1 {
return UnknownProcess, nil switch pid {
} case UnidentifiedProcessID:
if pid == 0 { return GetUnidentifiedProcess(ctx), nil
return OSProcess, nil case SystemProcessID:
return GetSystemProcess(ctx), nil
} }
process, ok := GetProcessFromStorage(pid) process, ok := GetProcessFromStorage(pid)

View file

@ -15,6 +15,8 @@ func (p *Process) GetProfile(ctx context.Context) error {
// only find profiles if not already done. // only find profiles if not already done.
if p.profile != nil { if p.profile != nil {
log.Tracer(ctx).Trace("process: profile already loaded") log.Tracer(ctx).Trace("process: profile already loaded")
// mark profile as used
p.profile.MarkUsed()
return nil return nil
} }
log.Tracer(ctx).Trace("process: loading profile") log.Tracer(ctx).Trace("process: loading profile")
@ -29,10 +31,8 @@ func (p *Process) GetProfile(ctx context.Context) error {
localProfile.Name = p.ExecName localProfile.Name = p.ExecName
} }
// mark as used and save // mark profile as used
if localProfile.MarkUsed() { localProfile.MarkUsed()
_ = localProfile.Save()
}
p.LocalProfileKey = localProfile.Key() p.LocalProfileKey = localProfile.Key()
p.profile = profile.NewLayeredProfile(localProfile) p.profile = profile.NewLayeredProfile(localProfile)

84
process/special.go Normal file
View file

@ -0,0 +1,84 @@
package process
import (
"context"
"time"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/profile"
)
// Special Process IDs
const (
UnidentifiedProcessID = -1
SystemProcessID = 0
)
var (
// unidentifiedProcess is used when a process cannot be found.
unidentifiedProcess = &Process{
UserID: UnidentifiedProcessID,
UserName: "Unknown",
Pid: UnidentifiedProcessID,
ParentPid: UnidentifiedProcessID,
Name: "Unidentified Processes",
}
// systemProcess is used to represent the Kernel.
systemProcess = &Process{
UserID: SystemProcessID,
UserName: "Kernel",
Pid: SystemProcessID,
ParentPid: SystemProcessID,
Name: "Operating System",
}
)
// GetUnidentifiedProcess returns the special process assigned to unidentified processes.
func GetUnidentifiedProcess(ctx context.Context) *Process {
return getSpecialProcess(ctx, UnidentifiedProcessID, unidentifiedProcess, profile.GetUnidentifiedProfile)
}
// GetSystemProcess returns the special process used for the Kernel.
func GetSystemProcess(ctx context.Context) *Process {
return getSpecialProcess(ctx, SystemProcessID, systemProcess, profile.GetSystemProfile)
}
func getSpecialProcess(ctx context.Context, pid int, template *Process, getProfile func() *profile.Profile) *Process {
// check storage
p, ok := GetProcessFromStorage(pid)
if ok {
return p
}
// assign template
p = template
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
}

View file

@ -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()
}

View file

@ -1,7 +1,14 @@
package profile package profile
import ( import (
"context"
"sync" "sync"
"time"
)
const (
activeProfileCleanerTickDuration = 10 * time.Minute
activeProfileCleanerThreshold = 1 * time.Hour
) )
var ( var (
@ -38,7 +45,34 @@ func markActiveProfileAsOutdated(scopedID string) {
profile, ok := activeProfiles[scopedID] profile, ok := activeProfiles[scopedID]
if ok { if ok {
profile.oudated.Set() profile.outdated.Set()
delete(activeProfiles, scopedID) delete(activeProfiles, scopedID)
} }
} }
func cleanActiveProfiles(ctx context.Context) error {
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
}
}
}

View file

@ -70,8 +70,8 @@ func updateGlobalConfigProfile(ctx context.Context, data interface{}) error {
// build global profile for reference // build global profile for reference
profile := &Profile{ profile := &Profile{
ID: "config", ID: "global-config",
Source: SourceGlobal, Source: SourceSpecial,
Name: "Global Configuration", Name: "Global Configuration",
Config: make(map[string]interface{}), Config: make(map[string]interface{}),
internalSave: true, internalSave: true,

View file

@ -42,6 +42,8 @@ func start() error {
return err return err
} }
module.StartServiceWorker("clean active profiles", 0, cleanActiveProfiles)
err = updateGlobalConfigProfile(module.Ctx, nil) err = updateGlobalConfigProfile(module.Ctx, nil)
if err != nil { if err != nil {
log.Warningf("profile: error during loading global profile from configuration: %s", err) log.Warningf("profile: error during loading global profile from configuration: %s", err)

View file

@ -128,7 +128,7 @@ func (lp *LayeredProfile) Update() (revisionCounter uint64) {
var changed bool var changed bool
for i, layer := range lp.layers { for i, layer := range lp.layers {
if layer.oudated.IsSet() { if layer.outdated.IsSet() {
changed = true changed = true
// update layer // update layer
newLayer, err := GetProfile(layer.Source, layer.ID) newLayer, err := GetProfile(layer.Source, layer.ID)
@ -175,6 +175,11 @@ func (lp *LayeredProfile) updateCaches() {
// TODO: ignore community profiles // 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. // SecurityLevel returns the highest security level of all layered profiles.
func (lp *LayeredProfile) SecurityLevel() uint8 { func (lp *LayeredProfile) SecurityLevel() uint8 {
return uint8(atomic.LoadUint32(lp.securityLevel)) return uint8(atomic.LoadUint32(lp.securityLevel))

View file

@ -19,15 +19,15 @@ import (
) )
var ( var (
lastUsedUpdateThreshold = 1 * time.Hour lastUsedUpdateThreshold = 24 * time.Hour
) )
// Profile Sources // Profile Sources
const ( const (
SourceLocal string = "local" SourceLocal string = "local" // local, editable
SourceSpecial string = "special" // specials (read-only)
SourceCommunity string = "community" SourceCommunity string = "community"
SourceEnterprise string = "enterprise" SourceEnterprise string = "enterprise"
SourceGlobal string = "global"
) )
// Default Action IDs // Default Action IDs
@ -77,7 +77,8 @@ type Profile struct { //nolint:maligned // not worth the effort
filterListIDs []string filterListIDs []string
// Lifecycle Management // Lifecycle Management
oudated *abool.AtomicBool outdated *abool.AtomicBool
lastUsed time.Time
// Framework // Framework
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process/actor must be found // 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) { func (profile *Profile) prepConfig() (err error) {
// prepare configuration // prepare configuration
profile.configPerspective, err = config.NewPerspective(profile.Config) profile.configPerspective, err = config.NewPerspective(profile.Config)
profile.oudated = abool.New() profile.outdated = abool.New()
return return
} }
@ -156,10 +157,11 @@ func (profile *Profile) parseConfig() error {
// New returns a new Profile. // New returns a new Profile.
func New() *Profile { func New() *Profile {
profile := &Profile{ profile := &Profile{
ID: uuid.NewV4().String(), ID: uuid.NewV4().String(),
Source: SourceLocal, Source: SourceLocal,
Created: time.Now().Unix(), Created: time.Now().Unix(),
Config: make(map[string]interface{}), Config: make(map[string]interface{}),
internalSave: true,
} }
// create placeholders // create placeholders
@ -190,13 +192,26 @@ func (profile *Profile) Save() error {
return profileDB.Put(profile) return profileDB.Put(profile)
} }
// MarkUsed marks the profile as used, eventually. // MarkUsed marks the profile as used and saves it when it has changed.
func (profile *Profile) MarkUsed() (updated bool) { func (profile *Profile) MarkUsed() {
profile.Lock()
// lastUsed
profile.lastUsed = time.Now()
// ApproxLastUsed
save := false
if time.Now().Add(-lastUsedUpdateThreshold).Unix() > profile.ApproxLastUsed { if time.Now().Add(-lastUsedUpdateThreshold).Unix() > profile.ApproxLastUsed {
profile.ApproxLastUsed = time.Now().Unix() 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. // String returns a string representation of the Profile.
@ -224,8 +239,6 @@ func (profile *Profile) addEndpointyEntry(cfgKey, newEntry string) {
endpointList = append(endpointList, newEntry) endpointList = append(endpointList, newEntry)
profile.Config[cfgKey] = endpointList profile.Config[cfgKey] = endpointList
// save without full reload
profile.internalSave = true
profile.Unlock() profile.Unlock()
err := profile.Save() err := profile.Save()
if err != nil { if err != nil {
@ -233,10 +246,13 @@ func (profile *Profile) addEndpointyEntry(cfgKey, newEntry string) {
} }
// reload manually // reload manually
profile.Lock()
profile.dataParsed = false
err = profile.parseConfig() err = profile.parseConfig()
if err != nil { if err != nil {
log.Warningf("profile: failed to parse profile config after adding endpoint: %s", err) log.Warningf("profile: failed to parse profile config after adding endpoint: %s", err)
} }
profile.Unlock()
} }
// GetProfile loads a profile from the database. // GetProfile loads a profile from the database.
@ -249,6 +265,7 @@ func GetProfileByScopedID(scopedID string) (*Profile, error) {
// check cache // check cache
profile := getActiveProfile(scopedID) profile := getActiveProfile(scopedID)
if profile != nil { if profile != nil {
profile.MarkUsed()
return profile, nil return profile, nil
} }
@ -266,7 +283,6 @@ func GetProfileByScopedID(scopedID string) (*Profile, error) {
// lock for prepping // lock for prepping
profile.Lock() profile.Lock()
defer profile.Unlock()
// prepare config // prepare config
err = profile.prepConfig() 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) log.Warningf("profiles: profile %s has (partly) invalid configuration: %s", profile.ID, err)
} }
// mark as internal
profile.internalSave = true
profile.Unlock()
// mark active // mark active
profile.MarkUsed()
markProfileActive(profile) markProfileActive(profile)
return profile, nil return profile, nil

56
profile/special.go Normal file
View file

@ -0,0 +1,56 @@
package profile
import (
"github.com/safing/portbase/log"
)
const (
unidentifiedProfileID = "_unidentified"
systemProfileID = "_system"
)
// GetUnidentifiedProfile returns the special profile assigned to unidentified processes.
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
}
// GetSystemProfile returns the special profile used for the Kernel.
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
}