aboutsummaryrefslogtreecommitdiffstats
path: root/tools/syz-execprog
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2024-06-04 12:55:41 +0200
committerDmitry Vyukov <dvyukov@google.com>2024-06-24 09:57:34 +0000
commite16e2c9a4cb6937323e861b646792a6c4c978a3c (patch)
tree6c513e98e5f465b44a98546d8984485d2c128582 /tools/syz-execprog
parent90d67044dab68568e8f35bc14b68055dbd166eff (diff)
executor: add runner mode
Move all syz-fuzzer logic into syz-executor and remove syz-fuzzer. Also restore syz-runtest functionality in the manager. Update #4917 (sets most signal handlers to SIG_IGN)
Diffstat (limited to 'tools/syz-execprog')
-rw-r--r--tools/syz-execprog/execprog.go427
1 files changed, 171 insertions, 256 deletions
diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go
index 54a6f39a0..8fce0d961 100644
--- a/tools/syz-execprog/execprog.go
+++ b/tools/syz-execprog/execprog.go
@@ -15,6 +15,7 @@ import (
"runtime"
"strings"
"sync"
+ "sync/atomic"
"time"
"github.com/google/syzkaller/pkg/cover/backend"
@@ -22,12 +23,10 @@ import (
"github.com/google/syzkaller/pkg/db"
"github.com/google/syzkaller/pkg/flatrpc"
"github.com/google/syzkaller/pkg/fuzzer/queue"
- "github.com/google/syzkaller/pkg/host"
- "github.com/google/syzkaller/pkg/ipc"
- "github.com/google/syzkaller/pkg/ipc/ipcconfig"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/mgrconfig"
"github.com/google/syzkaller/pkg/osutil"
+ "github.com/google/syzkaller/pkg/rpcserver"
"github.com/google/syzkaller/pkg/tool"
"github.com/google/syzkaller/pkg/vminfo"
"github.com/google/syzkaller/prog"
@@ -36,15 +35,22 @@ import (
)
var (
- flagOS = flag.String("os", runtime.GOOS, "target os")
- flagArch = flag.String("arch", runtime.GOARCH, "target arch")
- flagCoverFile = flag.String("coverfile", "", "write coverage to the file")
- flagRepeat = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)")
- flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes to execute programs")
- flagOutput = flag.Bool("output", false, "write programs and results to stdout")
- flagHints = flag.Bool("hints", false, "do a hints-generation run")
- flagEnable = flag.String("enable", "none", "enable only listed additional features")
- flagDisable = flag.String("disable", "none", "enable all additional features except listed")
+ flagOS = flag.String("os", runtime.GOOS, "target os")
+ flagArch = flag.String("arch", runtime.GOARCH, "target arch")
+ flagCoverFile = flag.String("coverfile", "", "write coverage to the file")
+ flagRepeat = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)")
+ flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes to execute programs")
+ flagOutput = flag.Bool("output", false, "write programs and results to stdout")
+ flagHints = flag.Bool("hints", false, "do a hints-generation run")
+ flagEnable = flag.String("enable", "none", "enable only listed additional features")
+ flagDisable = flag.String("disable", "none", "enable all additional features except listed")
+ flagExecutor = flag.String("executor", "./syz-executor", "path to executor binary")
+ flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor")
+ flagSignal = flag.Bool("cover", false, "collect feedback signals (coverage)")
+ flagSandbox = flag.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace/android)")
+ flagSandboxArg = flag.Int("sandbox_arg", 0, "argument for sandbox runner to adjust it via config")
+ flagDebug = flag.Bool("debug", false, "debug output from executor")
+ flagSlowdown = flag.Int("slowdown", 1, "execution slowdown caused by emulation/instrumentation")
// The in the stress mode resembles simple unguided fuzzer.
// This mode can be used as an intermediate step when porting syzkaller to a new OS,
@@ -56,6 +62,8 @@ var (
flagStress = flag.Bool("stress", false, "enable stress mode (local fuzzer)")
flagSyscalls = flag.String("syscalls", "", "comma-separated list of enabled syscalls for the stress mode")
+ flagGDB = flag.Bool("gdb", false, "start executor under gdb")
+
// The following flag is only kept to let syzkaller remain compatible with older execprog versions.
// In order to test incoming patches or perform bug bisection, syz-ci must use the exact syzkaller
// version that detected the bug (as descriptions and syntax could've already been changed), and
@@ -68,7 +76,7 @@ var (
// of syzkaller, but do not process it, as there's no such functionality anymore.
// Note, however, that we do not have to do the same for `syz-prog2c`, as `collide` was there false
// by default.
- flagCollide = flag.Bool("collide", false, "(DEPRECATED) collide syscalls to provoke data races")
+ _ = flag.Bool("collide", false, "(DEPRECATED) collide syscalls to provoke data races")
)
func main() {
@@ -78,24 +86,23 @@ func main() {
csource.PrintAvailableFeaturesFlags()
}
defer tool.Init()()
- featuresFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true)
+ target, err := prog.GetTarget(*flagOS, *flagArch)
if err != nil {
- log.Fatalf("%v", err)
+ tool.Fail(err)
}
- target, err := prog.GetTarget(*flagOS, *flagArch)
+ featureFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true)
if err != nil {
log.Fatalf("%v", err)
}
-
- progs := loadPrograms(target, flag.Args())
- if !*flagStress && len(progs) == 0 {
- flag.Usage()
- os.Exit(1)
- }
- if *flagCollide {
- log.Logf(0, "note: setting -collide to true is deprecated now and has no effect")
+ features := flatrpc.AllFeatures
+ for feat := range flatrpc.EnumNamesFeature {
+ opt := csource.FlatRPCFeaturesToCSource[feat]
+ if opt != "" && !featureFlags[opt].Enabled {
+ features &= ^feat
+ }
}
+
var requestedSyscalls []int
if *flagStress {
syscallList := strings.Split(*flagSyscalls, ",")
@@ -104,146 +111,153 @@ func main() {
}
requestedSyscalls, err = mgrconfig.ParseEnabledSyscalls(target, syscallList, nil)
if err != nil {
- log.Fatalf("failed to parse enabled syscalls: %v", err)
+ tool.Failf("failed to parse enabled syscalls: %v", err)
}
}
- config, execOpts, syscalls, features := createConfig(target, featuresFlags, requestedSyscalls)
- var gateCallback func()
- if features&flatrpc.FeatureLeak != 0 {
- gateCallback = func() {
- output, err := osutil.RunCmd(10*time.Minute, "", config.Executor, "leak")
- if err != nil {
- os.Stdout.Write(output)
- os.Exit(1)
- }
- }
+
+ sandbox, err := flatrpc.SandboxToFlags(*flagSandbox)
+ if err != nil {
+ tool.Failf("failed to parse sandbox: %v", err)
}
- var choiceTable *prog.ChoiceTable
- if *flagStress {
- choiceTable = target.BuildChoiceTable(progs, syscalls)
+ env := sandbox
+ if *flagDebug {
+ env |= flatrpc.ExecEnvDebug
+ }
+ cover := *flagSignal || *flagHints || *flagCoverFile != ""
+ if cover {
+ env |= flatrpc.ExecEnvSignal
}
- sysTarget := targets.Get(*flagOS, *flagArch)
+ var exec flatrpc.ExecFlag
+ if *flagThreaded {
+ exec |= flatrpc.ExecFlagThreaded
+ }
+ if *flagCoverFile == "" {
+ exec |= flatrpc.ExecFlagDedupCover
+ }
+
+ progs := loadPrograms(target, flag.Args())
+ if !*flagStress && len(progs) == 0 {
+ flag.Usage()
+ os.Exit(1)
+ }
+ rpcCtx, done := context.WithCancel(context.Background())
ctx := &Context{
- target: target,
- progs: progs,
- choiceTable: choiceTable,
- config: config,
- execOpts: execOpts,
- gate: ipc.NewGate(2**flagProcs, gateCallback),
- shutdown: make(chan struct{}),
- stress: *flagStress,
- repeat: *flagRepeat,
- sysTarget: sysTarget,
- }
- var wg sync.WaitGroup
- wg.Add(*flagProcs)
- for p := 0; p < *flagProcs; p++ {
- pid := p
- go func() {
- defer wg.Done()
- ctx.run(pid)
- }()
- }
- osutil.HandleInterrupts(ctx.shutdown)
- wg.Wait()
+ target: target,
+ done: done,
+ progs: progs,
+ rs: rand.NewSource(time.Now().UnixNano()),
+ coverFile: *flagCoverFile,
+ output: *flagOutput,
+ signal: *flagSignal,
+ hints: *flagHints,
+ stress: *flagStress,
+ repeat: *flagRepeat,
+ defaultOpts: flatrpc.ExecOpts{
+ EnvFlags: env,
+ ExecFlags: exec,
+ SandboxArg: int64(*flagSandboxArg),
+ },
+ }
+
+ cfg := &rpcserver.LocalConfig{
+ Config: rpcserver.Config{
+ Config: vminfo.Config{
+ Target: target,
+ Features: features,
+ Syscalls: requestedSyscalls,
+ Debug: *flagDebug,
+ Cover: cover,
+ Sandbox: sandbox,
+ SandboxArg: int64(*flagSandboxArg),
+ },
+ Procs: *flagProcs,
+ Slowdown: *flagSlowdown,
+ },
+ Executor: *flagExecutor,
+ HandleInterrupts: true,
+ GDB: *flagGDB,
+ Context: rpcCtx,
+ MachineChecked: ctx.machineChecked,
+ }
+ if err := rpcserver.RunLocal(cfg); err != nil {
+ tool.Fail(err)
+ }
}
type Context struct {
target *prog.Target
+ done func()
progs []*prog.Prog
+ defaultOpts flatrpc.ExecOpts
choiceTable *prog.ChoiceTable
- config *ipc.Config
- execOpts *flatrpc.ExecOpts
- gate *ipc.Gate
- shutdown chan struct{}
logMu sync.Mutex
posMu sync.Mutex
+ rs rand.Source
+ coverFile string
+ output bool
+ signal bool
+ hints bool
stress bool
repeat int
pos int
+ completed atomic.Uint64
+ resultIndex atomic.Int64
lastPrint time.Time
- sysTarget *targets.Target
}
-func (ctx *Context) run(pid int) {
- env, err := ipc.MakeEnv(ctx.config, pid)
- if err != nil {
- log.Fatalf("failed to create ipc env: %v", err)
- }
- defer env.Close()
- rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12)
- for {
- select {
- case <-ctx.shutdown:
- return
- default:
- }
- if ctx.stress {
- p := ctx.createStressProg(rs)
- ctx.execute(pid, env, p, 0)
- } else {
- idx := ctx.getProgramIndex()
- if ctx.repeat > 0 && idx >= len(ctx.progs)*ctx.repeat {
- return
- }
- p := ctx.progs[idx%len(ctx.progs)]
- ctx.execute(pid, env, p, idx)
- }
+func (ctx *Context) machineChecked(features flatrpc.Feature, syscalls map[*prog.Syscall]bool) queue.Source {
+ if ctx.stress {
+ ctx.choiceTable = ctx.target.BuildChoiceTable(ctx.progs, syscalls)
}
+ ctx.defaultOpts.EnvFlags |= csource.FeaturesToFlags(features, nil)
+ return queue.DefaultOpts(ctx, ctx.defaultOpts)
}
-func (ctx *Context) execute(pid int, env *ipc.Env, p *prog.Prog, progIndex int) {
- // Limit concurrency window.
- ticket := ctx.gate.Enter()
- defer ctx.gate.Leave(ticket)
+func (ctx *Context) Next() *queue.Request {
+ var p *prog.Prog
+ if ctx.stress {
+ p = ctx.createStressProg()
+ } else {
+ idx := ctx.getProgramIndex()
+ if idx < 0 {
+ return nil
+ }
+ p = ctx.progs[idx]
+ }
+ if ctx.output {
+ data := p.Serialize()
+ ctx.logMu.Lock()
+ log.Logf(0, "executing program:\n%s", data)
+ ctx.logMu.Unlock()
+ }
- callOpts := ctx.execOpts
- if *flagOutput {
- ctx.logProgram(pid, p)
+ req := &queue.Request{
+ Prog: p,
}
- progData, err := p.SerializeForExec()
- if err != nil {
- log.Logf(1, "RESULT: failed to serialize: %v", err)
- return
+ if ctx.hints {
+ req.ExecOpts.ExecFlags |= flatrpc.ExecFlagCollectComps
+ } else if ctx.signal || ctx.coverFile != "" {
+ req.ExecOpts.ExecFlags |= flatrpc.ExecFlagCollectSignal | flatrpc.ExecFlagCollectCover
}
- // This mimics the syz-fuzzer logic. This is important for reproduction.
- for try := 0; ; try++ {
- output, info, hanged, err := env.ExecProg(callOpts, progData)
- if err != nil {
- if ctx.execOpts.EnvFlags&flatrpc.ExecEnvDebug != 0 {
- log.Logf(0, "result: hanged=%v err=%v\n\n%s", hanged, err, output)
- }
- if try > 10 {
- log.SyzFatalf("executor %d failed %d times: %v\n%s", pid, try, err, output)
- }
- // Don't print err/output in this case as it may contain "SYZFAIL" and we want to fail yet.
- log.Logf(1, "executor failed, retrying")
- if try > 3 {
- time.Sleep(100 * time.Millisecond)
- }
- continue
+ req.OnDone(ctx.Done)
+ return req
+}
+
+func (ctx *Context) Done(req *queue.Request, res *queue.Result) bool {
+ if res.Info != nil {
+ ctx.printCallResults(res.Info)
+ if ctx.hints {
+ ctx.printHints(req.Prog, res.Info)
}
- if info != nil {
- ctx.printCallResults(info)
- if *flagHints {
- ctx.printHints(p, info)
- }
- if *flagCoverFile != "" {
- covFile := fmt.Sprintf("%s_prog%d", *flagCoverFile, progIndex)
- ctx.dumpCoverage(covFile, info)
- }
- } else {
- log.Logf(1, "RESULT: no calls executed")
+ if ctx.coverFile != "" {
+ ctx.dumpCoverage(res.Info)
}
- break
}
-}
-
-func (ctx *Context) logProgram(pid int, p *prog.Prog) {
- data := p.Serialize()
- ctx.logMu.Lock()
- log.Logf(0, "executing program %v:\n%s", pid, data)
- ctx.logMu.Unlock()
+ completed := int(ctx.completed.Add(1))
+ if ctx.repeat > 0 && completed >= len(ctx.progs)*ctx.repeat {
+ ctx.done()
+ }
+ return true
}
func (ctx *Context) printCallResults(info *flatrpc.ProgInfo) {
@@ -269,20 +283,20 @@ func (ctx *Context) printCallResults(info *flatrpc.ProgInfo) {
func (ctx *Context) printHints(p *prog.Prog, info *flatrpc.ProgInfo) {
ncomps, ncandidates := 0, 0
for i := range p.Calls {
- if *flagOutput {
+ if ctx.output {
fmt.Printf("call %v:\n", i)
}
comps := make(prog.CompMap)
for _, cmp := range info.Calls[i].Comps {
comps.AddComp(cmp.Op1, cmp.Op2)
- if *flagOutput {
+ if ctx.output {
fmt.Printf("comp 0x%x ? 0x%x\n", cmp.Op1, cmp.Op2)
}
}
ncomps += len(comps)
p.MutateWithHints(i, comps, func(p *prog.Prog) bool {
ncandidates++
- if *flagOutput {
+ if ctx.output {
log.Logf(1, "PROGRAM:\n%s", p.Serialize())
}
return true
@@ -295,9 +309,10 @@ func (ctx *Context) dumpCallCoverage(coverFile string, info *flatrpc.CallInfo) {
if info == nil || len(info.Cover) == 0 {
return
}
+ sysTarget := targets.Get(ctx.target.OS, ctx.target.Arch)
buf := new(bytes.Buffer)
for _, pc := range info.Cover {
- prev := backend.PreviousInstructionPC(ctx.sysTarget, "", pc)
+ prev := backend.PreviousInstructionPC(sysTarget, "", pc)
fmt.Fprintf(buf, "0x%x\n", prev)
}
err := osutil.WriteFile(coverFile, buf.Bytes())
@@ -306,7 +321,8 @@ func (ctx *Context) dumpCallCoverage(coverFile string, info *flatrpc.CallInfo) {
}
}
-func (ctx *Context) dumpCoverage(coverFile string, info *flatrpc.ProgInfo) {
+func (ctx *Context) dumpCoverage(info *flatrpc.ProgInfo) {
+ coverFile := fmt.Sprintf("%s_prog%v", ctx.coverFile, ctx.resultIndex.Add(1))
for i, inf := range info.Calls {
log.Logf(0, "call #%v: signal %v, coverage %v", i, len(inf.Signal), len(inf.Cover))
ctx.dumpCallCoverage(fmt.Sprintf("%v.%v", coverFile, i), inf)
@@ -319,23 +335,28 @@ func (ctx *Context) dumpCoverage(coverFile string, info *flatrpc.ProgInfo) {
func (ctx *Context) getProgramIndex() int {
ctx.posMu.Lock()
- idx := ctx.pos
- ctx.pos++
- if idx%len(ctx.progs) == 0 && time.Since(ctx.lastPrint) > 5*time.Second {
- log.Logf(0, "executed programs: %v", idx)
+ defer ctx.posMu.Unlock()
+ if ctx.repeat > 0 && ctx.pos >= len(ctx.progs)*ctx.repeat {
+ return -1
+ }
+ idx := ctx.pos % len(ctx.progs)
+ if idx == 0 && time.Since(ctx.lastPrint) > 5*time.Second {
+ log.Logf(0, "executed programs: %v", ctx.pos)
ctx.lastPrint = time.Now()
}
- ctx.posMu.Unlock()
+ ctx.pos++
return idx
}
-func (ctx *Context) createStressProg(rs rand.Source) *prog.Prog {
- rnd := rand.New(rs)
+func (ctx *Context) createStressProg() *prog.Prog {
+ ctx.posMu.Lock()
+ rnd := rand.New(ctx.rs)
+ ctx.posMu.Unlock()
if len(ctx.progs) == 0 || rnd.Intn(2) == 0 {
- return ctx.target.Generate(rs, prog.RecommendedCalls, ctx.choiceTable)
+ return ctx.target.Generate(rnd, prog.RecommendedCalls, ctx.choiceTable)
}
p := ctx.progs[rnd.Intn(len(ctx.progs))].Clone()
- p.Mutate(rs, prog.RecommendedCalls, ctx.choiceTable, nil, ctx.progs)
+ p.Mutate(rnd, prog.RecommendedCalls, ctx.choiceTable, nil, ctx.progs)
return p
}
@@ -363,109 +384,3 @@ func loadPrograms(target *prog.Target, files []string) []*prog.Prog {
log.Logf(0, "parsed %v programs", len(progs))
return progs
}
-
-func createConfig(target *prog.Target, featuresFlags csource.Features, syscalls []int) (
- *ipc.Config, *flatrpc.ExecOpts, map[*prog.Syscall]bool, flatrpc.Feature) {
- config, execOpts, err := ipcconfig.Default(target)
- if err != nil {
- log.Fatalf("%v", err)
- }
- if execOpts.EnvFlags&flatrpc.ExecEnvSignal != 0 {
- execOpts.ExecFlags |= flatrpc.ExecFlagCollectCover
- }
- if *flagCoverFile != "" {
- execOpts.EnvFlags |= flatrpc.ExecEnvSignal
- execOpts.ExecFlags |= flatrpc.ExecFlagCollectCover
- execOpts.ExecFlags &^= flatrpc.ExecFlagDedupCover
- }
- if *flagHints {
- if execOpts.ExecFlags&flatrpc.ExecFlagCollectCover != 0 {
- execOpts.ExecFlags ^= flatrpc.ExecFlagCollectCover
- }
- execOpts.ExecFlags |= flatrpc.ExecFlagCollectComps
- }
- cfg := &mgrconfig.Config{
- Sandbox: ipc.FlagsToSandbox(execOpts.EnvFlags),
- SandboxArg: execOpts.SandboxArg,
- Derived: mgrconfig.Derived{
- TargetOS: target.OS,
- TargetArch: target.Arch,
- TargetVMArch: target.Arch,
- Target: target,
- SysTarget: targets.Get(target.OS, target.Arch),
- Syscalls: syscalls,
- },
- }
- checker := vminfo.New(cfg)
- fileInfos := host.ReadFiles(checker.RequiredFiles())
- featureInfos, err := host.SetupFeatures(target, config.Executor, flatrpc.AllFeatures, featuresFlags)
- if err != nil {
- log.Fatal(err)
- }
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- debug := execOpts.EnvFlags&flatrpc.ExecEnvDebug != 0
- go checkerExecutor(ctx, checker, config, debug)
-
- enabledSyscalls, disabledSyscalls, features, err := checker.Run(fileInfos, featureInfos)
- if err != nil {
- log.Fatal(err)
- }
- if *flagOutput {
- for feat, info := range features {
- log.Logf(0, "%-24v: %v", flatrpc.EnumNamesFeature[feat], info.Reason)
- }
- for c, reason := range disabledSyscalls {
- log.Logf(0, "unsupported syscall: %v: %v", c.Name, reason)
- }
- enabledSyscalls, disabledSyscalls = target.TransitivelyEnabledCalls(enabledSyscalls)
- for c, reason := range disabledSyscalls {
- log.Logf(0, "transitively unsupported: %v: %v", c.Name, reason)
- }
- }
- execOpts.EnvFlags |= ipc.FeaturesToFlags(features.Enabled(), featuresFlags)
- return config, execOpts, enabledSyscalls, features.Enabled()
-}
-
-func checkerExecutor(ctx context.Context, source queue.Source, config *ipc.Config, debug bool) {
- env, err := ipc.MakeEnv(config, 0)
- if err != nil {
- log.Fatalf("failed to create ipc env: %v", err)
- }
- defer env.Close()
- for {
- req := source.Next()
- if req == nil {
- select {
- case <-time.After(time.Second / 100):
- case <-ctx.Done():
- return
- }
- continue
- }
- progData, err := req.Prog.SerializeForExec()
- if err != nil {
- log.Fatalf("failed to serialize %s: %v", req.Prog.Serialize(), err)
- }
- execOpts := req.ExecOpts
- if debug {
- execOpts.EnvFlags |= flatrpc.ExecEnvDebug
- }
- output, info, hanged, err := env.ExecProg(&execOpts, progData)
- res := &queue.Result{
- Status: queue.Success,
- Info: info,
- Output: output,
- Err: err,
- }
- if err != nil {
- res.Status = queue.ExecFailure
- }
- if hanged && err == nil {
- res.Status = queue.ExecFailure
- res.Err = fmt.Errorf("hanged")
- }
- req.Done(res)
- }
-}