// Copyright 2017 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 ( "fmt" "io/ioutil" "strings" "time" "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/pkg/runtest" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys" ) type checkArgs struct { target *prog.Target sandbox string gitRevision string targetRevision string enabledCalls []int allSandboxes bool ipcConfig *ipc.Config ipcExecOpts *ipc.ExecOpts } func testImage(hostAddr string, args *checkArgs) { log.Logf(0, "connecting to host at %v", hostAddr) conn, err := rpctype.Dial(hostAddr) if err != nil { log.Fatalf("failed to connect: %v", err) } conn.Close() if _, err := checkMachine(args); err != nil { log.Fatalf("%v", err) } } func runTest(target *prog.Target, manager *rpctype.RPCClient, name, executor string) { pollReq := &rpctype.RunTestPollReq{Name: name} for { req := new(rpctype.RunTestPollRes) if err := manager.Call("Manager.Poll", pollReq, req); err != nil { log.Fatalf("Manager.Poll call failed: %v", err) } if len(req.Bin) == 0 && len(req.Prog) == 0 { return } test := convertTestReq(target, req) if test.Err == nil { runtest.RunTest(test, executor) } reply := &rpctype.RunTestDoneArgs{ Name: name, ID: req.ID, Output: test.Output, Info: test.Info, } if test.Err != nil { reply.Error = test.Err.Error() } if err := manager.Call("Manager.Done", reply, nil); err != nil { log.Fatalf("Manager.Done call failed: %v", err) } } } func convertTestReq(target *prog.Target, req *rpctype.RunTestPollRes) *runtest.RunRequest { test := &runtest.RunRequest{ Cfg: req.Cfg, Opts: req.Opts, Repeat: req.Repeat, } if len(req.Bin) != 0 { bin, err := osutil.TempFile("syz-runtest") if err != nil { test.Err = err return test } if err := osutil.WriteExecFile(bin, req.Bin); err != nil { test.Err = err return test } test.Bin = bin } if len(req.Prog) != 0 { p, err := target.Deserialize(req.Prog) if err != nil { test.Err = err return test } test.P = p } return test } func checkMachine(args *checkArgs) (*rpctype.CheckArgs, error) { // Machine checking can be very slow on some machines (qemu without kvm, KMEMLEAK linux, etc), // so print periodic heartbeats for vm.MonitorExecution so that it does not decide that we are dead. done := make(chan bool) defer close(done) go func() { ticker := time.NewTicker(3 * time.Second) defer ticker.Stop() for { select { case <-done: return case <-ticker.C: fmt.Printf("executing program\n") } } }() if err := checkRevisions(args); err != nil { return nil, err } features, err := host.Check(args.target) if err != nil { return nil, err } if feat := features[host.FeatureCoverage]; !feat.Enabled && args.ipcConfig.Flags&ipc.FlagSignal != 0 { return nil, fmt.Errorf("coverage is not supported (%v)", feat.Reason) } if feat := features[host.FeatureSandboxSetuid]; !feat.Enabled && args.ipcConfig.Flags&ipc.FlagSandboxSetuid != 0 { return nil, fmt.Errorf("sandbox=setuid is not supported (%v)", feat.Reason) } if feat := features[host.FeatureSandboxNamespace]; !feat.Enabled && args.ipcConfig.Flags&ipc.FlagSandboxNamespace != 0 { return nil, fmt.Errorf("sandbox=namespace is not supported (%v)", feat.Reason) } if err := checkSimpleProgram(args); err != nil { return nil, err } res := &rpctype.CheckArgs{ Features: features, EnabledCalls: make(map[string][]int), DisabledCalls: make(map[string][]rpctype.SyscallReason), } sandboxes := []string{args.sandbox} if args.allSandboxes { if features[host.FeatureSandboxSetuid].Enabled { sandboxes = append(sandboxes, "setuid") } if features[host.FeatureSandboxNamespace].Enabled { sandboxes = append(sandboxes, "namespace") } } for _, sandbox := range sandboxes { enabledCalls, disabledCalls, err := buildCallList(args.target, args.enabledCalls, sandbox) if err != nil { return nil, err } res.EnabledCalls[sandbox] = enabledCalls res.DisabledCalls[sandbox] = disabledCalls } if args.allSandboxes { var enabled []int for _, id := range res.EnabledCalls["none"] { switch args.target.Syscalls[id].Name { default: enabled = append(enabled, id) case "syz_emit_ethernet", "syz_extract_tcp_res": // Tun is not setup without sandbox, this is a hacky way to workaround this. } } res.EnabledCalls[""] = enabled } return res, nil } func checkRevisions(args *checkArgs) error { log.Logf(0, "checking revisions...") executorArgs := strings.Split(args.ipcConfig.Executor, " ") executorArgs = append(executorArgs, "version") cmd := osutil.Command(executorArgs[0], executorArgs[1:]...) cmd.Stderr = ioutil.Discard if _, err := cmd.StdinPipe(); err != nil { // for the case executor is wrapped with ssh return err } out, err := osutil.Run(time.Minute, cmd) if err != nil { return fmt.Errorf("failed to run executor version: %v", err) } vers := strings.Split(strings.TrimSpace(string(out)), " ") if len(vers) != 4 { return fmt.Errorf("executor version returned bad result: %q", string(out)) } if args.target.Arch != vers[1] { return fmt.Errorf("mismatching target/executor arches: %v vs %v", args.target.Arch, vers[1]) } if sys.GitRevision != vers[3] { return fmt.Errorf("mismatching fuzzer/executor git revisions: %v vs %v", sys.GitRevision, vers[3]) } if args.gitRevision != "" && args.gitRevision != sys.GitRevision { return fmt.Errorf("mismatching manager/fuzzer git revisions: %v vs %v", args.gitRevision, sys.GitRevision) } if args.target.Revision != vers[2] { return fmt.Errorf("mismatching fuzzer/executor system call descriptions: %v vs %v", args.target.Revision, vers[2]) } if args.targetRevision != "" && args.targetRevision != args.target.Revision { return fmt.Errorf("mismatching manager/fuzzer system call descriptions: %v vs %v", args.targetRevision, args.target.Revision) } return nil } func checkSimpleProgram(args *checkArgs) error { log.Logf(0, "testing simple program...") env, err := ipc.MakeEnv(args.ipcConfig, 0) if err != nil { return fmt.Errorf("failed to create ipc env: %v", err) } defer env.Close() p := args.target.GenerateSimpleProg() output, info, failed, hanged, err := env.Exec(args.ipcExecOpts, p) if err != nil { return fmt.Errorf("program execution failed: %v\n%s", err, output) } if hanged { return fmt.Errorf("program hanged:\n%s", output) } if failed { return fmt.Errorf("program failed:\n%s", output) } if len(info) == 0 { return fmt.Errorf("no calls executed:\n%s", output) } if info[0].Errno != 0 { return fmt.Errorf("simple call failed: %+v\n%s", info[0], output) } if args.ipcConfig.Flags&ipc.FlagSignal != 0 && len(info[0].Signal) < 2 { return fmt.Errorf("got no coverage:\n%s", output) } if len(info[0].Signal) < 1 { return fmt.Errorf("got no fallback coverage:\n%s", output) } return nil } func buildCallList(target *prog.Target, enabledCalls []int, sandbox string) ( enabled []int, disabled []rpctype.SyscallReason, err error) { calls := make(map[*prog.Syscall]bool) if len(enabledCalls) != 0 { for _, n := range enabledCalls { if n >= len(target.Syscalls) { return nil, nil, fmt.Errorf("unknown enabled syscall %v", n) } calls[target.Syscalls[n]] = true } } else { for _, c := range target.Syscalls { calls[c] = true } } _, unsupported, err := host.DetectSupportedSyscalls(target, sandbox) if err != nil { return nil, nil, fmt.Errorf("failed to detect host supported syscalls: %v", err) } for c := range calls { if reason, ok := unsupported[c]; ok { log.Logf(1, "unsupported syscall: %v: %v", c.Name, reason) disabled = append(disabled, rpctype.SyscallReason{ ID: c.ID, Reason: reason, }) delete(calls, c) } } _, unsupported = target.TransitivelyEnabledCalls(calls) for c := range calls { if reason, ok := unsupported[c]; ok { log.Logf(1, "transitively unsupported: %v: %v", c.Name, reason) disabled = append(disabled, rpctype.SyscallReason{ ID: c.ID, Reason: reason, }) delete(calls, c) } } if len(calls) == 0 { return nil, nil, fmt.Errorf("all system calls are disabled") } for c := range calls { enabled = append(enabled, c.ID) } return enabled, disabled, nil }