diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 295f029..37f650f 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -44,17 +44,21 @@ func StartYaraHunter(opts *config.Options, config *config.Config, newwg *sync.Wa func runOnce(opts *config.Options, config *config.Config) { var jsonOutput IOCWriter - yaraRules, err := yararules.New(*opts.RulesPath).Compile(constants.Filescan, *opts.FailOnCompileWarning) + yaraRules := yararules.New(*opts.RulesPath) + err := yaraRules.Compile(constants.Filescan, *opts.FailOnCompileWarning) if err != nil { - log.Errorf("error compiling yara rules: %s", err) + log.Errorf("error in runOnce compiling yara rules: %s", err) return } - scanner, err := scan.New(opts, config, yaraRules) + yaraScanner, err := yaraRules.NewScanner() if err != nil { - log.Fatalf("error creating scanner: %s", err) + log.Error("error in runOnce creating yara scanner:", err) return } + + scanner := scan.New(opts, config, yaraScanner) + // Scan container image for IOC if len(*opts.ImageName) > 0 { log.Info("Scanning image %s for IOC...\n", *opts.ImageName) diff --git a/pkg/scan/process_image.go b/pkg/scan/process_image.go index 9380e2e..bfab4d5 100644 --- a/pkg/scan/process_image.go +++ b/pkg/scan/process_image.go @@ -15,7 +15,6 @@ import ( "path/filepath" "strings" "syscall" - "time" "fmt" @@ -193,9 +192,12 @@ func ScanFile(s *Scanner, f *os.File, iocs *[]output.IOCFound, layer string) err {"filepath", filepath.ToSlash(f.Name())}, {"extension", filepath.Ext(f.Name())}, } + + yrScanner := s.YaraScanner + yrScanner.SetCallback(&matches) for _, v := range variables { if v.value != nil { - if err = s.Rules.DefineVariable(v.name, v.value); err != nil { + if err = yrScanner.DefineVariable(v.name, v.value); err != nil { return filepath.SkipDir } } @@ -216,7 +218,7 @@ func ScanFile(s *Scanner, f *os.File, iocs *[]output.IOCFound, layer string) err log.Debug("\nyara: %v: Skipping large file, size=%v, max_size=%v", fileName, fi.Size(), *s.MaximumFileSize) return nil } - err = s.Rules.ScanFileDescriptor(f.Fd(), 0, 1*time.Minute, &matches) + err = yrScanner.ScanFileDescriptor(f.Fd()) if err != nil { fmt.Println("Scan File Descriptor error, trying alternative", err) var buf []byte @@ -225,7 +227,7 @@ func ScanFile(s *Scanner, f *os.File, iocs *[]output.IOCFound, layer string) err fileName, err.Error()) return filepath.SkipDir } - err = s.Rules.ScanMem(buf, 0, 1*time.Minute, &matches) + err = yrScanner.ScanMem(buf) if err != nil { fmt.Println("Scan File Mmory Error", err) return filepath.SkipDir diff --git a/pkg/scan/scanner.go b/pkg/scan/scanner.go index dfb3447..bd2a408 100644 --- a/pkg/scan/scanner.go +++ b/pkg/scan/scanner.go @@ -2,17 +2,17 @@ package scan import ( "github.com/deepfence/YaraHunter/pkg/config" - yara "github.com/hillu/go-yara/v4" + "github.com/hillu/go-yara/v4" ) -func New(opts *config.Options, yaraconfig *config.Config, yr *yara.Rules) (*Scanner, error) { - return &Scanner{opts, yaraconfig, yr}, nil +func New(opts *config.Options, yaraconfig *config.Config, yaraScannerIn *yara.Scanner) *Scanner { + return &Scanner{opts, yaraconfig, yaraScannerIn} } type Scanner struct { *config.Options *config.Config - Rules *yara.Rules + YaraScanner *yara.Scanner } func (s *Scanner) SetImageName(imageName string) { diff --git a/pkg/server/grpc.go b/pkg/server/grpc.go index 81f38a0..ea67c64 100644 --- a/pkg/server/grpc.go +++ b/pkg/server/grpc.go @@ -16,7 +16,6 @@ import ( "github.com/deepfence/YaraHunter/pkg/scan" yararules "github.com/deepfence/YaraHunter/pkg/yararules" pb "github.com/deepfence/agent-plugins-grpc/proto" - yara "github.com/hillu/go-yara/v4" log "github.com/sirupsen/logrus" "google.golang.org/grpc" ) @@ -37,7 +36,7 @@ func init() { type gRPCServer struct { options *config.Options yaraConfig *config.Config - yaraRules *yara.Rules + yaraRules *yararules.YaraRules plugin_name string pb.UnimplementedMalwareScannerServer pb.UnimplementedAgentPluginServer @@ -69,10 +68,14 @@ func (s *gRPCServer) FindMalwareInfo(c context.Context, r *pb.MalwareRequest) (* log.Infof("request to scan %+v", r) - scanner, err := scan.New(s.options, s.yaraConfig, s.yaraRules) + yaraScanner, err := s.yaraRules.NewScanner() if err != nil { + log.Error("Failed to create Yara Scanner, error:", err) return } + + scanner := scan.New(s.options, s.yaraConfig, yaraScanner) + var malwares chan output.IOCFound trim := false if r.GetPath() != "" { @@ -132,12 +135,14 @@ func RunGrpcServer(opts *config.Options, config *config.Config, plugin_name stri if err != nil { return err } + // compile yara rules - impl.yaraRules, err = yararules.New(*opts.RulesPath). - Compile(constants.Filescan, *opts.FailOnCompileWarning) + impl.yaraRules = yararules.New(*opts.RulesPath) + err = impl.yaraRules.Compile(constants.Filescan, *opts.FailOnCompileWarning) if err != nil { return err } + pb.RegisterAgentPluginServer(s, impl) pb.RegisterMalwareScannerServer(s, impl) pb.RegisterScannersServer(s, impl) diff --git a/pkg/yararules/yara.go b/pkg/yararules/yara.go index 1f3ce61..b607bc8 100644 --- a/pkg/yararules/yara.go +++ b/pkg/yararules/yara.go @@ -7,8 +7,8 @@ import ( "path/filepath" "strings" "sync" + "time" - // "github.com/aws/aws-sdk-go/aws/session" "github.com/deepfence/YaraHunter/constants" yara "github.com/hillu/go-yara/v4" log "github.com/sirupsen/logrus" @@ -27,9 +27,11 @@ var extvars = map[int]map[string]interface{}{ }, } +// NOTE:::Do not expose the rules +// Instead add a wrapper here protected with mutex type YaraRules struct { RulesPath string - YaraRules *yara.Rules + rules *yara.Rules ruleMutex sync.Mutex } @@ -37,46 +39,31 @@ func New(rulePath string) *YaraRules { return &YaraRules{RulesPath: rulePath} } -func (yr *YaraRules) SetYaraRule(rules *yara.Rules) { - yr.ruleMutex.Lock() - defer yr.ruleMutex.Unlock() - yr.YaraRules = rules -} - -func (yr *YaraRules) GetYaraRule() *yara.Rules { - yr.ruleMutex.Lock() - defer yr.ruleMutex.Unlock() - return yr.YaraRules -} - -func (yr *YaraRules) Compile(purpose int, failOnCompileWarning bool) (*yara.Rules, error) { +// Not thread safe function.Must only be called during the init. +func (yr *YaraRules) Compile(purpose int, failOnCompileWarning bool) error { var c *yara.Compiler - //log.Info("including yara rule file ") var err error if c, err = yara.NewCompiler(); err != nil { - return nil, err + return err } - //log.Info("including yara rule file ") - for k, v := range extvars[purpose] { if err = c.DefineVariable(k, v); err != nil { - return nil, err + return err } } - //log.Info("including yara rule file ") - //log.Info("including yara rule file ") - paths, err := getRuleFiles(yr.RulesPath) if err != nil { log.Error(err) - return nil, err + return err } + if len(paths) == 0 { - return nil, errors.New("no Yara rule files found") + return errors.New("no Yara rule files found") } + for _, path := range paths { // We use the include callback function to actually read files // because yr_compiler_add_string() does not accept a file @@ -84,33 +71,75 @@ func (yr *YaraRules) Compile(purpose int, failOnCompileWarning bool) (*yara.Rule log.Infof("including yara rule file %s", path) if err = c.AddString(fmt.Sprintf(`include "%s"`, path), ""); err != nil { log.Errorf("error obtained %s", err) - return nil, err + return err } } + purposeStr := [...]string{"file", "process"}[purpose] - yr.YaraRules, err = c.GetRules() + yr.rules, err = c.GetRules() if err != nil { for _, e := range c.Errors { log.Errorf("YARA compiler error in %s ruleset: %s:%d %s", purposeStr, e.Filename, e.Line, e.Text) } - return nil, fmt.Errorf("%d YARA compiler errors(s) found, rejecting %s ruleset", + return fmt.Errorf("%d YARA compiler errors(s) found, rejecting %s ruleset", len(c.Errors), purposeStr) } + if len(c.Warnings) > 0 { for _, w := range c.Warnings { log.Warn("YARA compiler warning in %s ruleset: %s:%d %s", purposeStr, w.Filename, w.Line, w.Text) } if failOnCompileWarning { - return nil, fmt.Errorf("%d YARA compiler warning(s) found, rejecting %s ruleset", + return fmt.Errorf("%d YARA compiler warning(s) found, rejecting %s ruleset", len(c.Warnings), purposeStr) } } - if len(yr.YaraRules.GetRules()) == 0 { - return nil, errors.New("No YARA rules defined") + + if len(yr.rules.GetRules()) == 0 { + return errors.New("No YARA rules defined") } - return yr.YaraRules, nil + return nil +} + +func (yr *YaraRules) NewScanner() (*yara.Scanner, error) { + + yr.ruleMutex.Lock() + defer yr.ruleMutex.Unlock() + + scanner, err := yara.NewScanner(yr.rules) + if err != nil { + return nil, err + } + scanner.SetTimeout(1 * time.Minute) + scanner.SetFlags(0) + return scanner, nil +} + +func (yr *YaraRules) DefineVariable(name string, value any) error { + yr.ruleMutex.Lock() + defer yr.ruleMutex.Unlock() + + return yr.rules.DefineVariable(name, value) +} + +func (yr *YaraRules) ScanFileDescriptor(fd uintptr, flags yara.ScanFlags, + timeout time.Duration, cb yara.ScanCallback) error { + + yr.ruleMutex.Lock() + defer yr.ruleMutex.Unlock() + + return yr.rules.ScanFileDescriptor(fd, flags, timeout, cb) +} + +func (yr *YaraRules) ScanMem(buf []byte, flags yara.ScanFlags, + timeout time.Duration, cb yara.ScanCallback) error { + + yr.ruleMutex.Lock() + defer yr.ruleMutex.Unlock() + + return yr.rules.ScanMem(buf, flags, timeout, cb) } func getRuleFiles(rulesPath string) ([]string, error) {