aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-02-01 18:26:06 +0100
committerAleksandr Nogikh <nogikh@google.com>2024-02-08 13:36:40 +0000
commit7f07e9b0e2d1b715e875a446eea8399f9bb8a4b2 (patch)
treeec243ce4296c5360c876389298a35dcaa8a1f662
parente8e3023880fc4f8be85c1d1c689fd1fc9a77d60e (diff)
syz-manager: prefer non-ANY progs in corpus minimization
In case of non-squashed programs we can leverage our descriptions in a much better way than just blind mutations of binary blobs.
-rw-r--r--prog/analysis.go9
-rw-r--r--prog/any.go4
-rw-r--r--syz-manager/hub.go4
-rw-r--r--syz-manager/manager.go33
-rw-r--r--syz-manager/rpc.go6
5 files changed, 40 insertions, 16 deletions
diff --git a/prog/analysis.go b/prog/analysis.go
index 47c470926..600f9bee4 100644
--- a/prog/analysis.go
+++ b/prog/analysis.go
@@ -367,3 +367,12 @@ func (p *Prog) ForEachAsset(cb func(name string, typ AssetType, r io.Reader)) {
})
}
}
+
+func (p *Prog) ContainsAny() bool {
+ for _, c := range p.Calls {
+ if p.Target.CallContainsAny(c) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/prog/any.go b/prog/any.go
index b99e64932..0a0fdddf5 100644
--- a/prog/any.go
+++ b/prog/any.go
@@ -126,7 +126,7 @@ func (target *Target) isAnyRes(name string) bool {
func (target *Target) CallContainsAny(c *Call) (res bool) {
ForeachArg(c, func(arg Arg, ctx *ArgCtx) {
- if target.isAnyPtr(arg.Type()) {
+ if target.isAnyPtr(arg.Type()) || res {
res = true
ctx.Stop = true
}
@@ -136,7 +136,7 @@ func (target *Target) CallContainsAny(c *Call) (res bool) {
func (target *Target) ArgContainsAny(arg0 Arg) (res bool) {
ForeachSubArg(arg0, func(arg Arg, ctx *ArgCtx) {
- if target.isAnyPtr(arg.Type()) {
+ if target.isAnyPtr(arg.Type()) || res {
res = true
ctx.Stop = true
}
diff --git a/syz-manager/hub.go b/syz-manager/hub.go
index 9598a41d4..c10011818 100644
--- a/syz-manager/hub.go
+++ b/syz-manager/hub.go
@@ -215,7 +215,7 @@ func (hc *HubConnector) sync(hub *rpctype.RPCClient, corpus [][]byte) error {
func (hc *HubConnector) processProgs(inputs []rpctype.HubInput) (minimized, smashed, dropped int) {
candidates := make([]rpctype.Candidate, 0, len(inputs))
for _, inp := range inputs {
- bad, disabled := checkProgram(hc.target, hc.enabledCalls, inp.Prog)
+ bad, disabled, _ := checkProgram(hc.target, hc.enabledCalls, inp.Prog)
if bad != nil || disabled {
log.Logf(0, "rejecting program from hub (bad=%v, disabled=%v):\n%s",
bad, disabled, inp)
@@ -268,7 +268,7 @@ func splitDomains(domain string) (string, string) {
func (hc *HubConnector) processRepros(repros [][]byte) int {
dropped := 0
for _, repro := range repros {
- bad, disabled := checkProgram(hc.target, hc.enabledCalls, repro)
+ bad, disabled, _ := checkProgram(hc.target, hc.enabledCalls, repro)
if bad != nil || disabled {
log.Logf(0, "rejecting repro from hub (bad=%v, disabled=%v):\n%s",
bad, disabled, repro)
diff --git a/syz-manager/manager.go b/syz-manager/manager.go
index 06a2a49b7..79a8c6586 100644
--- a/syz-manager/manager.go
+++ b/syz-manager/manager.go
@@ -14,6 +14,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "sort"
"sync"
"sync/atomic"
"time"
@@ -107,6 +108,7 @@ type CorpusItemUpdate struct {
type CorpusItem struct {
Call string
Prog []byte
+ HasAny bool // whether the prog contains squashed arguments
Signal signal.Serial
Cover []uint32
Updates []CorpusItemUpdate
@@ -685,7 +687,7 @@ func (mgr *Manager) loadCorpus() {
}
func (mgr *Manager) loadProg(data []byte, minimized, smashed bool) bool {
- bad, disabled := checkProgram(mgr.target, mgr.targetEnabledSyscalls, data)
+ bad, disabled, _ := checkProgram(mgr.target, mgr.targetEnabledSyscalls, data)
if bad != nil {
return false
}
@@ -734,26 +736,29 @@ func programLeftover(target *prog.Target, enabled map[*prog.Syscall]bool, data [
return p.Serialize()
}
-func checkProgram(target *prog.Target, enabled map[*prog.Syscall]bool, data []byte) (bad error, disabled bool) {
+// The linter complains about error not being the last argument.
+// nolint: stylecheck
+func checkProgram(target *prog.Target, enabled map[*prog.Syscall]bool, data []byte) (bad error, disabled, hasAny bool) {
p, err := target.Deserialize(data, prog.NonStrict)
if err != nil {
- return err, true
+ return err, true, false
}
if len(p.Calls) > prog.MaxCalls {
- return fmt.Errorf("longer than %d calls", prog.MaxCalls), true
+ return fmt.Errorf("longer than %d calls", prog.MaxCalls), true, false
}
// For some yet unknown reasons, programs with fail_nth > 0 may sneak in. Ignore them.
for _, call := range p.Calls {
if call.Props.FailNth > 0 {
- return fmt.Errorf("input has fail_nth > 0"), true
+ return fmt.Errorf("input has fail_nth > 0"), true, false
}
}
+ hasAny = p.ContainsAny()
for _, c := range p.Calls {
if !enabled[c.Meta] {
- return nil, true
+ return nil, true, hasAny
}
}
- return nil, false
+ return nil, false, hasAny
}
func (mgr *Manager) runInstance(index int) (*Crash, error) {
@@ -1251,9 +1256,18 @@ func (mgr *Manager) minimizeCorpus() {
Context: inp,
})
}
- newCorpus := make(map[string]CorpusItem)
+
// Note: inputs are unsorted (based on map iteration).
// This gives some intentional non-determinism during minimization.
+ // However, we want to give preference to non-squashed inputs,
+ // so let's sort by this criteria.
+ sort.SliceStable(inputs, func(i, j int) bool {
+ firstAny := inputs[i].Context.(CorpusItem).HasAny
+ secondAny := inputs[j].Context.(CorpusItem).HasAny
+ return !firstAny && secondAny
+ })
+
+ newCorpus := make(map[string]CorpusItem)
for _, ctx := range signal.Minimize(inputs) {
inp := ctx.(CorpusItem)
newCorpus[hash.String(inp.Prog)] = inp
@@ -1387,7 +1401,7 @@ func (mgr *Manager) machineChecked(a *rpctype.CheckArgs, enabledSyscalls map[*pr
mgr.firstConnect = time.Now()
}
-func (mgr *Manager) newInput(inp rpctype.Input, sign signal.Signal) bool {
+func (mgr *Manager) newInput(inp rpctype.Input, sign signal.Signal, hasAny bool) bool {
mgr.mu.Lock()
defer mgr.mu.Unlock()
if mgr.saturatedCalls[inp.Call] {
@@ -1416,6 +1430,7 @@ func (mgr *Manager) newInput(inp rpctype.Input, sign signal.Signal) bool {
mgr.corpus[sig] = CorpusItem{
Call: inp.Call,
Prog: inp.Prog,
+ HasAny: hasAny,
Signal: inp.Signal,
Cover: inp.Cover,
Updates: []CorpusItemUpdate{update},
diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go
index de1fd1d43..dfa050334 100644
--- a/syz-manager/rpc.go
+++ b/syz-manager/rpc.go
@@ -61,7 +61,7 @@ type RPCManagerView interface {
fuzzerConnect([]host.KernelModule) (
[]rpctype.Input, BugFrames, map[uint32]uint32, map[uint32]uint32, error)
machineChecked(result *rpctype.CheckArgs, enabledSyscalls map[*prog.Syscall]bool)
- newInput(inp rpctype.Input, sign signal.Signal) bool
+ newInput(inp rpctype.Input, sign signal.Signal, hasAny bool) bool
candidateBatch(size int) []rpctype.Candidate
rotateCorpus() bool
}
@@ -256,7 +256,7 @@ func (serv *RPCServer) Check(a *rpctype.CheckArgs, r *int) error {
}
func (serv *RPCServer) NewInput(a *rpctype.NewInputArgs, r *int) error {
- bad, disabled := checkProgram(serv.cfg.Target, serv.targetEnabledSyscalls, a.Input.Prog)
+ bad, disabled, hasAny := checkProgram(serv.cfg.Target, serv.targetEnabledSyscalls, a.Input.Prog)
if bad != nil || disabled {
log.Errorf("rejecting program from fuzzer (bad=%v, disabled=%v):\n%s", bad, disabled, a.Input.Prog)
return nil
@@ -281,7 +281,7 @@ func (serv *RPCServer) NewInput(a *rpctype.NewInputArgs, r *int) error {
if !genuine && !rotated {
return nil
}
- if !serv.mgr.newInput(a.Input, inputSignal) {
+ if !serv.mgr.newInput(a.Input, inputSignal, hasAny) {
return nil
}