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

Improve unix process matching tags #1364

Merged
merged 3 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 122 additions & 25 deletions process/tags/appimage_unix.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -72,18 +124,63 @@ 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.
},
},
})
}

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
}
87 changes: 87 additions & 0 deletions process/tags/flatpak_unix.go
Original file line number Diff line number Diff line change
@@ -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
}
31 changes: 23 additions & 8 deletions process/tags/interpreter_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -24,7 +26,8 @@ func init() {
type interpType struct {
process.TagDescription

Regex *regexp.Regexp
Extensions []string
Regex *regexp.Regexp
}

var knownInterperters = []interpType{
Expand All @@ -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
Expand Down Expand Up @@ -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,
},
},
Expand Down
Loading