github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/runtest/executor_test.go (about)

     1  // Copyright 2015 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  	"fmt"
     9  	"math/rand"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/google/syzkaller/pkg/csource"
    19  	"github.com/google/syzkaller/pkg/flatrpc"
    20  	"github.com/google/syzkaller/pkg/fuzzer/queue"
    21  	"github.com/google/syzkaller/pkg/image"
    22  	"github.com/google/syzkaller/pkg/osutil"
    23  	"github.com/google/syzkaller/pkg/testutil"
    24  	"github.com/google/syzkaller/prog"
    25  	_ "github.com/google/syzkaller/sys"
    26  	"github.com/google/syzkaller/sys/targets"
    27  )
    28  
    29  // Find the corresponding QEMU binary for the target arch, if needed.
    30  func qemuBinary(arch string) (string, error) {
    31  	qarch, ok := map[string]string{
    32  		"386":      "i386",
    33  		"amd64":    "x86_64",
    34  		"arm64":    "aarch64",
    35  		"arm":      "arm",
    36  		"mips64le": "mips64el",
    37  		"ppc64le":  "ppc64le",
    38  		"riscv64":  "riscv64",
    39  		"s390x":    "s390x",
    40  	}[arch]
    41  	if !ok {
    42  		return "", fmt.Errorf("unsupported architecture: %s", arch)
    43  	}
    44  
    45  	qemuBinary := "qemu-" + qarch
    46  	path, err := exec.LookPath(qemuBinary)
    47  	if err != nil {
    48  		return "", fmt.Errorf("qemu binary not found in PATH: %s", qemuBinary)
    49  	}
    50  
    51  	return filepath.Base(path), nil
    52  }
    53  
    54  // If the tests are running on CI, or if there is an assertion failure in the executor,
    55  // report a fatal error. Otherwise, assume cross-arch execution does not work, and skip
    56  // the test.
    57  func handleCrossArchError(t *testing.T, err error) {
    58  	if os.Getenv("CI") != "" || strings.Contains(err.Error(), "SYZFAIL:") {
    59  		t.Fatal(err)
    60  	} else {
    61  		t.Skipf("skipping, cross-arch execution failed: %v", err)
    62  	}
    63  }
    64  
    65  // TestExecutor runs all internal executor unit tests.
    66  // We do it here because we already build executor binary here.
    67  func TestExecutor(t *testing.T) {
    68  	t.Parallel()
    69  	for _, sysTarget := range targets.List[runtime.GOOS] {
    70  		sysTarget := targets.Get(runtime.GOOS, sysTarget.Arch)
    71  		t.Run(sysTarget.Arch, func(t *testing.T) {
    72  			if sysTarget.BrokenCompiler != "" {
    73  				t.Skipf("skipping, broken cross-compiler: %v", sysTarget.BrokenCompiler)
    74  			}
    75  			t.Parallel()
    76  			dir := t.TempDir()
    77  			target, err := prog.GetTarget(runtime.GOOS, sysTarget.Arch)
    78  			if err != nil {
    79  				t.Fatal(err)
    80  			}
    81  			bin := csource.BuildExecutor(t, target, "../..")
    82  			if sysTarget.Arch == runtime.GOARCH || sysTarget.VMArch == runtime.GOARCH {
    83  				// Execute the tests natively.
    84  				if _, err := osutil.RunCmd(time.Minute, dir, bin, "test"); err != nil {
    85  					t.Fatal(err)
    86  				}
    87  			} else {
    88  				// Get QEMU binary for the target arch. This code might run inside Docker, so it cannot
    89  				// rely on binfmt_misc handlers provided by qemu-user.
    90  				qemu, err := qemuBinary(sysTarget.Arch)
    91  				if err != nil {
    92  					handleCrossArchError(t, err)
    93  				}
    94  				if _, err := osutil.RunCmd(time.Minute, dir, qemu, bin, "test"); err != nil {
    95  					handleCrossArchError(t, err)
    96  				}
    97  			}
    98  		})
    99  	}
   100  }
   101  
   102  func TestZlib(t *testing.T) {
   103  	t.Parallel()
   104  	target, err := prog.GetTarget(targets.TestOS, targets.TestArch64)
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  	sysTarget := targets.Get(target.OS, target.Arch)
   109  	if sysTarget.BrokenCompiler != "" {
   110  		t.Skipf("skipping, broken cross-compiler: %v", sysTarget.BrokenCompiler)
   111  	}
   112  	executor := csource.BuildExecutor(t, target, "../..")
   113  	source := queue.Plain()
   114  	startRPCServer(t, target, executor, source, rpcParams{manyProcs: true})
   115  	r := rand.New(testutil.RandSource(t))
   116  	for i := 0; i < 10; i++ {
   117  		data := testutil.RandMountImage(r)
   118  		compressed := image.Compress(data)
   119  		text := fmt.Sprintf(`syz_compare_zlib(&(0x7f0000000000)="$%s", AUTO, &(0x7f0000800000)="$%s", AUTO)`,
   120  			image.EncodeB64(data), image.EncodeB64(compressed))
   121  		p, err := target.Deserialize([]byte(text), prog.Strict)
   122  		if err != nil {
   123  			t.Fatalf("failed to deserialize empty program: %v", err)
   124  		}
   125  		req := &queue.Request{
   126  			Prog:         p,
   127  			ReturnError:  true,
   128  			ReturnOutput: true,
   129  			ExecOpts: flatrpc.ExecOpts{
   130  				EnvFlags: flatrpc.ExecEnvSandboxNone,
   131  			},
   132  		}
   133  		source.Submit(req)
   134  		res := req.Wait(context.Background())
   135  		if res.Err != nil {
   136  			t.Fatalf("program execution failed: %v\n%s", res.Err, res.Output)
   137  		}
   138  		if res.Info.Calls[0].Error != 0 {
   139  			t.Fatalf("data comparison failed: %v\n%s", res.Info.Calls[0].Error, res.Output)
   140  		}
   141  	}
   142  }
   143  
   144  func TestExecutorCommonExt(t *testing.T) {
   145  	t.Parallel()
   146  	target, err := prog.GetTarget("test", "64_fork")
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	sysTarget := targets.Get(target.OS, target.Arch)
   151  	if sysTarget.BrokenCompiler != "" {
   152  		t.Skipf("skipping, broken cross-compiler: %v", sysTarget.BrokenCompiler)
   153  	}
   154  	executor := csource.BuildExecutor(t, target, "../..", "-DSYZ_TEST_COMMON_EXT_EXAMPLE=1")
   155  	// The example setup_ext_test does:
   156  	// *(uint64*)(SYZ_DATA_OFFSET + 0x1234) = 0xbadc0ffee;
   157  	// The following program tests that that value is present at 0x1234.
   158  	test := `syz_compare(&(0x7f0000001234)="", 0x8, &(0x7f0000000000)=@blob="eeffc0ad0b000000", AUTO)`
   159  	p, err := target.Deserialize([]byte(test), prog.Strict)
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	source := queue.Plain()
   164  	startRPCServer(t, target, executor, source, rpcParams{})
   165  	req := &queue.Request{
   166  		Prog:         p,
   167  		ReturnError:  true,
   168  		ReturnOutput: true,
   169  		ExecOpts: flatrpc.ExecOpts{
   170  			EnvFlags: flatrpc.ExecEnvSandboxNone,
   171  		},
   172  	}
   173  	source.Submit(req)
   174  	res := req.Wait(context.Background())
   175  	if res.Err != nil {
   176  		t.Fatalf("program execution failed: %v\n%s", res.Err, res.Output)
   177  	}
   178  	if call := res.Info.Calls[0]; call.Flags&flatrpc.CallFlagFinished == 0 || call.Error != 0 {
   179  		t.Fatalf("bad call result: flags=%x errno=%v", call.Flags, call.Error)
   180  	}
   181  }