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

v2-rules-racecondition: Fixing race condition in YaraHunter #55

Merged
merged 1 commit into from
May 23, 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
12 changes: 8 additions & 4 deletions pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions pkg/scan/process_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"path/filepath"
"strings"
"syscall"
"time"

"fmt"

Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions pkg/scan/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
15 changes: 10 additions & 5 deletions pkg/server/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand Down Expand Up @@ -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() != "" {
Expand Down Expand Up @@ -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)
Expand Down
93 changes: 61 additions & 32 deletions pkg/yararules/yara.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -27,90 +27,119 @@ 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
}

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
// name.
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) {
Expand Down