Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental support to inherit profiles from parent processes #948

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,18 @@ func (md *MatchingData) MatchingPath() string { return md.p.MatchingPath }

// Cmdline returns the command line of the process.
func (md *MatchingData) Cmdline() string { return md.p.CmdLine }

// Parent returns the matching data of the parent. Due to bad import cycles we cannot
// use the correct interface type here ...
func (md *MatchingData) Parent() interface{} {
if md.p.ParentPid == 0 {
return nil
}

parentProcess, err := loadProcess(module.Ctx, md.p.ParentPid)
if err != nil {
return nil
}

return parentProcess.MatchingData()
}
21 changes: 21 additions & 0 deletions profile/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ var (
cfgOptionExitHubPolicyOrder = 146

// Setting "DNS Exit Node Rules" at order 147.

CfgOptionInheritProfileKey = "core/inheritProfiles"
cfgOptionInhertiProfile config.BoolOption
)

// A list of all security level settings.
Expand Down Expand Up @@ -721,5 +724,23 @@ By default, the Portmaster tries to choose the node closest to the destination a
cfgOptionRoutingAlgorithm = config.Concurrent.GetAsString(CfgOptionRoutingAlgorithmKey, defaultRoutingAlg)
cfgStringOptions[CfgOptionRoutingAlgorithmKey] = cfgOptionRoutingAlgorithm

err = config.Register(&config.Option{
Name: "Inherit Process Profiles",
Key: CfgOptionInheritProfileKey,
Description: "Whether or not processes that do not have a dedicated profile should inherit the app profile from the parent process.",
OptType: config.OptTypeBool,
DefaultValue: false,
ExpertiseLevel: config.ExpertiseLevelDeveloper,
ReleaseLevel: config.ReleaseLevelExperimental,
Annotations: config.Annotations{
config.DisplayOrderAnnotation: 529,
config.CategoryAnnotation: "Development",
},
})
if err != nil {
return err
}
cfgOptionInhertiProfile = config.Concurrent.GetAsBool(CfgOptionInheritProfileKey, false)

return nil
}
4 changes: 4 additions & 0 deletions profile/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ type (
Path() string
MatchingPath() string
Cmdline() string
Parent() interface{} // implementations should return MatchingData but we do
// have import cycles here if we try to specify that.
// TODO(ppacher): fix that and move the matching data def into
// it's own package (where it belongs to because it's needed by process and profile)
}

matchingFingerprint interface {
Expand Down
45 changes: 31 additions & 14 deletions profile/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,30 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
}

// If we don't have a profile yet, find profile based on matching data.
if profile == nil {
profile, err = findProfile(SourceLocal, md)
iter := md
var highestScore int
for iter != nil {
iterProfile, score, err := findProfile(SourceLocal, iter)
if err != nil {
return nil, fmt.Errorf("failed to search for profile: %w", err)
}

if score > highestScore {
profile = iterProfile
highestScore = score
}

if !cfgOptionInhertiProfile() {
break
}

// try to get the matching data of the parent
parent := iter.Parent()
if parent == nil {
break
}

iter, _ = parent.(MatchingData)
}

// If we still don't have a profile, create a new one.
Expand Down Expand Up @@ -186,21 +205,19 @@ func getProfile(scopedID string) (profile *Profile, err error) {

// findProfile searches for a profile with the given linked path. If it cannot
// find one, it will create a new profile for the given linked path.
func findProfile(source profileSource, md MatchingData) (profile *Profile, err error) {
func findProfile(source profileSource, md MatchingData) (profile *Profile, highestScore int, err error) {
// TODO: Loading every profile from database and parsing it for every new
// process might be quite expensive. Measure impact and possibly improve.

// Get iterator over all profiles.
it, err := profileDB.Query(query.New(profilesDBPath + makeScopedID(source, "")))
if err != nil {
return nil, fmt.Errorf("failed to query for profiles: %w", err)
return nil, 0, fmt.Errorf("failed to query for profiles: %w", err)
}

// Find best matching profile.
var (
highestScore int
bestMatch record.Record
)
var bestMatch record.Record

profileFeed:
for r := range it.Next {
// Parse fingerprints.
Expand Down Expand Up @@ -232,27 +249,27 @@ profileFeed:

// Check if there was an error while iterating.
if it.Err() != nil {
return nil, fmt.Errorf("failed to iterate over profiles: %w", err)
return nil, 0, fmt.Errorf("failed to iterate over profiles: %w", err)
}

// Return nothing if no profile matched.
if bestMatch == nil {
return nil, nil
return nil, 0, nil
}

// If we have a match, parse and return the profile.
profile, err = loadProfile(bestMatch)
if err != nil {
return nil, fmt.Errorf("failed to parse selected profile %s: %w", bestMatch.Key(), err)
return nil, 0, fmt.Errorf("failed to parse selected profile %s: %w", bestMatch.Key(), err)
}

// Check if this profile is already active and return the active version instead.
if activeProfile := getActiveProfile(profile.ScopedID()); activeProfile != nil && !activeProfile.IsOutdated() {
return activeProfile, nil
return activeProfile, highestScore, nil
}

// Return nothing if no profile matched.
return profile, nil
// Return the freshly loaded profile since there's no active version available.
return profile, highestScore, nil
}

func loadProfileFingerprints(r record.Record) (parsed *parsedFingerprints, err error) {
Expand Down