180 lines
5.8 KiB
Go
180 lines
5.8 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/safing/portmaster/base/log"
|
|
"github.com/safing/portmaster/service/profile"
|
|
)
|
|
|
|
var ownPID = os.Getpid()
|
|
|
|
// GetProfile finds and assigns a profile set to the process.
|
|
func (p *Process) GetProfile(ctx context.Context) (changed bool, err error) {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
// Check if profile is already loaded.
|
|
if p.profile != nil {
|
|
log.Tracer(ctx).Trace("process: profile already loaded")
|
|
return
|
|
}
|
|
|
|
// If not, continue with loading the profile.
|
|
log.Tracer(ctx).Trace("process: loading profile")
|
|
|
|
// Get special or regular profile.
|
|
localProfile, err := profile.GetLocalProfile(p.getSpecialProfileID(), p.MatchingData(), p.CreateProfileCallback)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to find profile: %w", err)
|
|
}
|
|
|
|
// Assign profile to process.
|
|
p.PrimaryProfileID = localProfile.ScopedID()
|
|
p.profile = localProfile.LayeredProfile()
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// RefetchProfile removes the profile and finds and assigns a new profile.
|
|
func (p *Process) RefetchProfile(ctx context.Context) error {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
// Get special or regular profile.
|
|
localProfile, err := profile.GetLocalProfile(p.getSpecialProfileID(), p.MatchingData(), p.CreateProfileCallback)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find profile: %w", err)
|
|
}
|
|
|
|
// Assign profile to process.
|
|
p.PrimaryProfileID = localProfile.ScopedID()
|
|
p.profile = localProfile.LayeredProfile()
|
|
|
|
return nil
|
|
}
|
|
|
|
// getSpecialProfileID returns the special profile ID for the process, if any.
|
|
func (p *Process) getSpecialProfileID() (specialProfileID string) {
|
|
// Check if we need a special profile.
|
|
switch p.Pid {
|
|
case UnidentifiedProcessID:
|
|
specialProfileID = profile.UnidentifiedProfileID
|
|
case UnsolicitedProcessID:
|
|
specialProfileID = profile.UnsolicitedProfileID
|
|
case SystemProcessID:
|
|
specialProfileID = profile.SystemProfileID
|
|
case ownPID:
|
|
specialProfileID = profile.PortmasterProfileID
|
|
default:
|
|
// Check if this is another Portmaster component.
|
|
if p.IsPortmasterUi(context.Background()) {
|
|
specialProfileID = profile.PortmasterAppProfileID
|
|
}
|
|
// Check if this is the system resolver.
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
// Depending on the OS version System32 may be capitalized or not.
|
|
if (p.Path == `C:\Windows\System32\svchost.exe` ||
|
|
p.Path == `C:\Windows\system32\svchost.exe`) &&
|
|
// This comes from the windows tasklist command and should be pretty consistent.
|
|
(profile.KeyAndValueInTags(p.Tags, "svchost", "Dnscache") ||
|
|
// As an alternative in case of failure, we try to match the svchost.exe service parameter.
|
|
strings.Contains(p.CmdLine, "-s Dnscache")) {
|
|
specialProfileID = profile.SystemResolverProfileID
|
|
}
|
|
case "linux":
|
|
switch p.Path {
|
|
case "/lib/systemd/systemd-resolved",
|
|
"/usr/lib/systemd/systemd-resolved",
|
|
"/lib64/systemd/systemd-resolved",
|
|
"/usr/lib64/systemd/systemd-resolved",
|
|
"/usr/bin/nscd",
|
|
"/usr/sbin/nscd",
|
|
"/usr/bin/dnsmasq",
|
|
"/usr/sbin/dnsmasq":
|
|
specialProfileID = profile.SystemResolverProfileID
|
|
}
|
|
}
|
|
}
|
|
|
|
return specialProfileID
|
|
}
|
|
|
|
// IsPortmasterUi checks if the process is the Portmaster UI or its child (up to 3 parent levels).
|
|
func (p *Process) IsPortmasterUi(ctx context.Context) bool {
|
|
if module.portmasterUIPath == "" {
|
|
return false
|
|
}
|
|
|
|
// Find parent for up to two levels, if we don't match the path.
|
|
const checkLevels = 3
|
|
|
|
var previousPid int
|
|
proc := p
|
|
|
|
hasPmWebviewEnvVar := false
|
|
|
|
for i := 0; i < checkLevels; i++ {
|
|
if proc.Pid == UnidentifiedProcessID || proc.Pid == SystemProcessID {
|
|
break
|
|
}
|
|
|
|
realPath, err := filepath.EvalSymlinks(proc.Path)
|
|
if err == nil && realPath == module.portmasterUIPath {
|
|
if runtime.GOOS != "windows" {
|
|
return true
|
|
}
|
|
|
|
// On Windows, avoid false positive detection of the Portmaster UI.
|
|
// For example:
|
|
// There may be cases where a system browser is launched from the Portmaster UI,
|
|
// making it a child of the Portmaster UI process (e.g., user clicked a link in the UI).
|
|
// In this case, the parent process tree may look like this:
|
|
// Portmaster.exe
|
|
// ├─ WebView (PM UI)
|
|
// │ └─ WebView (PM UI child)
|
|
// └─ System Web Browser ...
|
|
//
|
|
// To ensure that 'p' is the actual Portmaster UI process, we check for the presence
|
|
// of the 'PORTMASTER_UI_WEBVIEW_PROCESS' environment variable in the process and its parents.
|
|
// If the env var is set, we are a child (WebView window) of the Portmaster UI process.
|
|
// Otherwise, the process was launched by the Portmaster UI, but should not be trusted as the Portmaster UI process.
|
|
if i == 0 {
|
|
return true // We are the main Portmaster UI process.
|
|
}
|
|
if hasPmWebviewEnvVar {
|
|
return true // We are a WebView window of the Portmaster UI process.
|
|
}
|
|
// The process was launched by the Portmaster UI, but should not be trusted as the Portmaster UI process.
|
|
log.Tracer(ctx).Warningf("process: %d %q is a child of the Portmaster UI, but does not have the PORTMASTER_UI_WEBVIEW_PROCESS environment variable set. Ignoring.", p.Pid, p.Path)
|
|
return false
|
|
}
|
|
|
|
// Check if the process has the environment variable set.
|
|
//
|
|
// It is OK to check for the existence of the environment variable in all
|
|
// processes in the parent chain (on all loop iterations). This increases the
|
|
// chance of correct detection, even if a child or grandchild WebView process
|
|
// did not inherit the environment variable for some reason.
|
|
if _, ok := proc.Env["PORTMASTER_UI_WEBVIEW_PROCESS"]; ok {
|
|
hasPmWebviewEnvVar = true
|
|
}
|
|
|
|
if i < checkLevels-1 { // no need to check parent if we are at the last level
|
|
previousPid = proc.Pid
|
|
proc, err = GetOrFindProcess(ctx, proc.ParentPid)
|
|
if err != nil || proc.Pid == previousPid {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|