Skip to content

Commit

Permalink
Merge pull request #1364 from safing/feature/unix-tag-improvements
Browse files Browse the repository at this point in the history
Improve unix process matching tags
  • Loading branch information
dhaavi authored Nov 22, 2023
2 parents 5397f41 + d058d86 commit 1a0be5f
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 33 deletions.
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

0 comments on commit 1a0be5f

Please sign in to comment.