aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/runtest/run.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2024-04-30 12:15:22 +0200
committerDmitry Vyukov <dvyukov@google.com>2024-05-03 11:23:33 +0000
commitb385c6f4c3087f54a49d5dd38705a67133d07a87 (patch)
tree912b4b10228e8632b7fad2e79fb35107c155c0ef /pkg/runtest/run.go
parent65b976e25e618a5f1036a7aea76bc94553222905 (diff)
tools/syz-runtest: switch to the generic program execution
syz-runtest effectively implemented the same execute program/return result mechanism we use now for normal fuzzing. So extend the general mechanism to allow collecting output/errors, repeating program, and executing a precompiled binary as a test. And switch syz-runtest to the general mechanism. This removes another chunk of code from syz-fuzzer.
Diffstat (limited to 'pkg/runtest/run.go')
-rw-r--r--pkg/runtest/run.go165
1 files changed, 56 insertions, 109 deletions
diff --git a/pkg/runtest/run.go b/pkg/runtest/run.go
index 5c00bf982..796fb6040 100644
--- a/pkg/runtest/run.go
+++ b/pkg/runtest/run.go
@@ -13,7 +13,6 @@ package runtest
import (
"bufio"
"bytes"
- "errors"
"fmt"
"os"
"path/filepath"
@@ -22,12 +21,10 @@ import (
"sort"
"strconv"
"strings"
- "time"
"github.com/google/syzkaller/pkg/csource"
"github.com/google/syzkaller/pkg/host"
"github.com/google/syzkaller/pkg/ipc"
- "github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/prog"
"github.com/google/syzkaller/sys/targets"
)
@@ -35,19 +32,19 @@ import (
type RunRequest struct {
Bin string
P *prog.Prog
- Cfg *ipc.Config
- Opts *ipc.ExecOpts
+ Opts ipc.ExecOpts
Repeat int
Done chan struct{}
Output []byte
- Info []*ipc.ProgInfo
+ Info ipc.ProgInfo
Err error
- results *ipc.ProgInfo
- name string
- broken string
- skip string
+ finished chan struct{}
+ results *ipc.ProgInfo
+ name string
+ broken string
+ skip string
}
type Context struct {
@@ -78,19 +75,15 @@ func (ctx *Context) Run() error {
defer close(progs)
errc <- ctx.generatePrograms(progs)
}()
- var ok, fail, broken, skip int
+ var requests []*RunRequest
for req := range progs {
- result := ""
- verbose := false
- if req.broken != "" {
- broken++
- result = fmt.Sprintf("BROKEN (%v)", req.broken)
- verbose = true
- } else if req.skip != "" {
- skip++
- result = fmt.Sprintf("SKIP (%v)", req.skip)
- verbose = true
- } else {
+ req := req
+ requests = append(requests, req)
+ if req.broken != "" || req.skip != "" {
+ continue
+ }
+ req.finished = make(chan struct{})
+ go func() {
// The tests depend on timings and may be flaky, esp on overloaded/slow machines.
// We don't want to fix this by significantly bumping all timeouts,
// because if a program fails all the time with the default timeouts,
@@ -102,7 +95,7 @@ func (ctx *Context) Run() error {
var resultErr error
for try, failed := 0, 0; try < ctx.Retries; try++ {
req.Output = nil
- req.Info = nil
+ req.Info = ipc.ProgInfo{}
req.Done = make(chan struct{})
ctx.Requests <- req
<-req.Done
@@ -122,6 +115,23 @@ func (ctx *Context) Run() error {
if req.Err == nil {
req.Err = resultErr
}
+ close(req.finished)
+ }()
+ }
+ var ok, fail, broken, skip int
+ for _, req := range requests {
+ result := ""
+ verbose := false
+ if req.broken != "" {
+ broken++
+ result = fmt.Sprintf("BROKEN (%v)", req.broken)
+ verbose = true
+ } else if req.skip != "" {
+ skip++
+ result = fmt.Sprintf("SKIP (%v)", req.skip)
+ verbose = true
+ } else {
+ <-req.finished
if req.Err != nil {
fail++
result = fmt.Sprintf("FAIL: %v",
@@ -400,12 +410,7 @@ func match(props, requires map[string]bool) bool {
}
func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bool, times int) (*RunRequest, error) {
- sysTarget := targets.Get(p.Target.OS, p.Target.Arch)
- cfg := new(ipc.Config)
- opts := new(ipc.ExecOpts)
- cfg.UseShmem = sysTarget.ExecutorUsesShmem
- cfg.UseForkServer = sysTarget.ExecutorUsesForkServer
- cfg.Timeouts = sysTarget.Timeouts(1)
+ var opts ipc.ExecOpts
sandboxFlags, err := ipc.SandboxToFlags(sandbox)
if err != nil {
return nil, err
@@ -425,7 +430,6 @@ func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bo
}
req := &RunRequest{
P: p,
- Cfg: cfg,
Opts: opts,
Repeat: times,
}
@@ -478,7 +482,7 @@ func (ctx *Context) createCTest(p *prog.Prog, sandbox string, threaded bool, tim
req := &RunRequest{
P: p,
Bin: bin,
- Opts: &ipc.ExecOpts{
+ Opts: ipc.ExecOpts{
ExecFlags: ipcFlags,
},
Repeat: times,
@@ -487,19 +491,30 @@ func (ctx *Context) createCTest(p *prog.Prog, sandbox string, threaded bool, tim
}
func checkResult(req *RunRequest) error {
+ var infos []ipc.ProgInfo
isC := req.Bin != ""
if isC {
var err error
- if req.Info, err = parseBinOutput(req); err != nil {
+ if infos, err = parseBinOutput(req); err != nil {
return err
}
+ } else {
+ raw := req.Info
+ for len(raw.Calls) != 0 {
+ ncalls := min(len(raw.Calls), len(req.P.Calls))
+ infos = append(infos, ipc.ProgInfo{
+ Extra: raw.Extra,
+ Calls: raw.Calls[:ncalls],
+ })
+ raw.Calls = raw.Calls[ncalls:]
+ }
}
- if req.Repeat != len(req.Info) {
- return fmt.Errorf("should repeat %v times, but repeated %v\n%s",
- req.Repeat, len(req.Info), req.Output)
+ if req.Repeat != len(infos) {
+ return fmt.Errorf("should repeat %v times, but repeated %v, prog calls %v, info calls %v\n%s",
+ req.Repeat, len(infos), req.P.Calls, len(req.Info.Calls), req.Output)
}
calls := make(map[string]bool)
- for run, info := range req.Info {
+ for run, info := range infos {
for call := range info.Calls {
if err := checkCallResult(req, isC, run, call, info, calls); err != nil {
return err
@@ -509,7 +524,7 @@ func checkResult(req *RunRequest) error {
return nil
}
-func checkCallResult(req *RunRequest, isC bool, run, call int, info *ipc.ProgInfo, calls map[string]bool) error {
+func checkCallResult(req *RunRequest, isC bool, run, call int, info ipc.ProgInfo, calls map[string]bool) error {
inf := info.Calls[call]
want := req.results.Calls[call]
for flag, what := range map[ipc.CallFlags]string{
@@ -571,13 +586,13 @@ func checkCallResult(req *RunRequest, isC bool, run, call int, info *ipc.ProgInf
return nil
}
-func parseBinOutput(req *RunRequest) ([]*ipc.ProgInfo, error) {
- var infos []*ipc.ProgInfo
+func parseBinOutput(req *RunRequest) ([]ipc.ProgInfo, error) {
+ var infos []ipc.ProgInfo
s := bufio.NewScanner(bytes.NewReader(req.Output))
re := regexp.MustCompile("^### call=([0-9]+) errno=([0-9]+)$")
for s.Scan() {
if s.Text() == "### start" {
- infos = append(infos, &ipc.ProgInfo{Calls: make([]ipc.CallInfo, len(req.P.Calls))})
+ infos = append(infos, ipc.ProgInfo{Calls: make([]ipc.CallInfo, len(req.P.Calls))})
}
match := re.FindSubmatch(s.Bytes())
if match == nil {
@@ -596,7 +611,7 @@ func parseBinOutput(req *RunRequest) ([]*ipc.ProgInfo, error) {
return nil, fmt.Errorf("failed to parse errno %q in %q",
string(match[2]), s.Text())
}
- info := infos[len(infos)-1]
+ info := &infos[len(infos)-1]
if call >= uint64(len(info.Calls)) {
return nil, fmt.Errorf("bad call index %v", call)
}
@@ -608,71 +623,3 @@ func parseBinOutput(req *RunRequest) ([]*ipc.ProgInfo, error) {
}
return infos, nil
}
-
-func RunTest(req *RunRequest, executor string) {
- if req.Bin != "" {
- runTestC(req)
- return
- }
- req.Cfg.Executor = executor
- var env *ipc.Env
- defer func() {
- if env != nil {
- env.Close()
- }
- }()
- for run := 0; run < req.Repeat; run++ {
- if run%2 == 0 {
- // Recreate Env every few iterations, this allows to cover more paths.
- if env != nil {
- env.Close()
- env = nil
- }
- var err error
- env, err = ipc.MakeEnv(req.Cfg, 0)
- if err != nil {
- req.Err = fmt.Errorf("failed to create ipc env: %w", err)
- return
- }
- }
- output, info, hanged, err := env.Exec(req.Opts, req.P)
- req.Output = append(req.Output, output...)
- if err != nil {
- req.Err = fmt.Errorf("run %v: failed to run: %w", run, err)
- return
- }
- if hanged {
- req.Err = fmt.Errorf("run %v: hanged", run)
- return
- }
- // Detach Signal and Cover because they point into the output shmem region.
- for i := range info.Calls {
- info.Calls[i].Signal = append([]uint32{}, info.Calls[i].Signal...)
- info.Calls[i].Cover = append([]uint32{}, info.Calls[i].Cover...)
- }
- info.Extra.Signal = append([]uint32{}, info.Extra.Signal...)
- info.Extra.Cover = append([]uint32{}, info.Extra.Cover...)
- req.Info = append(req.Info, info)
- }
-}
-
-func runTestC(req *RunRequest) {
- tmpDir, err := os.MkdirTemp("", "syz-runtest")
- if err != nil {
- req.Err = fmt.Errorf("failed to create temp dir: %w", err)
- return
- }
- defer os.RemoveAll(tmpDir)
- cmd := osutil.Command(req.Bin)
- cmd.Dir = tmpDir
- // Tell ASAN to not mess with our NONFAILING.
- cmd.Env = append(append([]string{}, os.Environ()...), "ASAN_OPTIONS=handle_segv=0 allow_user_segv_handler=1")
- req.Output, req.Err = osutil.Run(20*time.Second, cmd)
- var verr *osutil.VerboseError
- if errors.As(req.Err, &verr) {
- // The process can legitimately do something like exit_group(1).
- // So we ignore the error and rely on the rest of the checks (e.g. syscall return values).
- req.Err = nil
- req.Output = verr.Output
- }
-}