github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/runtest/run_test.go (about)

     1  // Copyright 2018 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package runtest
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"flag"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/google/syzkaller/pkg/csource"
    18  	"github.com/google/syzkaller/pkg/fuzzer/queue"
    19  	"github.com/google/syzkaller/pkg/ipc"
    20  	"github.com/google/syzkaller/pkg/osutil"
    21  	"github.com/google/syzkaller/pkg/testutil"
    22  	"github.com/google/syzkaller/prog"
    23  	"github.com/google/syzkaller/sys/targets"
    24  	_ "github.com/google/syzkaller/sys/test/gen" // pull in the test target
    25  )
    26  
    27  // Can be used as:
    28  // go test -v -run=Test/64_fork ./pkg/runtest -filter=nonfailing
    29  // to select a subset of tests to run.
    30  var flagFilter = flag.String("filter", "", "prefix to match test file names")
    31  
    32  var flagDebug = flag.Bool("debug", false, "include debug output from the executor")
    33  
    34  func Test(t *testing.T) {
    35  	switch runtime.GOOS {
    36  	case targets.OpenBSD:
    37  		t.Skipf("broken on %v", runtime.GOOS)
    38  	}
    39  	// Test only one target in short mode (each takes 5+ seconds to run).
    40  	shortTarget := targets.Get(targets.TestOS, targets.TestArch64)
    41  	for _, sysTarget := range targets.List[targets.TestOS] {
    42  		if testing.Short() && sysTarget != shortTarget {
    43  			continue
    44  		}
    45  		sysTarget1 := targets.Get(sysTarget.OS, sysTarget.Arch)
    46  		t.Run(sysTarget1.Arch, func(t *testing.T) {
    47  			t.Parallel()
    48  			test(t, sysTarget1)
    49  		})
    50  	}
    51  }
    52  
    53  func test(t *testing.T, sysTarget *targets.Target) {
    54  	target, err := prog.GetTarget(sysTarget.OS, sysTarget.Arch)
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	executor := csource.BuildExecutor(t, target, "../../", "-fsanitize-coverage=trace-pc")
    59  	calls := make(map[*prog.Syscall]bool)
    60  	for _, call := range target.Syscalls {
    61  		calls[call] = true
    62  	}
    63  	enabledCalls := map[string]map[*prog.Syscall]bool{
    64  		"":     calls,
    65  		"none": calls,
    66  	}
    67  	ctx := &Context{
    68  		Dir:          filepath.Join("..", "..", "sys", target.OS, targets.TestOS),
    69  		Target:       target,
    70  		Tests:        *flagFilter,
    71  		Features:     0,
    72  		EnabledCalls: enabledCalls,
    73  		LogFunc: func(text string) {
    74  			t.Helper()
    75  			t.Logf(text)
    76  		},
    77  		Retries: 7, // empirical number that seem to reduce flakes to zero
    78  		Verbose: true,
    79  		Debug:   *flagDebug,
    80  	}
    81  
    82  	executorCtx, cancel := context.WithCancel(context.Background())
    83  	t.Cleanup(cancel)
    84  	go func() {
    85  		for {
    86  			select {
    87  			case <-time.After(time.Millisecond):
    88  			case <-executorCtx.Done():
    89  				return
    90  			}
    91  			req := ctx.Next()
    92  			if req == nil {
    93  				continue
    94  			}
    95  			if req.BinaryFile != "" {
    96  				req.Done(runTestC(req))
    97  			} else {
    98  				req.Done(runTest(req, executor))
    99  			}
   100  		}
   101  	}()
   102  	if err := ctx.Run(); err != nil {
   103  		t.Fatal(err)
   104  	}
   105  }
   106  
   107  func runTest(req *queue.Request, executor string) *queue.Result {
   108  	cfg := new(ipc.Config)
   109  	sysTarget := targets.Get(req.Prog.Target.OS, req.Prog.Target.Arch)
   110  	cfg.UseShmem = sysTarget.ExecutorUsesShmem
   111  	cfg.UseForkServer = sysTarget.ExecutorUsesForkServer
   112  	cfg.Timeouts = sysTarget.Timeouts(1)
   113  	cfg.Executor = executor
   114  	env, err := ipc.MakeEnv(cfg, 0)
   115  	if err != nil {
   116  		return &queue.Result{
   117  			Status: queue.ExecFailure,
   118  			Err:    fmt.Errorf("failed to create ipc env: %w", err),
   119  		}
   120  	}
   121  	defer env.Close()
   122  	ret := &queue.Result{Status: queue.Success}
   123  	for run := 0; run < req.Repeat; run++ {
   124  		if run%2 == 0 {
   125  			// Recreate Env every few iterations, this allows to cover more paths.
   126  			env.ForceRestart()
   127  		}
   128  		output, info, hanged, err := env.Exec(&req.ExecOpts, req.Prog)
   129  		ret.Output = append(ret.Output, output...)
   130  		if err != nil {
   131  			return &queue.Result{
   132  				Status: queue.ExecFailure,
   133  				Err:    fmt.Errorf("run %v: failed to run: %w", run, err),
   134  			}
   135  		}
   136  		if hanged {
   137  			return &queue.Result{
   138  				Status: queue.ExecFailure,
   139  				Err:    fmt.Errorf("run %v: hanged", run),
   140  			}
   141  		}
   142  		if run == 0 {
   143  			ret.Info = info
   144  		} else {
   145  			ret.Info.Calls = append(ret.Info.Calls, info.Calls...)
   146  		}
   147  	}
   148  	return ret
   149  }
   150  
   151  func runTestC(req *queue.Request) *queue.Result {
   152  	tmpDir, err := os.MkdirTemp("", "syz-runtest")
   153  	if err != nil {
   154  		return &queue.Result{
   155  			Status: queue.ExecFailure,
   156  			Err:    fmt.Errorf("failed to create temp dir: %w", err),
   157  		}
   158  	}
   159  	defer os.RemoveAll(tmpDir)
   160  	cmd := osutil.Command(req.BinaryFile)
   161  	cmd.Dir = tmpDir
   162  	// Tell ASAN to not mess with our NONFAILING.
   163  	cmd.Env = append(append([]string{}, os.Environ()...), "ASAN_OPTIONS=handle_segv=0 allow_user_segv_handler=1")
   164  	res := &queue.Result{}
   165  	res.Output, res.Err = osutil.Run(20*time.Second, cmd)
   166  	var verr *osutil.VerboseError
   167  	if errors.As(res.Err, &verr) {
   168  		// The process can legitimately do something like exit_group(1).
   169  		// So we ignore the error and rely on the rest of the checks (e.g. syscall return values).
   170  		res.Err = nil
   171  		res.Output = verr.Output
   172  	}
   173  	return res
   174  }
   175  
   176  func TestParsing(t *testing.T) {
   177  	t.Parallel()
   178  	// Test only one target in race mode (we have gazillion of auto-generated Linux test).
   179  	raceTarget := targets.Get(targets.TestOS, targets.TestArch64)
   180  	for OS, arches := range targets.List {
   181  		if testutil.RaceEnabled && OS != raceTarget.OS {
   182  			continue
   183  		}
   184  		dir := filepath.Join("..", "..", "sys", OS, "test")
   185  		if !osutil.IsExist(dir) {
   186  			continue
   187  		}
   188  		files, err := progFileList(dir, "")
   189  		if err != nil {
   190  			t.Fatal(err)
   191  		}
   192  		for arch := range arches {
   193  			if testutil.RaceEnabled && arch != raceTarget.Arch {
   194  				continue
   195  			}
   196  			target, err := prog.GetTarget(OS, arch)
   197  			if err != nil {
   198  				t.Fatal(err)
   199  			}
   200  			sysTarget := targets.Get(target.OS, target.Arch)
   201  			t.Run(fmt.Sprintf("%v/%v", target.OS, target.Arch), func(t *testing.T) {
   202  				t.Parallel()
   203  				for _, file := range files {
   204  					p, requires, _, err := parseProg(target, dir, file)
   205  					if err != nil {
   206  						t.Errorf("failed to parse %v: %v", file, err)
   207  					}
   208  					if p == nil {
   209  						continue
   210  					}
   211  					if runtime.GOOS != sysTarget.BuildOS {
   212  						continue // we need at least preprocessor binary to generate sources
   213  					}
   214  					// syz_mount_image tests are very large and this test takes too long.
   215  					// syz-imagegen that generates does some of this testing (Deserialize/SerializeForExec).
   216  					if requires["manual"] {
   217  						continue
   218  					}
   219  					if _, err = csource.Write(p, csource.ExecutorOpts); err != nil {
   220  						t.Errorf("failed to generate C source for %v: %v", file, err)
   221  					}
   222  				}
   223  			})
   224  		}
   225  	}
   226  }
   227  
   228  func TestRequires(t *testing.T) {
   229  	{
   230  		requires := parseRequires([]byte("# requires: manual arch=amd64"))
   231  		if !checkArch(requires, "amd64") {
   232  			t.Fatalf("amd64 does not pass check")
   233  		}
   234  		if checkArch(requires, "riscv64") {
   235  			t.Fatalf("riscv64 passes check")
   236  		}
   237  	}
   238  	{
   239  		requires := parseRequires([]byte("# requires: -arch=arm64 manual -arch=riscv64"))
   240  		if !checkArch(requires, "amd64") {
   241  			t.Fatalf("amd64 does not pass check")
   242  		}
   243  		if checkArch(requires, "riscv64") {
   244  			t.Fatalf("riscv64 passes check")
   245  		}
   246  	}
   247  }