diff options
| author | Taras Madan <tarasmadan@gmail.com> | 2021-10-14 15:33:39 +0000 |
|---|---|---|
| committer | tarasmadan <89859571+tarasmadan@users.noreply.github.com> | 2021-10-15 09:52:17 +0200 |
| commit | 0c5d9412d774262384cbdbe9d672b077364ed776 (patch) | |
| tree | 5ba9f0e51d9306492c8812632e3aa11b2dddbc4c /syz-verifier | |
| parent | 115b2d95e56f01373ebe1c3f8f5a4d8c3c4d33e4 (diff) | |
syz-verifier: move Verifier to verifier.go
Diffstat (limited to 'syz-verifier')
| -rwxr-xr-x | syz-verifier/main.go | 242 | ||||
| -rw-r--r-- | syz-verifier/verifier.go | 259 |
2 files changed, 259 insertions, 242 deletions
diff --git a/syz-verifier/main.go b/syz-verifier/main.go index 168e69d26..b4d97d3c2 100755 --- a/syz-verifier/main.go +++ b/syz-verifier/main.go @@ -6,19 +6,14 @@ package main import ( - "errors" "flag" - "fmt" "io" "math/rand" "os" - "os/signal" "path/filepath" "strconv" - "strings" "time" - "github.com/google/syzkaller/pkg/instance" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" @@ -32,36 +27,6 @@ const ( maxResultReports = 100 ) -// Verifier TODO. -type Verifier struct { - pools map[int]*poolInfo - vmStop chan bool - // Location of a working directory for all VMs for the syz-verifier process. - // Outputs here include: - // - <workdir>/crashes/<OS-Arch>/*: crash output files grouped by OS/Arch - // - <workdir>/corpus.db: corpus with interesting programs - // - <workdir>/<OS-Arch>/instance-x: per VM instance temporary files - // grouped by OS/Arch - workdir string - crashdir string - resultsdir string - target *prog.Target - runnerBin string - executorBin string - choiceTable *prog.ChoiceTable - rnd *rand.Rand - progIdx int - addr string - srv *RPCServer - calls map[*prog.Syscall]bool - reasons map[*prog.Syscall]string - reportReasons bool - stats *Stats - statsWrite io.Writer - newEnv bool - reruns int -} - // poolInfo contains kernel-specific information for spawning virtual machines // and reporting crashes. It also keeps track of the Runners executing on // spawned VMs, what programs have been sent to each Runner and what programs @@ -224,210 +189,3 @@ func main() { vrf.startInstances() } - -// SetPrintStatAtSIGINT asks Stats object to report verification -// statistics when an os.Interrupt occurs and Exit(). -func (vrf *Verifier) SetPrintStatAtSIGINT() error { - if vrf.stats == nil { - return errors.New("verifier.stats is nil") - } - - osSignalChannel := make(chan os.Signal) - signal.Notify(osSignalChannel, os.Interrupt) - - go func() { - <-osSignalChannel - defer os.Exit(0) - - totalExecutionTime := time.Since(vrf.stats.StartTime).Minutes() - if vrf.stats.TotalMismatches < 0 { - fmt.Fprint(vrf.statsWrite, "No mismatches occurred until syz-verifier was stopped.") - }else{ - fmt.Fprintf(vrf.statsWrite, vrf.stats.GetTextDescription(totalExecutionTime)) - } - }() - - return nil -} - -func (vrf *Verifier) startInstances() { - for idx, pi := range vrf.pools { - go func(pi *poolInfo, idx int) { - for { - // TODO: implement support for multiple VMs per Pool. - - vrf.createAndManageInstance(pi, idx) - } - }(pi, idx) - } - - select {} -} - -func (vrf *Verifier) createAndManageInstance(pi *poolInfo, idx int) { - inst, err := pi.pool.Create(0) - if err != nil { - log.Fatalf("failed to create instance: %v", err) - } - defer inst.Close() - defer vrf.srv.cleanup(idx, 0) - - fwdAddr, err := inst.Forward(vrf.srv.port) - if err != nil { - log.Fatalf("failed to set up port forwarding: %v", err) - } - - runnerBin, err := inst.Copy(vrf.runnerBin) - if err != nil { - log.Fatalf(" failed to copy runner binary: %v", err) - } - _, err = inst.Copy(vrf.executorBin) - if err != nil { - log.Fatalf("failed to copy executor binary: %v", err) - } - - cmd := instance.RunnerCmd(runnerBin, fwdAddr, vrf.target.OS, vrf.target.Arch, idx, 0, false, false, vrf.newEnv) - outc, errc, err := inst.Run(pi.cfg.Timeouts.VMRunningTime, vrf.vmStop, cmd) - if err != nil { - log.Fatalf("failed to start runner: %v", err) - } - - inst.MonitorExecution(outc, errc, pi.Reporter, vm.ExitTimeout) - - log.Logf(0, "reboot the VM in pool %d", idx) -} - -// finalizeCallSet removes the system calls that are not supported from the set -// of enabled system calls and reports the reason to the io.Writer (either -// because the call is not supported by one of the kernels or because the call -// is missing some transitive dependencies). The resulting set of system calls -// will be used to build the prog.ChoiceTable. -func (vrf *Verifier) finalizeCallSet(w io.Writer) { - for c := range vrf.reasons { - delete(vrf.calls, c) - } - - // Find and report to the user all the system calls that need to be - // disabled due to missing dependencies. - _, disabled := vrf.target.TransitivelyEnabledCalls(vrf.calls) - for c, reason := range disabled { - vrf.reasons[c] = reason - delete(vrf.calls, c) - } - - if len(vrf.calls) == 0 { - log.Logf(0, "All enabled system calls are missing dependencies or not"+ - " supported by some kernels, exiting syz-verifier.") - } - - if !vrf.reportReasons { - return - } - - fmt.Fprintln(w, "The following calls have been disabled:") - for c, reason := range vrf.reasons { - fmt.Fprintf(w, "\t%v: %v\n", c.Name, reason) - } -} - -// processResults will send a set of complete results for verification and, in -// case differences are found, it will start the rerun process for the program -// (if reruns are enabled). If every rerun produces the same results, the result -// report will be printed to persistent storage. Otherwise, the program is -// discarded as flaky. -func (vrf *Verifier) processResults(prog *progInfo) bool { - // TODO: Simplify this if clause. - if prog.runIdx == 0 { - vrf.stats.TotalProgs++ - prog.report = Verify(prog.res[0], prog.prog, vrf.stats) - if prog.report == nil { - return true - } - } else { - if !VerifyRerun(prog.res[prog.runIdx], prog.report) { - vrf.stats.FlakyProgs++ - log.Logf(0, "flaky results detected: %d", vrf.stats.FlakyProgs) - return true - } - } - - if prog.runIdx < vrf.reruns-1 { - vrf.srv.newRun(prog) - return false - } - - rr := prog.report - vrf.stats.MismatchingProgs++ - - for _, cr := range rr.Reports { - if !cr.Mismatch { - break - } - vrf.stats.Calls[cr.Call].Mismatches++ - vrf.stats.TotalMismatches++ - for _, state := range cr.States { - if state0 := cr.States[0]; state0 != state { - vrf.stats.Calls[cr.Call].States[state] = true - vrf.stats.Calls[cr.Call].States[state0] = true - } - } - } - - oldest := 0 - var oldestTime time.Time - for i := 0; i < maxResultReports; i++ { - info, err := os.Stat(filepath.Join(vrf.resultsdir, fmt.Sprintf("result-%d", i))) - if err != nil { - // There are only i-1 report files so the i-th one - // can be created. - oldest = i - break - } - - // Otherwise, search for the oldest report file to - // overwrite as newer result reports are more useful. - if oldestTime.IsZero() || info.ModTime().Before(oldestTime) { - oldest = i - oldestTime = info.ModTime() - } - } - - err := osutil.WriteFile(filepath.Join(vrf.resultsdir, - fmt.Sprintf("result-%d", oldest)), createReport(rr, len(vrf.pools))) - if err != nil { - log.Logf(0, "failed to write result-%d file, err %v", oldest, err) - } - - log.Logf(0, "result-%d written successfully", oldest) - return true -} - -func createReport(rr *ResultReport, pools int) []byte { - calls := strings.Split(rr.Prog, "\n") - calls = calls[:len(calls)-1] - - data := "ERRNO mismatches found for program:\n\n" - for idx, cr := range rr.Reports { - tick := "[=]" - if cr.Mismatch { - tick = "[!]" - } - data += fmt.Sprintf("%s %s\n", tick, calls[idx]) - - // Ensure results are ordered by pool index. - for i := 0; i < pools; i++ { - state := cr.States[i] - data += fmt.Sprintf("\t↳ Pool: %d, %s\n", i, state) - } - - data += "\n" - } - - return []byte(data) -} - -// generate will return a newly generated program and its index. -func (vrf *Verifier) generate() (*prog.Prog, int) { - vrf.progIdx++ - return vrf.target.Generate(vrf.rnd, prog.RecommendedCalls, vrf.choiceTable), vrf.progIdx -}
\ No newline at end of file diff --git a/syz-verifier/verifier.go b/syz-verifier/verifier.go new file mode 100644 index 000000000..ad0b43ca4 --- /dev/null +++ b/syz-verifier/verifier.go @@ -0,0 +1,259 @@ +// Copyright 2021 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. + +package main + +import ( + "errors" + "fmt" + "io" + "math/rand" + "os" + "os/signal" + "path/filepath" + "strings" + "time" + + "github.com/google/syzkaller/pkg/instance" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/vm" +) + +// Verifier TODO. +type Verifier struct { + pools map[int]*poolInfo + vmStop chan bool + // Location of a working directory for all VMs for the syz-verifier process. + // Outputs here include: + // - <workdir>/crashes/<OS-Arch>/*: crash output files grouped by OS/Arch + // - <workdir>/corpus.db: corpus with interesting programs + // - <workdir>/<OS-Arch>/instance-x: per VM instance temporary files + // grouped by OS/Arch + workdir string + crashdir string + resultsdir string + target *prog.Target + runnerBin string + executorBin string + choiceTable *prog.ChoiceTable + rnd *rand.Rand + progIdx int + addr string + srv *RPCServer + calls map[*prog.Syscall]bool + reasons map[*prog.Syscall]string + reportReasons bool + stats *Stats + statsWrite io.Writer + newEnv bool + reruns int +} + +// SetPrintStatAtSIGINT asks Stats object to report verification +// statistics when an os.Interrupt occurs and Exit(). +func (vrf *Verifier) SetPrintStatAtSIGINT() error { + if vrf.stats == nil { + return errors.New("verifier.stats is nil") + } + + osSignalChannel := make(chan os.Signal) + signal.Notify(osSignalChannel, os.Interrupt) + + go func() { + <-osSignalChannel + defer os.Exit(0) + + totalExecutionTime := time.Since(vrf.stats.StartTime).Minutes() + if vrf.stats.TotalMismatches < 0 { + fmt.Fprint(vrf.statsWrite, "No mismatches occurred until syz-verifier was stopped.") + } else { + fmt.Fprintf(vrf.statsWrite, "%s", vrf.stats.GetTextDescription(totalExecutionTime)) + } + }() + + return nil +} + +func (vrf *Verifier) startInstances() { + for idx, pi := range vrf.pools { + go func(pi *poolInfo, idx int) { + for { + // TODO: implement support for multiple VMs per Pool. + + vrf.createAndManageInstance(pi, idx) + } + }(pi, idx) + } + + select {} +} + +func (vrf *Verifier) createAndManageInstance(pi *poolInfo, idx int) { + inst, err := pi.pool.Create(0) + if err != nil { + log.Fatalf("failed to create instance: %v", err) + } + defer inst.Close() + defer vrf.srv.cleanup(idx, 0) + + fwdAddr, err := inst.Forward(vrf.srv.port) + if err != nil { + log.Fatalf("failed to set up port forwarding: %v", err) + } + + runnerBin, err := inst.Copy(vrf.runnerBin) + if err != nil { + log.Fatalf(" failed to copy runner binary: %v", err) + } + _, err = inst.Copy(vrf.executorBin) + if err != nil { + log.Fatalf("failed to copy executor binary: %v", err) + } + + cmd := instance.RunnerCmd(runnerBin, fwdAddr, vrf.target.OS, vrf.target.Arch, idx, 0, false, false, vrf.newEnv) + outc, errc, err := inst.Run(pi.cfg.Timeouts.VMRunningTime, vrf.vmStop, cmd) + if err != nil { + log.Fatalf("failed to start runner: %v", err) + } + + inst.MonitorExecution(outc, errc, pi.Reporter, vm.ExitTimeout) + + log.Logf(0, "reboot the VM in pool %d", idx) +} + +// finalizeCallSet removes the system calls that are not supported from the set +// of enabled system calls and reports the reason to the io.Writer (either +// because the call is not supported by one of the kernels or because the call +// is missing some transitive dependencies). The resulting set of system calls +// will be used to build the prog.ChoiceTable. +func (vrf *Verifier) finalizeCallSet(w io.Writer) { + for c := range vrf.reasons { + delete(vrf.calls, c) + } + + // Find and report to the user all the system calls that need to be + // disabled due to missing dependencies. + _, disabled := vrf.target.TransitivelyEnabledCalls(vrf.calls) + for c, reason := range disabled { + vrf.reasons[c] = reason + delete(vrf.calls, c) + } + + if len(vrf.calls) == 0 { + log.Logf(0, "All enabled system calls are missing dependencies or not"+ + " supported by some kernels, exiting syz-verifier.") + } + + if !vrf.reportReasons { + return + } + + fmt.Fprintln(w, "The following calls have been disabled:") + for c, reason := range vrf.reasons { + fmt.Fprintf(w, "\t%v: %v\n", c.Name, reason) + } +} + +// processResults will send a set of complete results for verification and, in +// case differences are found, it will start the rerun process for the program +// (if reruns are enabled). If every rerun produces the same results, the result +// report will be printed to persistent storage. Otherwise, the program is +// discarded as flaky. +func (vrf *Verifier) processResults(prog *progInfo) bool { + // TODO: Simplify this if clause. + if prog.runIdx == 0 { + vrf.stats.TotalProgs++ + prog.report = Verify(prog.res[0], prog.prog, vrf.stats) + if prog.report == nil { + return true + } + } else { + if !VerifyRerun(prog.res[prog.runIdx], prog.report) { + vrf.stats.FlakyProgs++ + log.Logf(0, "flaky results detected: %d", vrf.stats.FlakyProgs) + return true + } + } + + if prog.runIdx < vrf.reruns-1 { + vrf.srv.newRun(prog) + return false + } + + rr := prog.report + vrf.stats.MismatchingProgs++ + + for _, cr := range rr.Reports { + if !cr.Mismatch { + break + } + vrf.stats.Calls[cr.Call].Mismatches++ + vrf.stats.TotalMismatches++ + for _, state := range cr.States { + if state0 := cr.States[0]; state0 != state { + vrf.stats.Calls[cr.Call].States[state] = true + vrf.stats.Calls[cr.Call].States[state0] = true + } + } + } + + oldest := 0 + var oldestTime time.Time + for i := 0; i < maxResultReports; i++ { + info, err := os.Stat(filepath.Join(vrf.resultsdir, fmt.Sprintf("result-%d", i))) + if err != nil { + // There are only i-1 report files so the i-th one + // can be created. + oldest = i + break + } + + // Otherwise, search for the oldest report file to + // overwrite as newer result reports are more useful. + if oldestTime.IsZero() || info.ModTime().Before(oldestTime) { + oldest = i + oldestTime = info.ModTime() + } + } + + err := osutil.WriteFile(filepath.Join(vrf.resultsdir, + fmt.Sprintf("result-%d", oldest)), createReport(rr, len(vrf.pools))) + if err != nil { + log.Logf(0, "failed to write result-%d file, err %v", oldest, err) + } + + log.Logf(0, "result-%d written successfully", oldest) + return true +} + +// generate will return a newly generated program and its index. +func (vrf *Verifier) generate() (*prog.Prog, int) { + vrf.progIdx++ + return vrf.target.Generate(vrf.rnd, prog.RecommendedCalls, vrf.choiceTable), vrf.progIdx +} + +func createReport(rr *ResultReport, pools int) []byte { + calls := strings.Split(rr.Prog, "\n") + calls = calls[:len(calls)-1] + + data := "ERRNO mismatches found for program:\n\n" + for idx, cr := range rr.Reports { + tick := "[=]" + if cr.Mismatch { + tick = "[!]" + } + data += fmt.Sprintf("%s %s\n", tick, calls[idx]) + + // Ensure results are ordered by pool index. + for i := 0; i < pools; i++ { + state := cr.States[i] + data += fmt.Sprintf("\t↳ Pool: %d, %s\n", i, state) + } + + data += "\n" + } + + return []byte(data) +} |
