Skip to content

Commit 0a9fb05

Browse files
authored
Remove scanner pool in favor of single-use scanners (#765)
* Remove scanner pool in favor of single-use scanners Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> * Remove unused flag Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> --------- Signed-off-by: egibs <20933572+egibs@users.noreply.github.com>
1 parent 983bfae commit 0a9fb05

File tree

6 files changed

+4
-215
lines changed

6 files changed

+4
-215
lines changed

cmd/mal/mal.go

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ var (
6363
outputFlag string
6464
profileFlag bool
6565
quantityIncreasesRiskFlag bool
66-
scannersFlag int
6766
statsFlag bool
6867
thirdPartyFlag bool
6968
verboseFlag bool
@@ -250,35 +249,20 @@ func main() {
250249
concurrency = 1
251250
}
252251

253-
maxScanners := scannersFlag
254-
if maxScanners > concurrency {
255-
maxScanners = concurrency
256-
}
257-
258-
var pool *malcontent.ScannerPool
259-
if mc.ScannerPool == nil {
260-
pool, err = malcontent.NewScannerPool(yrs, maxScanners)
261-
if err != nil {
262-
returnCode = ExitInvalidRules
263-
}
264-
}
265-
266252
mc = malcontent.Config{
267253
Concurrency: concurrency,
268254
ExitFirstHit: exitFirstHitFlag,
269255
ExitFirstMiss: exitFirstMissFlag,
270256
IgnoreSelf: ignoreSelfFlag,
271257
IgnoreTags: ignoreTags,
272258
IncludeDataFiles: includeDataFiles,
273-
MaxScanners: maxScanners,
274259
MinFileRisk: minFileRisk,
275260
MinRisk: minRisk,
276261
OCI: ociFlag,
277262
QuantityIncreasesRisk: quantityIncreasesRiskFlag,
278263
Renderer: renderer,
279264
Rules: yrs,
280265
ScanPaths: scanPaths,
281-
ScannerPool: pool,
282266
Stats: statsFlag,
283267
}
284268

@@ -379,12 +363,6 @@ func main() {
379363
Usage: "Increase file risk score based on behavior quantity",
380364
Destination: &quantityIncreasesRiskFlag,
381365
},
382-
&cli.IntFlag{
383-
Name: "scanners",
384-
Value: runtime.NumCPU(),
385-
Usage: "Number of scanners to create",
386-
Destination: &scannersFlag,
387-
},
388366
&cli.BoolFlag{
389367
Name: "stats",
390368
Aliases: []string{"s"},

pkg/action/scan.go

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,6 @@ func scanSinglePath(ctx context.Context, c malcontent.Config, path string, ruleF
5555
yrs = c.Rules
5656
}
5757

58-
var pool *malcontent.ScannerPool
59-
if c.ScannerPool == nil {
60-
pool, err = malcontent.NewScannerPool(yrs, c.MaxScanners)
61-
if err != nil {
62-
return nil, fmt.Errorf("failed to create scanner pool: %w", err)
63-
}
64-
c.ScannerPool = pool
65-
}
66-
67-
var scanner *yarax.Scanner
68-
scanner, err = c.ScannerPool.Get()
69-
if err != nil {
70-
return nil, fmt.Errorf("failed to retrieve scanner: %w", err)
71-
}
72-
defer c.ScannerPool.Put(scanner)
73-
7458
isArchive := archiveRoot != ""
7559
mime := "<unknown>"
7660
kind, err := programkind.File(path)
@@ -91,7 +75,7 @@ func scanSinglePath(ctx context.Context, c malcontent.Config, path string, ruleF
9175
return nil, err
9276
}
9377

94-
mrs, err := scanner.Scan(fc)
78+
mrs, err := yrs.Scan(fc)
9579
if err != nil {
9680
logger.Debug("skipping", slog.Any("error", err))
9781
return &malcontent.FileReport{Path: path, Error: fmt.Sprintf("scan: %v", err)}, nil

pkg/malcontent/malcontent.go

Lines changed: 0 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ package malcontent
55

66
import (
77
"context"
8-
"fmt"
98
"io"
109
"io/fs"
11-
"runtime"
1210
"sync"
13-
"sync/atomic"
1411

1512
yarax "github.com/VirusTotal/yara-x/go"
1613
orderedmap "github.com/wk8/go-ordered-map/v2"
@@ -33,7 +30,6 @@ type Config struct {
3330
IgnoreSelf bool
3431
IgnoreTags []string
3532
IncludeDataFiles bool
36-
MaxScanners int
3733
MinFileRisk int
3834
MinRisk int
3935
OCI bool
@@ -45,7 +41,6 @@ type Config struct {
4541
Rules *yarax.Rules
4642
Scan bool
4743
ScanPaths []string
48-
ScannerPool *ScannerPool
4944
Stats bool
5045
TrimPrefixes []string
5146
}
@@ -148,142 +143,3 @@ type CombinedReport struct {
148143
RemovedFR *FileReport
149144
Score float64
150145
}
151-
152-
// ScannerPool manages a limited pool of YARA scanners.
153-
type ScannerPool struct {
154-
mu sync.Mutex
155-
rules *yarax.Rules
156-
scanners []*yarax.Scanner
157-
available chan *yarax.Scanner
158-
maxScanners int32
159-
currentCount int32
160-
closed atomic.Bool
161-
}
162-
163-
// NewScannerPool creates a new scanner pool with a maximum number of scanners.
164-
func NewScannerPool(rules *yarax.Rules, maxScanners int) (*ScannerPool, error) {
165-
if rules == nil {
166-
return nil, fmt.Errorf("rules cannot be nil")
167-
}
168-
if maxScanners < 1 {
169-
maxScanners = max(1, runtime.GOMAXPROCS(0)/2)
170-
}
171-
172-
// #nosec G115 // ignore converting int to int32
173-
pool := &ScannerPool{
174-
rules: rules,
175-
available: make(chan *yarax.Scanner, maxScanners),
176-
maxScanners: int32(maxScanners),
177-
scanners: make([]*yarax.Scanner, 0, maxScanners),
178-
closed: atomic.Bool{},
179-
}
180-
181-
scanner := yarax.NewScanner(rules)
182-
if scanner == nil {
183-
return nil, fmt.Errorf("failed to create scanner")
184-
}
185-
186-
pool.available <- scanner
187-
atomic.AddInt32(&pool.currentCount, 1)
188-
189-
return pool, nil
190-
}
191-
192-
// createScanner creates a new yarax scanner.
193-
func (p *ScannerPool) createScanner() (*yarax.Scanner, error) {
194-
if atomic.LoadInt32(&p.currentCount) > p.maxScanners/2 {
195-
runtime.GC()
196-
}
197-
198-
if p.rules == nil {
199-
return nil, fmt.Errorf("rules not initialized")
200-
}
201-
202-
scanner := yarax.NewScanner(p.rules)
203-
if scanner == nil {
204-
return nil, fmt.Errorf("failed to create new scanner")
205-
}
206-
207-
if err := p.validateScanner(scanner); err != nil {
208-
scanner.Destroy()
209-
return nil, err
210-
}
211-
212-
return scanner, nil
213-
}
214-
215-
// validateScanner attempts to compile the provided rules.
216-
func (p *ScannerPool) validateScanner(scanner *yarax.Scanner) error {
217-
if scanner == nil {
218-
return fmt.Errorf("nil scanner")
219-
}
220-
_, err := scanner.Scan([]byte("test"))
221-
if err != nil {
222-
return fmt.Errorf("scanner validation failed: %w", err)
223-
}
224-
return nil
225-
}
226-
227-
// Get retrieves a scanner from the pool or creates a new one if necessary.
228-
func (p *ScannerPool) Get() (*yarax.Scanner, error) {
229-
if p.closed.Load() {
230-
return nil, fmt.Errorf("scanner pool is closed")
231-
}
232-
233-
// Retrieve an existing scanner
234-
// If none are available, create up to the maximum number of scanners
235-
select {
236-
case scanner := <-p.available:
237-
return scanner, nil
238-
default:
239-
p.mu.Lock()
240-
if atomic.LoadInt32(&p.currentCount) < p.maxScanners {
241-
scanner, err := p.createScanner()
242-
if err != nil {
243-
p.mu.Unlock()
244-
return nil, fmt.Errorf("create scanner: %w", err)
245-
}
246-
p.scanners = append(p.scanners, scanner)
247-
atomic.AddInt32(&p.currentCount, 1)
248-
p.mu.Unlock()
249-
return scanner, nil
250-
}
251-
p.mu.Unlock()
252-
253-
return <-p.available, nil
254-
}
255-
}
256-
257-
// Put returns a scanner to the pool.
258-
func (p *ScannerPool) Put(scanner *yarax.Scanner) {
259-
if scanner == nil || p.closed.Load() {
260-
return
261-
}
262-
p.available <- scanner
263-
}
264-
265-
// Cleanup destroys all scanners in the pool.
266-
func (p *ScannerPool) Cleanup() {
267-
p.mu.Lock()
268-
defer p.mu.Unlock()
269-
270-
if p.closed.Swap(true) {
271-
return
272-
}
273-
274-
for len(p.available) > 0 {
275-
if scanner := <-p.available; scanner != nil {
276-
scanner.Destroy()
277-
}
278-
}
279-
close(p.available)
280-
281-
for _, scanner := range p.scanners {
282-
if scanner != nil {
283-
scanner.Destroy()
284-
}
285-
}
286-
287-
p.scanners = nil
288-
atomic.StoreInt32(&p.currentCount, 0)
289-
}

pkg/refresh/action.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ func actionRefresh(ctx context.Context) ([]TestData, error) {
6969
c := &malcontent.Config{
7070
Concurrency: runtime.NumCPU(),
7171
IgnoreSelf: false,
72-
MaxScanners: runtime.NumCPU(),
7372
MinFileRisk: 0,
7473
MinRisk: 0,
7574
OCI: false,
@@ -80,15 +79,6 @@ func actionRefresh(ctx context.Context) ([]TestData, error) {
8079
TrimPrefixes: []string{"pkg/action/"},
8180
}
8281

83-
var pool *malcontent.ScannerPool
84-
if c.ScannerPool == nil {
85-
pool, err = malcontent.NewScannerPool(yrs, c.MaxScanners)
86-
if err != nil {
87-
return nil, err
88-
}
89-
c.ScannerPool = pool
90-
}
91-
9282
testData = append(testData, TestData{
9383
Config: c,
9484
OutputPath: output,

pkg/refresh/diff.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ func diffRefresh(ctx context.Context, rc Config) ([]TestData, error) {
196196
Concurrency: runtime.NumCPU(),
197197
FileRiskChange: td.riskChange,
198198
FileRiskIncrease: td.riskIncrease,
199-
MaxScanners: runtime.NumCPU(),
200199
MinFileRisk: minFileRisk,
201200
MinRisk: minRisk,
202201
QuantityIncreasesRisk: true,
@@ -206,15 +205,6 @@ func diffRefresh(ctx context.Context, rc Config) ([]TestData, error) {
206205
TrimPrefixes: []string{rc.SamplesPath},
207206
}
208207

209-
var pool *malcontent.ScannerPool
210-
if c.ScannerPool == nil {
211-
pool, err = malcontent.NewScannerPool(yrs, c.MaxScanners)
212-
if err != nil {
213-
return nil, err
214-
}
215-
c.ScannerPool = pool
216-
}
217-
218208
testData = append(testData, TestData{
219209
Config: c,
220210
OutputPath: output,

pkg/refresh/refresh.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ func newConfig(rc Config) *malcontent.Config {
7474
return &malcontent.Config{
7575
Concurrency: runtime.NumCPU(),
7676
IgnoreTags: []string{"harmless"},
77-
MaxScanners: runtime.NumCPU(),
7877
MinFileRisk: 1,
7978
MinRisk: 1,
8079
QuantityIncreasesRisk: true,
@@ -135,15 +134,6 @@ func prepareRefresh(ctx context.Context, rc Config) ([]TestData, error) {
135134
c.Renderer = r
136135
c.Rules = yrs
137136

138-
var pool *malcontent.ScannerPool
139-
if c.ScannerPool == nil {
140-
pool, err = malcontent.NewScannerPool(yrs, c.Concurrency)
141-
if err != nil {
142-
return nil, err
143-
}
144-
c.ScannerPool = pool
145-
}
146-
147137
if strings.HasSuffix(data, ".mdiff") || strings.HasSuffix(data, ".sdiff") {
148138
dirPath := filepath.Dir(sample)
149139
files, err := os.ReadDir(dirPath)
@@ -179,13 +169,14 @@ func prepareRefresh(ctx context.Context, rc Config) ([]TestData, error) {
179169
}
180170

181171
// executeRefresh reads from a populated slice of TestData.
182-
func executeRefresh(ctx context.Context, testData []TestData) error {
172+
func executeRefresh(ctx context.Context, c Config, testData []TestData) error {
183173
g, ctx := errgroup.WithContext(ctx)
184174

185175
var mu sync.Mutex
186176
completed := 0
187177
total := len(testData)
188178

179+
g.SetLimit(c.Concurrency)
189180
for _, data := range testData {
190181
g.Go(func() error {
191182
select {
@@ -252,5 +243,5 @@ func Refresh(ctx context.Context, rc Config) error {
252243
return fmt.Errorf("failed to prepare sample data refresh: %w", err)
253244
}
254245

255-
return executeRefresh(ctx, testData)
246+
return executeRefresh(ctx, rc, testData)
256247
}

0 commit comments

Comments
 (0)