Add experimental support to inherit profiles from parent processes

This commit is contained in:
Patrick Pacher 2022-10-10 16:07:07 +02:00
parent a13cd45a13
commit dc68abc17d
No known key found for this signature in database
GPG key ID: E8CD2DA160925A6D
4 changed files with 71 additions and 14 deletions

View file

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

View file

@ -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.
@ -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
}

View file

@ -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 {

View file

@ -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.
@ -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.
@ -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) {