syz-manager: more consistently check disabled syscalls

We have program "validity" check duplicated 4 times
(initially it was just "does it deserialize?").
Then we added program length and disabled syscall.
But some of the sites have only a subset of checks.
Factor out program checking procedure into a separate function
and use it at all sites.
This commit is contained in:
Dmitry Vyukov 2020-05-12 15:26:07 +02:00
parent 9cea41ad33
commit a44eb8f7ea
3 changed files with 36 additions and 39 deletions

View File

@ -21,7 +21,7 @@ func (mgr *Manager) hubSyncLoop() {
cfg: mgr.cfg,
target: mgr.target,
stats: mgr.stats,
enabledCalls: mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox],
enabledCalls: mgr.targetEnabledSyscalls,
leak: mgr.checkResult.Features[host.FeatureLeak].Enabled,
fresh: mgr.fresh,
hubReproQueue: mgr.hubReproQueue,
@ -37,7 +37,7 @@ type HubConnector struct {
cfg *mgrconfig.Config
target *prog.Target
stats *Stats
enabledCalls []int
enabledCalls map[*prog.Syscall]bool
leak bool
fresh bool
hubCorpus map[hash.Sig]bool
@ -80,8 +80,8 @@ func (hc *HubConnector) connect(corpus [][]byte) (*rpctype.RPCClient, error) {
Manager: hc.cfg.Name,
Fresh: hc.fresh,
}
for _, id := range hc.enabledCalls {
a.Calls = append(a.Calls, hc.target.Syscalls[id].Name)
for call := range hc.enabledCalls {
a.Calls = append(a.Calls, call.Name)
}
hubCorpus := make(map[hash.Sig]bool)
for _, inp := range corpus {
@ -171,8 +171,10 @@ func (hc *HubConnector) processProgs(progs [][]byte) int {
dropped := 0
candidates := make([][]byte, 0, len(progs))
for _, inp := range progs {
p, err := hc.target.Deserialize(inp, prog.NonStrict)
if err != nil || len(p.Calls) > prog.MaxCalls {
bad, disabled := checkProgram(hc.target, hc.enabledCalls, inp)
if bad || disabled {
log.Logf(0, "rejecting program from hub (bad=%v, disabled=%v):\n%s",
bad, disabled, inp)
dropped++
continue
}
@ -185,7 +187,10 @@ func (hc *HubConnector) processProgs(progs [][]byte) int {
func (hc *HubConnector) processRepros(repros [][]byte) int {
dropped := 0
for _, repro := range repros {
if _, err := hc.target.Deserialize(repro, prog.NonStrict); err != nil {
bad, disabled := checkProgram(hc.target, hc.enabledCalls, repro)
if bad || disabled {
log.Logf(0, "rejecting repro from hub (bad=%v, disabled=%v):\n%s",
bad, disabled, repro)
dropped++
continue
}

View File

@ -470,27 +470,14 @@ func (mgr *Manager) loadCorpus() {
fallthrough
case currentDBVersion:
}
broken, tooLong := 0, 0
broken := 0
for key, rec := range mgr.corpusDB.Records {
p, err := mgr.target.Deserialize(rec.Val, prog.NonStrict)
if err != nil {
bad, disabled := checkProgram(mgr.target, mgr.targetEnabledSyscalls, rec.Val)
if bad {
mgr.corpusDB.Delete(key)
broken++
continue
}
if len(p.Calls) > prog.MaxCalls {
mgr.corpusDB.Delete(key)
tooLong++
continue
}
disabled := false
for _, c := range p.Calls {
if !mgr.targetEnabledSyscalls[c.Meta] {
disabled = true
break
}
}
if disabled {
// This program contains a disabled syscall.
// We won't execute it, but remember its hash so
@ -505,8 +492,8 @@ func (mgr *Manager) loadCorpus() {
})
}
mgr.fresh = len(mgr.corpusDB.Records) == 0
log.Logf(0, "%-24v: %v (deleted %v broken, %v too long)",
"corpus", len(mgr.candidates), broken, tooLong)
log.Logf(0, "%-24v: %v (deleted %v broken)",
"corpus", len(mgr.candidates), broken)
// Now this is ugly.
// We duplicate all inputs in the corpus and shuffle the second part.
@ -525,6 +512,22 @@ func (mgr *Manager) loadCorpus() {
mgr.phase = phaseLoadedCorpus
}
func checkProgram(target *prog.Target, enabled map[*prog.Syscall]bool, data []byte) (bad, disabled bool) {
p, err := target.Deserialize(data, prog.NonStrict)
if err != nil {
return true, true
}
if len(p.Calls) > prog.MaxCalls {
return true, true
}
for _, c := range p.Calls {
if !enabled[c.Meta] {
return false, true
}
}
return false, false
}
func (mgr *Manager) runInstance(index int) (*Crash, error) {
mgr.checkUsedFiles()
inst, err := mgr.vmPool.Create(index)

View File

@ -211,22 +211,11 @@ func (serv *RPCServer) NewInput(a *rpctype.NewInputArgs, r *int) error {
inputSignal := a.Signal.Deserialize()
log.Logf(4, "new input from %v for syscall %v (signal=%v, cover=%v)",
a.Name, a.Call, inputSignal.Len(), len(a.Cover))
p, err := serv.target.Deserialize(a.RPCInput.Prog, prog.NonStrict)
if err != nil {
// This should not happen, but we see such cases episodically (probably corrupted VM memory).
log.Logf(0, "failed to deserialize program from fuzzer: %v\n%s", err, a.RPCInput.Prog)
bad, disabled := checkProgram(serv.target, serv.targetEnabledSyscalls, a.RPCInput.Prog)
if bad || disabled {
log.Logf(0, "rejecting program from fuzzer (bad=%v, disabled=%v):\n%s", bad, disabled, a.RPCInput.Prog)
return nil
}
if len(p.Calls) > prog.MaxCalls {
log.Logf(0, "rejecting too long program from fuzzer: %v calls\n%s", len(p.Calls), a.RPCInput.Prog)
return nil
}
for _, call := range p.Calls {
if !serv.targetEnabledSyscalls[call.Meta] {
log.Logf(0, "rejecting program with disabled call %v:\n%s", call.Meta.Name, a.RPCInput.Prog)
return nil
}
}
serv.mu.Lock()
defer serv.mu.Unlock()