From 130cc40fea6e7045eb61b9075eb8737f0384911f Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 17 Nov 2023 12:21:10 +0100 Subject: [PATCH 1/3] Improve AppImage matching, even without ENV vars --- process/tags/appimage_unix.go | 147 ++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 25 deletions(-) diff --git a/process/tags/appimage_unix.go b/process/tags/appimage_unix.go index 375482421..b2f47750b 100644 --- a/process/tags/appimage_unix.go +++ b/process/tags/appimage_unix.go @@ -1,8 +1,13 @@ package tags import ( + "bufio" + "bytes" + "os" + "regexp" "strings" + "github.com/safing/portbase/log" "github.com/safing/portbase/utils/osdetail" "github.com/safing/portmaster/process" "github.com/safing/portmaster/profile" @@ -16,8 +21,14 @@ func init() { } const ( - appImageName = "AppImage" - appImagePathTagKey = "app-image-path" + appImageName = "AppImage" + appImagePathTagKey = "app-image-path" + appImageMountIDTagKey = "app-image-mount-id" +) + +var ( + appImageMountDirRegex = regexp.MustCompile(`^/tmp/.mount_[^/]+`) + appImageMountNameExtractRegex = regexp.MustCompile(`^[A-Za-z0-9]+`) ) // AppImageHandler handles AppImage processes on Unix systems. @@ -34,36 +45,77 @@ func (h *AppImageHandler) TagDescriptions() []process.TagDescription { return []process.TagDescription{ { ID: appImagePathTagKey, - Name: "App Image Path", + Name: "AppImage Path", Description: "Path to the app image file itself.", }, + { + ID: appImageMountIDTagKey, + Name: "AppImage Mount ID", + Description: "Extracted ID from the AppImage mount name. Use AppImage Path instead, if available.", + }, } } // AddTags adds tags to the given process. func (h *AppImageHandler) AddTags(p *process.Process) { - // Get and verify AppImage location. - appImageLocation, ok := p.Env["APPIMAGE"] - if !ok { - return - } - appImageMountDir, ok := p.Env["APPDIR"] - if !ok { - return - } - // Check if the process path is in the mount dir. - if !strings.HasPrefix(p.Path, appImageMountDir) { - return - } + // Detect app image path via ENV vars. + func() { + // Get and verify AppImage location. + appImageLocation, ok := p.Env["APPIMAGE"] + if !ok || appImageLocation == "" { + return + } + appImageMountDir, ok := p.Env["APPDIR"] + if !ok || appImageMountDir == "" { + return + } + // Check if the process path is in the mount dir. + if !strings.HasPrefix(p.Path, appImageMountDir) { + return + } - // Add matching path for regular profile matching. - p.MatchingPath = appImageLocation + // Add matching path for regular profile matching. + p.MatchingPath = appImageLocation + + // Add app image tag. + p.Tags = append(p.Tags, profile.Tag{ + Key: appImagePathTagKey, + Value: appImageLocation, + }) + }() - // Add app image tags. - p.Tags = append(p.Tags, profile.Tag{ - Key: appImagePathTagKey, - Value: appImageLocation, - }) + // Detect app image mount point. + func() { + // Check if binary path matches app image mount pattern. + mountDir := appImageMountDirRegex.FindString(p.Path) + if mountDir == "" { + return + } + + // Get mount name of mount dir. + // Also, this confirm this is actually a mounted dir. + mountName, err := getAppImageMountName(mountDir) + if err != nil { + log.Debugf("process/tags: failed to get mount name: %s", err) + return + } + if mountName == "" { + return + } + + // Extract a usable ID from the mount name. + mountName, _ = strings.CutPrefix(mountName, "gearlever_") + mountName = appImageMountNameExtractRegex.FindString(mountName) + if mountName == "" { + return + } + + // Add app image tag. + p.Tags = append(p.Tags, profile.Tag{ + Key: appImageMountIDTagKey, + Value: mountName, + }) + }() } // CreateProfile creates a profile based on the tags of the process. @@ -72,12 +124,13 @@ func (h *AppImageHandler) CreateProfile(p *process.Process) *profile.Profile { if tag, ok := p.GetTag(appImagePathTagKey); ok { return profile.New(&profile.Profile{ Source: profile.SourceLocal, - Name: osdetail.GenerateBinaryNameFromPath(tag.Value), + Name: osdetail.GenerateBinaryNameFromPath(p.Path), PresentationPath: p.Path, UsePresentationPath: true, Fingerprints: []profile.Fingerprint{ { - Type: profile.FingerprintTypePathID, + Type: profile.FingerprintTypeTagID, + Key: tag.Key, Operation: profile.FingerprintOperationEqualsID, Value: tag.Value, // Value of appImagePathTagKey. }, @@ -85,5 +138,49 @@ func (h *AppImageHandler) CreateProfile(p *process.Process) *profile.Profile { }) } + if tag, ok := p.GetTag(appImageMountIDTagKey); ok { + return profile.New(&profile.Profile{ + Source: profile.SourceLocal, + Name: osdetail.GenerateBinaryNameFromPath(p.Path), + PresentationPath: p.Path, + UsePresentationPath: true, + Fingerprints: []profile.Fingerprint{ + { + Type: profile.FingerprintTypeTagID, + Key: tag.Key, + Operation: profile.FingerprintOperationEqualsID, + Value: tag.Value, // Value of appImageMountIDTagKey. + }, + }, + }) + } + return nil } + +func getAppImageMountName(mountPoint string) (mountName string, err error) { + // Get mounts. + data, err := os.ReadFile("/proc/mounts") + if err != nil { + return "", err + } + + scanner := bufio.NewScanner(bytes.NewReader(data)) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) >= 2 { + switch { + case fields[1] != mountPoint: + case !strings.HasSuffix(strings.ToLower(fields[0]), ".appimage"): + default: + // Found AppImage mount! + return fields[0], nil + } + } + } + if scanner.Err() != nil { + return "", scanner.Err() + } + + return "", nil +} From 4940280737e715b7e355726f5f3c00331824ce93 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 17 Nov 2023 12:21:34 +0100 Subject: [PATCH 2/3] Improve Interpreter matching --- process/tags/interpreter_unix.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/process/tags/interpreter_unix.go b/process/tags/interpreter_unix.go index c254f4a75..a5ba2c18b 100644 --- a/process/tags/interpreter_unix.go +++ b/process/tags/interpreter_unix.go @@ -7,10 +7,12 @@ import ( "os" "path/filepath" "regexp" + "strings" "unicode/utf8" "github.com/google/shlex" + "github.com/safing/portbase/utils/osdetail" "github.com/safing/portmaster/process" "github.com/safing/portmaster/profile" ) @@ -24,7 +26,8 @@ func init() { type interpType struct { process.TagDescription - Regex *regexp.Regexp + Extensions []string + Regex *regexp.Regexp } var knownInterperters = []interpType{ @@ -33,35 +36,40 @@ var knownInterperters = []interpType{ ID: "python-script", Name: "Python Script", }, - Regex: regexp.MustCompile(`^(/usr)?/bin/python[23]\.[0-9]+$`), + Extensions: []string{".py", ".py2", ".py3"}, + Regex: regexp.MustCompile(`^(/usr)?/bin/python[23](\.[0-9]+)?$`), }, { TagDescription: process.TagDescription{ ID: "shell-script", Name: "Shell Script", }, - Regex: regexp.MustCompile(`^(/usr)?/bin/(ba|k|z|a)?sh$`), + Extensions: []string{".sh", ".bash", ".ksh", ".zsh", ".ash"}, + Regex: regexp.MustCompile(`^(/usr)?/bin/(ba|k|z|a)?sh$`), }, { TagDescription: process.TagDescription{ ID: "perl-script", Name: "Perl Script", }, - Regex: regexp.MustCompile(`^(/usr)?/bin/perl$`), + Extensions: []string{".pl"}, + Regex: regexp.MustCompile(`^(/usr)?/bin/perl$`), }, { TagDescription: process.TagDescription{ ID: "ruby-script", Name: "Ruby Script", }, - Regex: regexp.MustCompile(`^(/usr)?/bin/ruby$`), + Extensions: []string{".rb"}, + Regex: regexp.MustCompile(`^(/usr)?/bin/ruby$`), }, { TagDescription: process.TagDescription{ ID: "nodejs-script", Name: "NodeJS Script", }, - Regex: regexp.MustCompile(`^(/usr)?/bin/node(js)?$`), + Extensions: []string{".js"}, + Regex: regexp.MustCompile(`^(/usr)?/bin/node(js)?$`), }, /* While similar to nodejs, electron is a bit harder as it uses a multiple processes @@ -148,16 +156,23 @@ func (h *InterpHandler) CreateProfile(p *process.Process) *profile.Profile { args = args[1:] } + // Create a nice script name from filename. + scriptName := filepath.Base(args[0]) + for _, ext := range it.Extensions { + scriptName, _ = strings.CutSuffix(scriptName, ext) + } + scriptName = osdetail.GenerateBinaryNameFromPath(scriptName) + return profile.New(&profile.Profile{ Source: profile.SourceLocal, - Name: fmt.Sprintf("%s: %s", it.Name, args[0]), + Name: fmt.Sprintf("%s: %s", it.Name, scriptName), PresentationPath: tag.Value, UsePresentationPath: true, Fingerprints: []profile.Fingerprint{ { Type: profile.FingerprintTypeTagID, - Operation: profile.FingerprintOperationEqualsID, Key: it.ID, + Operation: profile.FingerprintOperationEqualsID, Value: tag.Value, }, }, From d058d86dcaf130864bcfd067058f6b8d4815b13b Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 17 Nov 2023 12:21:46 +0100 Subject: [PATCH 3/3] Add support for matching flatpaks --- process/tags/flatpak_unix.go | 87 ++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 process/tags/flatpak_unix.go diff --git a/process/tags/flatpak_unix.go b/process/tags/flatpak_unix.go new file mode 100644 index 000000000..1c5bf656f --- /dev/null +++ b/process/tags/flatpak_unix.go @@ -0,0 +1,87 @@ +package tags + +import ( + "strings" + + "github.com/safing/portbase/utils/osdetail" + "github.com/safing/portmaster/process" + "github.com/safing/portmaster/profile" +) + +func init() { + err := process.RegisterTagHandler(new(flatpakHandler)) + if err != nil { + panic(err) + } +} + +const ( + flatpakName = "Flatpak" + flatpakIDTagKey = "flatpak-id" +) + +// flatpakHandler handles flatpak processes on Unix systems. +type flatpakHandler struct{} + +// Name returns the tag handler name. +func (h *flatpakHandler) Name() string { + return flatpakName +} + +// TagDescriptions returns a list of all possible tags and their description +// of this handler. +func (h *flatpakHandler) TagDescriptions() []process.TagDescription { + return []process.TagDescription{ + { + ID: flatpakIDTagKey, + Name: "Flatpak ID", + Description: "ID of the flatpak.", + }, + } +} + +// AddTags adds tags to the given process. +func (h *flatpakHandler) AddTags(p *process.Process) { + // Check if binary lives in the /app space. + if !strings.HasPrefix(p.Path, "/app/") { + return + } + + // Get the Flatpak ID. + flatpakID, ok := p.Env["FLATPAK_ID"] + if !ok || flatpakID == "" { + return + } + + // Add matching path for regular profile matching. + p.MatchingPath = p.Path + + // Add app image tag. + p.Tags = append(p.Tags, profile.Tag{ + Key: flatpakIDTagKey, + Value: flatpakID, + }) +} + +// CreateProfile creates a profile based on the tags of the process. +// Returns nil to skip. +func (h *flatpakHandler) CreateProfile(p *process.Process) *profile.Profile { + if tag, ok := p.GetTag(flatpakIDTagKey); ok { + return profile.New(&profile.Profile{ + Source: profile.SourceLocal, + Name: osdetail.GenerateBinaryNameFromPath(p.Path), + PresentationPath: p.Path, + UsePresentationPath: true, + Fingerprints: []profile.Fingerprint{ + { + Type: profile.FingerprintTypeTagID, + Key: tag.Key, + Operation: profile.FingerprintOperationEqualsID, + Value: tag.Value, // Value of flatpakIDTagKey. + }, + }, + }) + } + + return nil +}