From 7f07e9b0e2d1b715e875a446eea8399f9bb8a4b2 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Thu, 1 Feb 2024 18:26:06 +0100 Subject: 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. --- prog/analysis.go | 9 +++++++++ prog/any.go | 4 ++-- syz-manager/hub.go | 4 ++-- syz-manager/manager.go | 33 ++++++++++++++++++++++++--------- syz-manager/rpc.go | 6 +++--- 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 } -- cgit mrf-deployment