From dc18fdf9b0a2dc9a54167eaf7600075da09d023e Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 6 May 2024 10:38:20 +0200 Subject: tools/syz-stress: delete utility Move syz-stress logic into syz-execprog. It's already doing most of what syz-stress could do, it even can load a corpus since recently. There are few remaining bits that are missing in execprog, so add them to execprog. --- Makefile | 7 +- docs/windows/README.md | 4 +- tools/syz-execprog/execprog.go | 130 +++++++++++++++++++++-------- tools/syz-stress/stress.go | 182 ----------------------------------------- 4 files changed, 102 insertions(+), 221 deletions(-) delete mode 100644 tools/syz-stress/stress.go diff --git a/Makefile b/Makefile index 584336822..459037457 100644 --- a/Makefile +++ b/Makefile @@ -100,7 +100,7 @@ endif .PHONY: all clean host target \ manager runtest fuzzer executor \ ci hub \ - execprog mutate prog2c trace2syz stress repro upgrade db \ + execprog mutate prog2c trace2syz repro upgrade db \ usbgen symbolize cover kconf syz-build crush \ bin/syz-extract bin/syz-fmt \ extract generate generate_go generate_rpc generate_sys \ @@ -113,7 +113,7 @@ endif all: host target host: manager runtest repro mutate prog2c db upgrade -target: fuzzer execprog stress executor +target: fuzzer execprog executor executor: descriptions ifeq ($(TARGETOS),fuchsia) @@ -185,9 +185,6 @@ crush: descriptions reporter: descriptions GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(HOSTGO) build $(GOHOSTFLAGS) -o ./bin/syz-reporter github.com/google/syzkaller/tools/syz-reporter -stress: descriptions - GOOS=$(TARGETGOOS) GOARCH=$(TARGETGOARCH) $(GO) build $(GOTARGETFLAGS) -o ./bin/$(TARGETOS)_$(TARGETVMARCH)/syz-stress$(EXE) github.com/google/syzkaller/tools/syz-stress - db: descriptions GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(HOSTGO) build $(GOHOSTFLAGS) -o ./bin/syz-db github.com/google/syzkaller/tools/syz-db diff --git a/docs/windows/README.md b/docs/windows/README.md index a511ba86b..a72cec627 100644 --- a/docs/windows/README.md +++ b/docs/windows/README.md @@ -35,9 +35,9 @@ cl executor\executor_windows.cc /EHsc -o bin\windows_amd64\syz-executor.exe \ Msimg32.lib RpcRT4.lib Rpcrt4.lib lz32.lib ``` -To run `syz-stress`: +To run `syz-execprog`: ``` -bin\windows_amd64\syz-stress.exe -executor c:\full\path\to\bin\windows_amd64\syz-executor.exe +bin\windows_amd64\syz-execprog.exe -executor c:\full\path\to\bin\windows_amd64\syz-executor.exe -stress ``` Windows is supported by only `gce` VMs at the moment. diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index 9cfebf15b..6c3314721 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -9,9 +9,11 @@ import ( "bytes" "flag" "fmt" + "math/rand" "os" "runtime" "strconv" + "strings" "sync" "time" @@ -23,6 +25,7 @@ import ( "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/tool" "github.com/google/syzkaller/prog" @@ -40,6 +43,17 @@ var ( 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") + + // 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, + // or when testing on a machine that is not supported by the vm package (as syz-manager cannot be used). + // To use this mode one needs to start a VM manually, copy syz-execprog and run it. + // syz-execprog will execute random programs infinitely until it's stopped or it crashes + // the kernel underneath. If it's given a corpus of programs, it will alternate between + // executing random programs and mutated programs from the corpus. + flagStress = flag.Bool("stress", false, "enable stress mode (local fuzzer)") + flagSyscalls = flag.String("syscalls", "", "comma-separated list of enabled syscalls for the stress mode") + // 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 @@ -62,10 +76,6 @@ func main() { csource.PrintAvailableFeaturesFlags() } defer tool.Init()() - if len(flag.Args()) == 0 { - flag.Usage() - os.Exit(1) - } featuresFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true) if err != nil { log.Fatalf("%v", err) @@ -77,8 +87,9 @@ func main() { } progs := loadPrograms(target, flag.Args()) - if len(progs) == 0 { - return + if !*flagStress && len(progs) == 0 { + flag.Usage() + os.Exit(1) } features, err := host.Check(target) if err != nil { @@ -106,17 +117,29 @@ func main() { } } } + var choiceTable *prog.ChoiceTable + if *flagStress { + var syscalls []string + if *flagSyscalls != "" { + syscalls = strings.Split(*flagSyscalls, ",") + } + calls := buildCallList(target, syscalls) + choiceTable = target.BuildChoiceTable(progs, calls) + } sysTarget := targets.Get(*flagOS, *flagArch) upperBase := getKernelUpperBase(sysTarget) ctx := &Context{ - progs: progs, - config: config, - execOpts: execOpts, - gate: ipc.NewGate(2**flagProcs, gateCallback), - shutdown: make(chan struct{}), - repeat: *flagRepeat, - target: sysTarget, - upperBase: upperBase, + 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, + upperBase: upperBase, } var wg sync.WaitGroup wg.Add(*flagProcs) @@ -132,18 +155,21 @@ func main() { } type Context struct { - progs []*prog.Prog - config *ipc.Config - execOpts *ipc.ExecOpts - gate *ipc.Gate - shutdown chan struct{} - logMu sync.Mutex - posMu sync.Mutex - repeat int - pos int - lastPrint time.Time - target *targets.Target - upperBase uint32 + target *prog.Target + progs []*prog.Prog + choiceTable *prog.ChoiceTable + config *ipc.Config + execOpts *ipc.ExecOpts + gate *ipc.Gate + shutdown chan struct{} + logMu sync.Mutex + posMu sync.Mutex + stress bool + repeat int + pos int + lastPrint time.Time + sysTarget *targets.Target + upperBase uint32 } func (ctx *Context) run(pid int) { @@ -152,18 +178,24 @@ func (ctx *Context) run(pid int) { 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: } - idx := ctx.getProgramIndex() - if ctx.repeat > 0 && idx >= len(ctx.progs)*ctx.repeat { - return + 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) } - entry := ctx.progs[idx%len(ctx.progs)] - ctx.execute(pid, env, entry, idx) } } @@ -301,7 +333,8 @@ func (ctx *Context) dumpCallCoverage(coverFile string, info *ipc.CallInfo) { } buf := new(bytes.Buffer) for _, pc := range info.Cover { - fmt.Fprintf(buf, "0x%x\n", backend.PreviousInstructionPC(ctx.target, cover.RestorePC(pc, ctx.upperBase))) + prev := backend.PreviousInstructionPC(ctx.sysTarget, cover.RestorePC(pc, ctx.upperBase)) + fmt.Fprintf(buf, "0x%x\n", prev) } err := osutil.WriteFile(coverFile, buf.Bytes()) if err != nil { @@ -330,6 +363,16 @@ func (ctx *Context) getProgramIndex() int { return idx } +func (ctx *Context) createStressProg(rs rand.Source) *prog.Prog { + rnd := rand.New(rs) + if len(ctx.progs) == 0 || rnd.Intn(2) == 0 { + return ctx.target.Generate(rs, prog.RecommendedCalls, ctx.choiceTable) + } + p := ctx.progs[rnd.Intn(len(ctx.progs))].Clone() + p.Mutate(rs, prog.RecommendedCalls, ctx.choiceTable, nil, ctx.progs) + return p +} + func loadPrograms(target *prog.Target, files []string) []*prog.Prog { var progs []*prog.Prog for _, fn := range files { @@ -378,3 +421,26 @@ func createConfig(target *prog.Target, features *host.Features, featuresFlags cs execOpts.EnvFlags |= ipc.FeaturesToFlags(features, featuresFlags) return config, execOpts } + +func buildCallList(target *prog.Target, enabled []string) map[*prog.Syscall]bool { + syscallsIDs, err := mgrconfig.ParseEnabledSyscalls(target, enabled, nil) + if err != nil { + log.Fatalf("failed to parse enabled syscalls: %v", err) + } + enabledSyscalls := make(map[*prog.Syscall]bool) + for _, id := range syscallsIDs { + enabledSyscalls[target.Syscalls[id]] = true + } + calls, disabled, err := host.DetectSupportedSyscalls(target, "none", enabledSyscalls) + if err != nil { + log.Fatalf("failed to detect host supported syscalls: %v", err) + } + for c, reason := range disabled { + log.Logf(0, "unsupported syscall: %v: %v", c.Name, reason) + } + calls, disabled = target.TransitivelyEnabledCalls(calls) + for c, reason := range disabled { + log.Logf(0, "transitively unsupported: %v: %v", c.Name, reason) + } + return calls +} diff --git a/tools/syz-stress/stress.go b/tools/syz-stress/stress.go deleted file mode 100644 index 080c8615e..000000000 --- a/tools/syz-stress/stress.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2015 syzkaller project authors. All rights reserved. -// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - -// syz-stress executes random programs locally. -// A user needs to start a VM manually, copy syz-stress and run it. -// syz-stress will execute random programs infinitely until it's stopped or it crashes the kernel underneath. -// If it's given a corpus of programs, it will alternate between executing random programs and mutated -// programs from the corpus. Running syz-stress can be used as an intermediate step when porting syzkaller -// to a new OS, or when testing on a machine that is not supported by the vm package (as syz-manager cannot be used). -package main - -import ( - "flag" - "fmt" - "math/rand" - "os" - "runtime" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/google/syzkaller/pkg/csource" - "github.com/google/syzkaller/pkg/db" - "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/prog" - _ "github.com/google/syzkaller/sys" -) - -var ( - flagOS = flag.String("os", runtime.GOOS, "target os") - flagArch = flag.String("arch", runtime.GOARCH, "target arch") - flagCorpus = flag.String("corpus", "", "corpus database") - flagOutput = flag.Bool("output", false, "print executor output to console") - flagProcs = flag.Int("procs", 2*runtime.NumCPU(), "number of parallel processes") - flagLogProg = flag.Bool("logprog", false, "print programs before execution") - flagGenerate = flag.Bool("generate", true, "generate new programs, otherwise only mutate corpus") - flagSyscalls = flag.String("syscalls", "", "comma-separated list of enabled syscalls") - flagEnable = flag.String("enable", "none", "enable only listed additional features") - flagDisable = flag.String("disable", "none", "enable all additional features except listed") - - statExec uint64 - gate *ipc.Gate -) - -func main() { - flag.Usage = func() { - flag.PrintDefaults() - csource.PrintAvailableFeaturesFlags() - } - flag.Parse() - featuresFlags, err := csource.ParseFeaturesFlags(*flagEnable, *flagDisable, true) - if err != nil { - log.Fatalf("%v", err) - } - target, err := prog.GetTarget(*flagOS, *flagArch) - if err != nil { - log.Fatalf("%v", err) - } - corpus, err := db.ReadCorpus(*flagCorpus, target) - if err != nil { - log.Fatalf("failed to read corpus: %v", err) - } - log.Logf(0, "parsed %v programs", len(corpus)) - if !*flagGenerate && len(corpus) == 0 { - log.Fatalf("nothing to mutate (-generate=false and no corpus)") - } - - features, err := host.Check(target) - if err != nil { - log.Fatalf("%v", err) - } - - var syscalls []string - if *flagSyscalls != "" { - syscalls = strings.Split(*flagSyscalls, ",") - } - calls := buildCallList(target, syscalls) - ct := target.BuildChoiceTable(corpus, calls) - - config, execOpts, err := createIPCConfig(target, features, featuresFlags) - if err != nil { - log.Fatalf("%v", err) - } - if err = host.Setup(target, features, featuresFlags, config.Executor); err != nil { - log.Fatal(err) - } - gate = ipc.NewGate(2**flagProcs, nil) - for pid := 0; pid < *flagProcs; pid++ { - pid := pid - go func() { - env, err := ipc.MakeEnv(config, pid) - if err != nil { - log.Fatalf("failed to create execution environment: %v", err) - } - rs := rand.NewSource(time.Now().UnixNano() + int64(pid)*1e12) - rnd := rand.New(rs) - for i := 0; ; i++ { - var p *prog.Prog - if *flagGenerate && len(corpus) == 0 || i%4 != 0 { - p = target.Generate(rs, prog.RecommendedCalls, ct) - execute(pid, env, execOpts, p) - p.Mutate(rs, prog.RecommendedCalls, ct, nil, corpus) - execute(pid, env, execOpts, p) - } else { - p = corpus[rnd.Intn(len(corpus))].Clone() - p.Mutate(rs, prog.RecommendedCalls, ct, nil, corpus) - execute(pid, env, execOpts, p) - p.Mutate(rs, prog.RecommendedCalls, ct, nil, corpus) - execute(pid, env, execOpts, p) - } - } - }() - } - for range time.NewTicker(5 * time.Second).C { - log.Logf(0, "executed %v programs", atomic.LoadUint64(&statExec)) - } -} - -var outMu sync.Mutex - -func execute(pid int, env *ipc.Env, execOpts *ipc.ExecOpts, p *prog.Prog) { - atomic.AddUint64(&statExec, 1) - if *flagLogProg { - ticket := gate.Enter() - defer gate.Leave(ticket) - outMu.Lock() - fmt.Printf("executing program %v\n%s\n", pid, p.Serialize()) - outMu.Unlock() - } - output, _, hanged, err := env.Exec(execOpts, p) - if err != nil { - fmt.Printf("failed to execute executor: %v\n", err) - } - if hanged || err != nil || *flagOutput { - fmt.Printf("PROGRAM:\n%s\n", p.Serialize()) - } - if hanged || err != nil || *flagOutput { - os.Stdout.Write(output) - } -} - -func createIPCConfig(target *prog.Target, features *host.Features, featuresFlags csource.Features) ( - *ipc.Config, *ipc.ExecOpts, error) { - config, execOpts, err := ipcconfig.Default(target) - if err != nil { - return nil, nil, err - } - execOpts.EnvFlags |= ipc.FeaturesToFlags(features, featuresFlags) - return config, execOpts, nil -} - -func buildCallList(target *prog.Target, enabled []string) map[*prog.Syscall]bool { - enabledSyscalls := make(map[*prog.Syscall]bool) - if len(enabled) != 0 { - syscallsIDs, err := mgrconfig.ParseEnabledSyscalls(target, enabled, nil) - if err != nil { - log.Fatalf("failed to parse enabled syscalls: %v", err) - } - for _, id := range syscallsIDs { - enabledSyscalls[target.Syscalls[id]] = true - } - } - - calls, disabled, err := host.DetectSupportedSyscalls(target, "none", enabledSyscalls) - if err != nil { - log.Fatalf("failed to detect host supported syscalls: %v", err) - } - - for c, reason := range disabled { - log.Logf(0, "unsupported syscall: %v: %v", c.Name, reason) - } - calls, disabled = target.TransitivelyEnabledCalls(calls) - for c, reason := range disabled { - log.Logf(0, "transitively unsupported: %v: %v", c.Name, reason) - } - return calls -} -- cgit mrf-deployment