Skip to content

Commit

Permalink
all: experiment with an option to reset accumulated state
Browse files Browse the repository at this point in the history
In the cases where we do not / cannot sandbox individual prog executions
well enough, some share of progs end up being dependent on the
previously accumulated state of the whole VM.

As the result,
* We lose 5-10% of coverage/signal on every instance restart.
* A share of our corpus programs do not actually trigger the coverage
  they were thought to reliably trigger.

This significantly affects fuzzing efficiency and prevents syzkaller
from accumulating bigger and better corpus over multiple runs.

Let's see if the situation becomes better if we restart syz-executor
before most of prog executions.
  • Loading branch information
a-nogikh committed Jan 23, 2024
1 parent 9165e30 commit 1e153dc
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 14 deletions.
10 changes: 6 additions & 4 deletions pkg/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,11 @@ func (inst *inst) testRepro() ([]byte, error) {
}

type OptionalFuzzerArgs struct {
Slowdown int
RawCover bool
SandboxArg int
PprofPort int
Slowdown int
RawCover bool
SandboxArg int
PprofPort int
ResetAccState bool
}

type FuzzerCmdArgs struct {
Expand Down Expand Up @@ -502,6 +503,7 @@ func FuzzerCmd(args *FuzzerCmdArgs) string {
{Name: "raw_cover", Value: fmt.Sprint(args.Optional.RawCover)},
{Name: "sandbox_arg", Value: fmt.Sprint(args.Optional.SandboxArg)},
{Name: "pprof_port", Value: fmt.Sprint(args.Optional.PprofPort)},
{Name: "reset_acc_state", Value: fmt.Sprint(args.Optional.ResetAccState)},
}
optionalArg = " " + tool.OptionalFlags(flags)
}
Expand Down
7 changes: 7 additions & 0 deletions pkg/ipc/ipc.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info *ProgInf
return
}

func (env *Env) ForceRestart() {
if env.cmd != nil {
env.cmd.close()
env.cmd = nil
}
}

// This smethod brings up an executor process if it was stopped.
func (env *Env) RestartIfNeeded(target *prog.Target) error {
if env.cmd == nil {
Expand Down
11 changes: 11 additions & 0 deletions pkg/mgrconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,21 @@ type Config struct {
// More details can be found in pkg/asset/config.go.
AssetStorage *asset.Config `json:"asset_storage"`

// Experimental options.
Experimental Experimental

// Implementation details beyond this point. Filled after parsing.
Derived `json:"-"`
}

// These options are not guaranteed to be backward/forward compatible and
// can be dropped at any moment.
type Experimental struct {
// Don't let the VM state accumulate too much by restarting
// syz-executor before most prog executions.
ResetAccState bool `json:"reset_acc_state"`
}

type Subsystem struct {
Name string `json:"name"`
Paths []string `json:"path"`
Expand Down
7 changes: 7 additions & 0 deletions syz-fuzzer/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ type Fuzzer struct {

// Let's limit the number of concurrent NewInput requests.
parallelNewInputs chan struct{}

// Experimental flags.
resetAccState bool
}

type FuzzerSnapshot struct {
Expand Down Expand Up @@ -169,6 +172,9 @@ func main() {
flagRunTest = flag.Bool("runtest", false, "enable program testing mode") // used by pkg/runtest
flagRawCover = flag.Bool("raw_cover", false, "fetch raw coverage")
flagPprofPort = flag.Int("pprof_port", 0, "HTTP port for the pprof endpoint (disabled if 0)")

// Experimental flags.
flagResetAccState = flag.Bool("reset_acc_state", false, "restarts executor before most executions")
)
defer tool.Init()()
outputType := parseOutputType(*flagOutput)
Expand Down Expand Up @@ -299,6 +305,7 @@ func main() {
stats: make([]uint64, StatCount),
// Queue no more than ~3 new inputs / proc.
parallelNewInputs: make(chan struct{}, int64(3**flagProcs)),
resetAccState: *flagResetAccState,
}
gateCallback := fuzzer.useBugFrames(r, *flagProcs)
fuzzer.gate = ipc.NewGate(gateSize, gateCallback)
Expand Down
25 changes: 19 additions & 6 deletions syz-fuzzer/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (proc *Proc) loop() {
case *WorkTriage:
proc.triageInput(item)
case *WorkCandidate:
proc.execute(proc.execOpts, item.p, item.flags, StatCandidate)
proc.execute(proc.execOpts, item.p, item.flags, StatCandidate, false)
case *WorkSmash:
proc.smashInput(item)
default:
Expand Down Expand Up @@ -124,6 +124,7 @@ func (proc *Proc) triageInput(item *WorkTriage) {
notexecuted := 0
rawCover := []uint32{}
for i := 0; i < signalRuns; i++ {
proc.resetAccState()
info := proc.executeRaw(proc.execOptsCover, item.p, StatTriage)
if !reexecutionSuccess(info, &item.info, item.call) {
// The call was not executed or failed.
Expand All @@ -149,7 +150,8 @@ func (proc *Proc) triageInput(item *WorkTriage) {
item.p, item.call = prog.Minimize(item.p, item.call, false,
func(p1 *prog.Prog, call1 int) bool {
for i := 0; i < minimizeAttempts; i++ {
info := proc.execute(proc.execOpts, p1, ProgNormal, StatMinimize)
info := proc.execute(proc.execOpts, p1, ProgNormal,
StatMinimize, i == 0)
if !reexecutionSuccess(info, &item.info, call1) {
// The call was not executed or failed.
continue
Expand Down Expand Up @@ -237,7 +239,7 @@ func (proc *Proc) failCall(p *prog.Prog, call int) {
func (proc *Proc) executeHintSeed(p *prog.Prog, call int) {
log.Logf(1, "#%v: collecting comparisons", proc.pid)
// First execute the original program to dump comparisons from KCOV.
info := proc.execute(proc.execOptsComps, p, ProgNormal, StatSeed)
info := proc.execute(proc.execOptsComps, p, ProgNormal, StatSeed, true)
if info == nil {
return
}
Expand All @@ -247,11 +249,15 @@ func (proc *Proc) executeHintSeed(p *prog.Prog, call int) {
// Execute each of such mutants to check if it gives new coverage.
p.MutateWithHints(call, info.Calls[call].Comps, func(p *prog.Prog) {
log.Logf(1, "#%v: executing comparison hint", proc.pid)
proc.execute(proc.execOpts, p, ProgNormal, StatHint)
proc.execute(proc.execOpts, p, ProgNormal, StatHint, false)
})
}

func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, stat Stat) *ipc.ProgInfo {
func (proc *Proc) execute(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, stat Stat,
resetState bool) *ipc.ProgInfo {
if resetState {
proc.resetAccState()
}
info := proc.executeRaw(execOpts, p, stat)
if info == nil {
return nil
Expand Down Expand Up @@ -281,7 +287,7 @@ func (proc *Proc) enqueueCallTriage(p *prog.Prog, flags ProgTypes, callIndex int
}

func (proc *Proc) executeAndCollide(execOpts *ipc.ExecOpts, p *prog.Prog, flags ProgTypes, stat Stat) {
proc.execute(execOpts, p, flags, stat)
proc.execute(execOpts, p, flags, stat, true)

if proc.execOptsCollide.Flags&ipc.FlagThreaded == 0 {
// We cannot collide syscalls without being in the threaded mode.
Expand Down Expand Up @@ -389,3 +395,10 @@ func (proc *Proc) logProgram(opts *ipc.ExecOpts, p *prog.Prog) {
log.SyzFatalf("unknown output type: %v", proc.fuzzer.outputType)
}
}

func (proc *Proc) resetAccState() {
if !proc.fuzzer.resetAccState {
return
}
proc.env.ForceRestart()
}
15 changes: 11 additions & 4 deletions syz-manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,10 +825,11 @@ func (mgr *Manager) runInstanceInner(index int, instanceName string) (*report.Re
Test: false,
Runtest: false,
Optional: &instance.OptionalFuzzerArgs{
Slowdown: mgr.cfg.Timeouts.Slowdown,
RawCover: mgr.cfg.RawCover,
SandboxArg: mgr.cfg.SandboxArg,
PprofPort: inst.PprofPort(),
Slowdown: mgr.cfg.Timeouts.Slowdown,
RawCover: mgr.cfg.RawCover,
SandboxArg: mgr.cfg.SandboxArg,
PprofPort: inst.PprofPort(),
ResetAccState: mgr.cfg.Experimental.ResetAccState,
},
}
cmd := instance.FuzzerCmd(args)
Expand Down Expand Up @@ -1212,6 +1213,12 @@ func (mgr *Manager) getMinimizedCorpus() (corpus, repros [][]byte) {
func (mgr *Manager) addNewCandidates(candidates []rpctype.Candidate) {
mgr.mu.Lock()
defer mgr.mu.Unlock()
if mgr.cfg.Experimental.ResetAccState {
// Don't accept new candidates -- the execution is already very slow,
// syz-hub will just overwhelm us.
return
}

mgr.candidates = append(mgr.candidates, candidates...)
if mgr.phase == phaseTriagedCorpus {
mgr.phase = phaseQueriedHub
Expand Down

0 comments on commit 1e153dc

Please sign in to comment.