Add support for cmdline matching and add basic interpreter support

This commit is contained in:
Patrick Pacher
2022-10-10 15:28:57 +02:00
committed by Daniel
parent b3007b71db
commit 77c0d954a9
11 changed files with 338 additions and 66 deletions

View File

@@ -68,6 +68,15 @@ type Process struct {
ExecHashes map[string]string
}
func (p *Process) GetTag(tagID string) (profile.Tag, bool) {
for _, t := range p.Tags {
if t.Key == tagID {
return t, true
}
}
return profile.Tag{}, false
}
// Profile returns the assigned layered profile.
func (p *Process) Profile() *profile.LayeredProfile {
if p == nil {
@@ -226,11 +235,13 @@ func loadProcess(ctx context.Context, pid int) (*Process, error) {
_, process.ExecName = filepath.Split(process.Path)
// Current working directory
// net yet implemented for windows
// new.Cwd, err = pInfo.Cwd()
// if err != nil {
// log.Warningf("process: failed to get Cwd: %w", err)
// }
// not yet implemented for windows
if runtime.GOOS != "windows" {
process.Cwd, err = pInfo.Cwd()
if err != nil {
log.Warningf("process: failed to get Cwd: %w", err)
}
}
// Command line arguments
process.CmdLine, err = pInfo.CmdlineWithContext(ctx)
@@ -292,3 +303,6 @@ func (md *MatchingData) Path() string { return md.p.Path }
// MatchingPath returns process.MatchingPath.
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 }

View File

@@ -69,22 +69,21 @@ func (h *AppImageHandler) AddTags(p *process.Process) {
// CreateProfile creates a profile based on the tags of the process.
// Returns nil to skip.
func (h *AppImageHandler) CreateProfile(p *process.Process) *profile.Profile {
for _, tag := range p.Tags {
if tag.Key == appImagePathTagKey {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(tag.Value),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{
{
Type: profile.FingerprintTypePathID,
Operation: profile.FingerprintOperationEqualsID,
Value: tag.Value, // Value of appImagePathTagKey.
},
if tag, ok := p.GetTag(appImagePathTagKey); ok {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(tag.Value),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{
{
Type: profile.FingerprintTypePathID,
Operation: profile.FingerprintOperationEqualsID,
Value: tag.Value, // Value of appImagePathTagKey.
},
})
}
},
})
}
return nil
}

View File

@@ -0,0 +1,220 @@
package tags
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"unicode/utf8"
"github.com/google/shlex"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
)
func init() {
if err := process.RegisterTagHandler(new(InterpHandler)); err != nil {
panic(err)
}
}
type interpType struct {
process.TagDescription
Regex *regexp.Regexp
}
var knownInterperters = []interpType{
{
TagDescription: process.TagDescription{
ID: "python-script",
Name: "Python Script",
},
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$`),
},
{
TagDescription: process.TagDescription{
ID: "perl-script",
Name: "Perl Script",
},
Regex: regexp.MustCompile(`^(/usr)?/bin/perl$`),
},
{
TagDescription: process.TagDescription{
ID: "ruby-script",
Name: "Ruby Script",
},
Regex: regexp.MustCompile(`^(/usr)?/bin/ruby$`),
},
{
TagDescription: process.TagDescription{
ID: "nodejs-script",
Name: "NodeJS Script",
},
Regex: regexp.MustCompile(`^(/usr)?/bin/node(js)?$`),
},
/*
While similar to nodejs, electron is a bit harder as it uses a multiple processes
like Chromium and thus a interpreter match on them will but those processes into
different groups.
I'm still not sure how this could work in the future. Maybe processes should try to
inherit the profile of the parents if there is no profile that matches the current one....
{
TagDescription: process.TagDescription{
ID: "electron-app",
Name: "Electron App",
},
Regex: regexp.MustCompile(`^(/usr)?/bin/electron([0-9]+)?$`),
},
*/
}
func fileMustBeUTF8(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
// read the first chunk of bytes
buf := new(bytes.Buffer)
size, _ := io.CopyN(buf, f, 128)
if size == 0 {
return false
}
b := buf.Bytes()[:size]
for len(b) > 0 {
r, runeSize := utf8.DecodeRune(b)
if r == utf8.RuneError {
return false
}
b = b[runeSize:]
}
return true
}
type InterpHandler struct{}
func (h *InterpHandler) Name() string {
return "Interpreter"
}
func (h *InterpHandler) TagDescriptions() []process.TagDescription {
l := make([]process.TagDescription, len(knownInterperters))
for idx, it := range knownInterperters {
l[idx] = it.TagDescription
}
return l
}
func (h *InterpHandler) CreateProfile(p *process.Process) *profile.Profile {
for _, it := range knownInterperters {
if tag, ok := p.GetTag(it.ID); ok {
// we can safely ignore the error
args, err := shlex.Split(p.CmdLine)
if err != nil {
// this should not happen since we already called shlex.Split()
// when adding the tag. Though, make the linter happy and bail out
return nil
}
// if arg0 is the interpreter name itself strip it away
// and use the next one
if it.Regex.MatchString(args[0]) && len(args) > 1 {
args = args[1:]
}
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: fmt.Sprintf("%s: %s", it.Name, args[0]),
PresentationPath: tag.Value,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{
{
Type: profile.FingerprintTypeTagID,
Operation: profile.FingerprintOperationEqualsID,
Key: it.ID,
Value: tag.Value,
},
},
})
}
}
return nil
}
func (h *InterpHandler) AddTags(p *process.Process) {
// check if we have a matching interpreter
var matched interpType
for _, it := range knownInterperters {
if it.Regex.MatchString(p.Path) {
matched = it
}
}
// zero value means we did not find any interpreter matches.
if matched.ID == "" {
return
}
args, err := shlex.Split(p.CmdLine)
if err != nil {
// give up if we failed to parse the command line
return
}
// if args[0] matches the interpreter name we expect
// the second arg to be a file-name
if matched.Regex.MatchString(args[0]) {
if len(args) == 1 {
// there's no argument given, this is likely an interactive
// interpreter session
return
}
filePath := args[1]
if !filepath.IsAbs(filePath) {
filePath = filepath.Join(
p.Cwd,
filePath,
)
}
// TODO(ppacher): there could be some other arguments as well
// so it may be better to scan the whole command line for a path to a UTF8
// file and use that one.
if !fileMustBeUTF8(filePath) {
return
}
p.Tags = append(p.Tags, profile.Tag{
Key: matched.ID,
Value: filePath,
})
return
}
// we know that this process is interpreted by some known interpreter but args[0]
// does not contain the path to the interpreter.
p.Tags = append(p.Tags, profile.Tag{
Key: matched.ID,
Value: args[0],
})
}

View File

@@ -77,24 +77,23 @@ func (h *SVCHostTagHandler) AddTags(p *process.Process) {
// CreateProfile creates a profile based on the tags of the process.
// Returns nil to skip.
func (h *SVCHostTagHandler) CreateProfile(p *process.Process) *profile.Profile {
for _, tag := range p.Tags {
if tag.Key == svchostTagKey {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: "Windows Service: " + osdetail.GenerateBinaryNameFromPath(tag.Value),
Icon: `C:\Windows\System32\@WLOGO_48x48.png`,
IconType: profile.IconTypeFile,
UsePresentationPath: false,
Fingerprints: []profile.Fingerprint{
profile.Fingerprint{
Type: profile.FingerprintTypeTagID,
Key: tag.Key,
Operation: profile.FingerprintOperationEqualsID,
Value: tag.Value,
},
if tag, ok := p.GetTag(svchostTagKey); ok {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: "Windows Service: " + osdetail.GenerateBinaryNameFromPath(tag.Value),
Icon: `C:\Windows\System32\@WLOGO_48x48.png`,
IconType: profile.IconTypeFile,
UsePresentationPath: false,
Fingerprints: []profile.Fingerprint{
profile.Fingerprint{
Type: profile.FingerprintTypeTagID,
Key: tag.Key,
Operation: profile.FingerprintOperationEqualsID,
Value: tag.Value,
},
})
}
},
})
}
return nil
}

View File

@@ -101,23 +101,22 @@ func (h *WinStoreHandler) AddTags(p *process.Process) {
// CreateProfile creates a profile based on the tags of the process.
// Returns nil to skip.
func (h *WinStoreHandler) CreateProfile(p *process.Process) *profile.Profile {
for _, tag := range p.Tags {
if tag.Key == winStoreAppNameTagKey {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(tag.Value),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{
{
Type: profile.FingerprintTypeTagID,
Key: tag.Key,
Operation: profile.FingerprintOperationEqualsID,
Value: tag.Value, // Value of appImagePathTagKey.
},
if tag, ok := p.GetTag(winStoreAppNameTagKey); ok {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(tag.Value),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{
{
Type: profile.FingerprintTypeTagID,
Key: tag.Key,
Operation: profile.FingerprintOperationEqualsID,
Value: tag.Value, // Value of appImagePathTagKey.
},
})
}
},
})
}
return nil
}