Improve profile metadata handling
This commit is contained in:
@@ -163,8 +163,9 @@ type parsedFingerprints struct {
|
||||
func parseFingerprints(raw []Fingerprint, deprecatedLinkedPath string) (parsed *parsedFingerprints, firstErr error) {
|
||||
parsed = &parsedFingerprints{}
|
||||
|
||||
// Add deprecated linked path to fingerprints.
|
||||
if deprecatedLinkedPath != "" {
|
||||
// Add deprecated LinkedPath to fingerprints, if they are empty.
|
||||
// TODO: Remove in v1.5
|
||||
if len(raw) == 0 && deprecatedLinkedPath != "" {
|
||||
parsed.pathPrints = append(parsed.pathPrints, &fingerprintEquals{
|
||||
Fingerprint: Fingerprint{
|
||||
Type: FingerprintTypePathID,
|
||||
|
||||
@@ -70,7 +70,10 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
||||
}
|
||||
|
||||
// If we still don't have a profile, create a new one.
|
||||
var created bool
|
||||
if profile == nil {
|
||||
created = true
|
||||
|
||||
// Try the profile creation callback, if we have one.
|
||||
if createProfileCallback != nil {
|
||||
profile = createProfileCallback()
|
||||
@@ -84,9 +87,10 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
||||
}
|
||||
|
||||
profile = New(&Profile{
|
||||
ID: id,
|
||||
Source: SourceLocal,
|
||||
PresentationPath: md.Path(),
|
||||
ID: id,
|
||||
Source: SourceLocal,
|
||||
PresentationPath: md.Path(),
|
||||
UsePresentationPath: true,
|
||||
Fingerprints: []Fingerprint{
|
||||
{
|
||||
Type: FingerprintTypePathID,
|
||||
@@ -98,6 +102,25 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize and update profile.
|
||||
|
||||
// Update metadata.
|
||||
changed := profile.updateMetadata(md.Path())
|
||||
|
||||
// Save if created or changed.
|
||||
if created || changed {
|
||||
// Save profile.
|
||||
err := profile.Save()
|
||||
if err != nil {
|
||||
log.Warningf("profile: failed to save profile %s after creation: %s", profile.ScopedID(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger further metadata fetching from system if profile was created.
|
||||
if created && profile.UsePresentationPath {
|
||||
module.StartWorker("get profile metadata", profile.updateMetadataFromSystem)
|
||||
}
|
||||
|
||||
// Prepare profile for first use.
|
||||
|
||||
// Process profiles are coming directly from the database or are new.
|
||||
@@ -158,7 +181,10 @@ profileFeed:
|
||||
prints, err := loadProfileFingerprints(r)
|
||||
if err != nil {
|
||||
log.Debugf("profile: failed to load fingerprints of %s: %s", r.Key(), err)
|
||||
continue
|
||||
}
|
||||
// Continue with any returned fingerprints.
|
||||
if prints == nil {
|
||||
continue profileFeed
|
||||
}
|
||||
|
||||
// Get matching score and compare.
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -89,6 +88,10 @@ type Profile struct { //nolint:maligned // not worth the effort
|
||||
// Is automatically removed when the path does not exist.
|
||||
// Is automatically populated with the next match when empty.
|
||||
PresentationPath string
|
||||
// UsePresentationPath can be used to enable/disable fetching information
|
||||
// from the executable at PresentationPath. In some cases, this is not
|
||||
// desirable.
|
||||
UsePresentationPath bool
|
||||
// Fingerprints holds process matching information.
|
||||
Fingerprints []Fingerprint
|
||||
// SecurityLevel is the mininum security level to apply to
|
||||
@@ -419,48 +422,54 @@ func EnsureProfile(r record.Record) (*Profile, error) {
|
||||
return newProfile, nil
|
||||
}
|
||||
|
||||
// UpdateMetadata updates meta data fields on the profile and returns whether
|
||||
// the profile was changed. If there is data that needs to be fetched from the
|
||||
// operating system, it will start an async worker to fetch that data and save
|
||||
// the profile afterwards.
|
||||
func (profile *Profile) UpdateMetadata(binaryPath string) (changed bool) {
|
||||
// updateMetadata updates meta data fields on the profile and returns whether
|
||||
// the profile was changed.
|
||||
func (profile *Profile) updateMetadata(binaryPath string) (changed bool) {
|
||||
// Check if this is a local profile, else warn and return.
|
||||
if profile.Source != SourceLocal {
|
||||
log.Warningf("tried to update metadata for non-local profile %s", profile.ScopedID())
|
||||
return false
|
||||
}
|
||||
|
||||
profile.Lock()
|
||||
defer profile.Unlock()
|
||||
|
||||
// Update special profile and return if it was one.
|
||||
if ok, changed := updateSpecialProfileMetadata(profile, binaryPath); ok {
|
||||
return changed
|
||||
// Set PresentationPath if unset.
|
||||
if profile.PresentationPath == "" && binaryPath != "" {
|
||||
profile.PresentationPath = binaryPath
|
||||
changed = true
|
||||
}
|
||||
|
||||
var needsUpdateFromSystem bool
|
||||
// Migrate LinkedPath to PresentationPath.
|
||||
// TODO: Remove in v1.5
|
||||
if profile.PresentationPath == "" && profile.LinkedPath != "" {
|
||||
profile.PresentationPath = profile.LinkedPath
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Check profile name.
|
||||
filename := filepath.Base(profile.PresentationPath)
|
||||
|
||||
// Update profile name if it is empty or equals the filename, which is the
|
||||
// case for older profiles.
|
||||
if strings.TrimSpace(profile.Name) == "" || profile.Name == filename {
|
||||
// Generate a default profile name if does not exist.
|
||||
// Set Name if unset.
|
||||
if profile.Name == "" && profile.PresentationPath != "" {
|
||||
// Generate a default profile name from path.
|
||||
profile.Name = osdetail.GenerateBinaryNameFromPath(profile.PresentationPath)
|
||||
if profile.Name == filename {
|
||||
// TODO: Theoretically, the generated name could be identical to the
|
||||
// filename.
|
||||
// As a quick fix, append a space to the name.
|
||||
profile.Name += " "
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Migrato to Fingerprints.
|
||||
// TODO: Remove in v1.5
|
||||
if len(profile.Fingerprints) == 0 && profile.LinkedPath != "" {
|
||||
profile.Fingerprints = []Fingerprint{
|
||||
{
|
||||
Type: FingerprintTypePathID,
|
||||
Operation: FingerprintOperationEqualsID,
|
||||
Value: profile.LinkedPath,
|
||||
},
|
||||
}
|
||||
changed = true
|
||||
needsUpdateFromSystem = true
|
||||
}
|
||||
|
||||
// If needed, get more/better data from the operating system.
|
||||
if needsUpdateFromSystem {
|
||||
module.StartWorker("get profile metadata", profile.updateMetadataFromSystem)
|
||||
// UI Backward Compatibility:
|
||||
// Fill LinkedPath with PresentationPath
|
||||
// TODO: Remove in v1.1
|
||||
if profile.LinkedPath == "" && profile.PresentationPath != "" {
|
||||
profile.LinkedPath = profile.PresentationPath
|
||||
changed = true
|
||||
}
|
||||
|
||||
return changed
|
||||
@@ -469,23 +478,14 @@ func (profile *Profile) UpdateMetadata(binaryPath string) (changed bool) {
|
||||
// updateMetadataFromSystem updates the profile metadata with data from the
|
||||
// operating system and saves it afterwards.
|
||||
func (profile *Profile) updateMetadataFromSystem(ctx context.Context) error {
|
||||
var changed bool
|
||||
|
||||
// This function is only valid for local profiles.
|
||||
if profile.Source != SourceLocal || profile.PresentationPath == "" {
|
||||
return fmt.Errorf("tried to update metadata for non-local or non-path profile %s", profile.ScopedID())
|
||||
}
|
||||
|
||||
// Save the profile when finished, if needed.
|
||||
save := false
|
||||
defer func() {
|
||||
if save {
|
||||
err := profile.Save()
|
||||
if err != nil {
|
||||
log.Warningf("profile: failed to save %s after metadata update: %s", profile.ScopedID(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Get binary name from linked path.
|
||||
// Get binary name from PresentationPath.
|
||||
newName, err := osdetail.GetBinaryNameFromSystem(profile.PresentationPath)
|
||||
if err != nil {
|
||||
switch {
|
||||
@@ -503,25 +503,26 @@ func (profile *Profile) updateMetadataFromSystem(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get filename of linked path for comparison.
|
||||
filename := filepath.Base(profile.PresentationPath)
|
||||
// Apply new data to profile.
|
||||
func() {
|
||||
// Lock profile for applying metadata.
|
||||
profile.Lock()
|
||||
defer profile.Unlock()
|
||||
|
||||
// TODO: Theoretically, the generated name from the system could be identical
|
||||
// to the filename. This would mean that the worker is triggered every time
|
||||
// the profile is freshly loaded.
|
||||
if newName == filename {
|
||||
// As a quick fix, append a space to the name.
|
||||
newName += " "
|
||||
}
|
||||
// Apply new name if it changed.
|
||||
if profile.Name != newName {
|
||||
profile.Name = newName
|
||||
changed = true
|
||||
}
|
||||
}()
|
||||
|
||||
// Lock profile for applying metadata.
|
||||
profile.Lock()
|
||||
defer profile.Unlock()
|
||||
|
||||
// Apply new name if it changed.
|
||||
if profile.Name != newName {
|
||||
profile.Name = newName
|
||||
save = true
|
||||
// If anything changed, save the profile.
|
||||
// profile.Lock must not be held!
|
||||
if changed {
|
||||
err := profile.Save()
|
||||
if err != nil {
|
||||
log.Warningf("profile: failed to save %s after metadata update: %s", profile.ScopedID(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -110,20 +110,40 @@ func GetSpecialProfile(id string, path string) ( //nolint:gocognit
|
||||
}
|
||||
|
||||
// Get special profile from DB and check if it needs a reset.
|
||||
var created bool
|
||||
profile, err = getProfile(scopedID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, database.ErrNotFound) {
|
||||
log.Warningf("profile: failed to get special profile %s: %s", id, err)
|
||||
switch {
|
||||
case err == nil:
|
||||
// Reset profile if needed.
|
||||
if specialProfileNeedsReset(profile) {
|
||||
profile = createSpecialProfile(id, path)
|
||||
created = true
|
||||
}
|
||||
case !errors.Is(err, database.ErrNotFound):
|
||||
// Warn when fetching from DB fails, and create new profile as fallback.
|
||||
log.Warningf("profile: failed to get special profile %s: %s", id, err)
|
||||
fallthrough
|
||||
default:
|
||||
// Create new profile if it does not exist (or failed to load).
|
||||
profile = createSpecialProfile(id, path)
|
||||
} else if specialProfileNeedsReset(profile) {
|
||||
log.Debugf("profile: resetting special profile %s", id)
|
||||
profile = createSpecialProfile(id, path)
|
||||
created = true
|
||||
}
|
||||
// Check if creating the special profile was successful.
|
||||
if profile == nil {
|
||||
return nil, errors.New("given ID is not a special profile ID")
|
||||
}
|
||||
|
||||
// Update metadata
|
||||
changed := updateSpecialProfileMetadata(profile, path)
|
||||
|
||||
// Save if created or changed.
|
||||
if created || changed {
|
||||
err := profile.Save()
|
||||
if err != nil {
|
||||
log.Warningf("profile: failed to save special profile %s: %s", scopedID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare profile for first use.
|
||||
|
||||
// If we are refetching, assign the layered profile from the previous version.
|
||||
@@ -144,7 +164,7 @@ func GetSpecialProfile(id string, path string) ( //nolint:gocognit
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
func updateSpecialProfileMetadata(profile *Profile, binaryPath string) (ok, changed bool) {
|
||||
func updateSpecialProfileMetadata(profile *Profile, binaryPath string) (changed bool) {
|
||||
// Get new profile name and check if profile is applicable to special handling.
|
||||
var newProfileName, newDescription string
|
||||
switch profile.ID {
|
||||
@@ -170,7 +190,7 @@ func updateSpecialProfileMetadata(profile *Profile, binaryPath string) (ok, chan
|
||||
newProfileName = PortmasterNotifierProfileName
|
||||
newDescription = PortmasterNotifierProfileDescription
|
||||
default:
|
||||
return false, false
|
||||
return false
|
||||
}
|
||||
|
||||
// Update profile name if needed.
|
||||
@@ -191,7 +211,7 @@ func updateSpecialProfileMetadata(profile *Profile, binaryPath string) (ok, chan
|
||||
changed = true
|
||||
}
|
||||
|
||||
return true, changed
|
||||
return changed
|
||||
}
|
||||
|
||||
func createSpecialProfile(profileID string, path string) *Profile {
|
||||
@@ -203,6 +223,13 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
||||
PresentationPath: path,
|
||||
})
|
||||
|
||||
case UnsolicitedProfileID:
|
||||
return New(&Profile{
|
||||
ID: UnsolicitedProfileID,
|
||||
Source: SourceLocal,
|
||||
PresentationPath: path,
|
||||
})
|
||||
|
||||
case SystemProfileID:
|
||||
return New(&Profile{
|
||||
ID: SystemProfileID,
|
||||
@@ -306,7 +333,7 @@ func specialProfileNeedsReset(profile *Profile) bool {
|
||||
|
||||
switch profile.ID {
|
||||
case SystemResolverProfileID:
|
||||
return canBeUpgraded(profile, "20.11.2021")
|
||||
return canBeUpgraded(profile, "21.10.2022")
|
||||
case PortmasterAppProfileID:
|
||||
return canBeUpgraded(profile, "8.9.2021")
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user