1// Copyright 2017 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 4package main 5 6import ( 7 "fmt" 8 "io/ioutil" 9 "strings" 10 "time" 11 12 "github.com/google/syzkaller/pkg/host" 13 "github.com/google/syzkaller/pkg/ipc" 14 "github.com/google/syzkaller/pkg/log" 15 "github.com/google/syzkaller/pkg/osutil" 16 "github.com/google/syzkaller/pkg/rpctype" 17 "github.com/google/syzkaller/pkg/runtest" 18 "github.com/google/syzkaller/prog" 19 "github.com/google/syzkaller/sys" 20) 21 22type checkArgs struct { 23 target *prog.Target 24 sandbox string 25 gitRevision string 26 targetRevision string 27 enabledCalls []int 28 allSandboxes bool 29 ipcConfig *ipc.Config 30 ipcExecOpts *ipc.ExecOpts 31} 32 33func testImage(hostAddr string, args *checkArgs) { 34 log.Logf(0, "connecting to host at %v", hostAddr) 35 conn, err := rpctype.Dial(hostAddr) 36 if err != nil { 37 log.Fatalf("failed to connect: %v", err) 38 } 39 conn.Close() 40 if _, err := checkMachine(args); err != nil { 41 log.Fatalf("%v", err) 42 } 43} 44 45func runTest(target *prog.Target, manager *rpctype.RPCClient, name, executor string) { 46 pollReq := &rpctype.RunTestPollReq{Name: name} 47 for { 48 req := new(rpctype.RunTestPollRes) 49 if err := manager.Call("Manager.Poll", pollReq, req); err != nil { 50 log.Fatalf("Manager.Poll call failed: %v", err) 51 } 52 if len(req.Bin) == 0 && len(req.Prog) == 0 { 53 return 54 } 55 test := convertTestReq(target, req) 56 if test.Err == nil { 57 runtest.RunTest(test, executor) 58 } 59 reply := &rpctype.RunTestDoneArgs{ 60 Name: name, 61 ID: req.ID, 62 Output: test.Output, 63 Info: test.Info, 64 } 65 if test.Err != nil { 66 reply.Error = test.Err.Error() 67 } 68 if err := manager.Call("Manager.Done", reply, nil); err != nil { 69 log.Fatalf("Manager.Done call failed: %v", err) 70 } 71 } 72} 73 74func convertTestReq(target *prog.Target, req *rpctype.RunTestPollRes) *runtest.RunRequest { 75 test := &runtest.RunRequest{ 76 Cfg: req.Cfg, 77 Opts: req.Opts, 78 Repeat: req.Repeat, 79 } 80 if len(req.Bin) != 0 { 81 bin, err := osutil.TempFile("syz-runtest") 82 if err != nil { 83 test.Err = err 84 return test 85 } 86 if err := osutil.WriteExecFile(bin, req.Bin); err != nil { 87 test.Err = err 88 return test 89 } 90 test.Bin = bin 91 } 92 if len(req.Prog) != 0 { 93 p, err := target.Deserialize(req.Prog) 94 if err != nil { 95 test.Err = err 96 return test 97 } 98 test.P = p 99 } 100 return test 101} 102 103func checkMachine(args *checkArgs) (*rpctype.CheckArgs, error) { 104 // Machine checking can be very slow on some machines (qemu without kvm, KMEMLEAK linux, etc), 105 // so print periodic heartbeats for vm.MonitorExecution so that it does not decide that we are dead. 106 done := make(chan bool) 107 defer close(done) 108 go func() { 109 ticker := time.NewTicker(3 * time.Second) 110 defer ticker.Stop() 111 for { 112 select { 113 case <-done: 114 return 115 case <-ticker.C: 116 fmt.Printf("executing program\n") 117 } 118 } 119 }() 120 if err := checkRevisions(args); err != nil { 121 return nil, err 122 } 123 features, err := host.Check(args.target) 124 if err != nil { 125 return nil, err 126 } 127 if feat := features[host.FeatureCoverage]; !feat.Enabled && 128 args.ipcConfig.Flags&ipc.FlagSignal != 0 { 129 return nil, fmt.Errorf("coverage is not supported (%v)", feat.Reason) 130 } 131 if feat := features[host.FeatureSandboxSetuid]; !feat.Enabled && 132 args.ipcConfig.Flags&ipc.FlagSandboxSetuid != 0 { 133 return nil, fmt.Errorf("sandbox=setuid is not supported (%v)", feat.Reason) 134 } 135 if feat := features[host.FeatureSandboxNamespace]; !feat.Enabled && 136 args.ipcConfig.Flags&ipc.FlagSandboxNamespace != 0 { 137 return nil, fmt.Errorf("sandbox=namespace is not supported (%v)", feat.Reason) 138 } 139 if err := checkSimpleProgram(args); err != nil { 140 return nil, err 141 } 142 res := &rpctype.CheckArgs{ 143 Features: features, 144 EnabledCalls: make(map[string][]int), 145 DisabledCalls: make(map[string][]rpctype.SyscallReason), 146 } 147 sandboxes := []string{args.sandbox} 148 if args.allSandboxes { 149 if features[host.FeatureSandboxSetuid].Enabled { 150 sandboxes = append(sandboxes, "setuid") 151 } 152 if features[host.FeatureSandboxNamespace].Enabled { 153 sandboxes = append(sandboxes, "namespace") 154 } 155 } 156 for _, sandbox := range sandboxes { 157 enabledCalls, disabledCalls, err := buildCallList(args.target, args.enabledCalls, sandbox) 158 if err != nil { 159 return nil, err 160 } 161 res.EnabledCalls[sandbox] = enabledCalls 162 res.DisabledCalls[sandbox] = disabledCalls 163 } 164 if args.allSandboxes { 165 var enabled []int 166 for _, id := range res.EnabledCalls["none"] { 167 switch args.target.Syscalls[id].Name { 168 default: 169 enabled = append(enabled, id) 170 case "syz_emit_ethernet", "syz_extract_tcp_res": 171 // Tun is not setup without sandbox, this is a hacky way to workaround this. 172 } 173 } 174 res.EnabledCalls[""] = enabled 175 } 176 return res, nil 177} 178 179func checkRevisions(args *checkArgs) error { 180 log.Logf(0, "checking revisions...") 181 executorArgs := strings.Split(args.ipcConfig.Executor, " ") 182 executorArgs = append(executorArgs, "version") 183 cmd := osutil.Command(executorArgs[0], executorArgs[1:]...) 184 cmd.Stderr = ioutil.Discard 185 if _, err := cmd.StdinPipe(); err != nil { // for the case executor is wrapped with ssh 186 return err 187 } 188 out, err := osutil.Run(time.Minute, cmd) 189 if err != nil { 190 return fmt.Errorf("failed to run executor version: %v", err) 191 } 192 vers := strings.Split(strings.TrimSpace(string(out)), " ") 193 if len(vers) != 4 { 194 return fmt.Errorf("executor version returned bad result: %q", string(out)) 195 } 196 if args.target.Arch != vers[1] { 197 return fmt.Errorf("mismatching target/executor arches: %v vs %v", args.target.Arch, vers[1]) 198 } 199 if sys.GitRevision != vers[3] { 200 return fmt.Errorf("mismatching fuzzer/executor git revisions: %v vs %v", 201 sys.GitRevision, vers[3]) 202 } 203 if args.gitRevision != "" && args.gitRevision != sys.GitRevision { 204 return fmt.Errorf("mismatching manager/fuzzer git revisions: %v vs %v", 205 args.gitRevision, sys.GitRevision) 206 } 207 if args.target.Revision != vers[2] { 208 return fmt.Errorf("mismatching fuzzer/executor system call descriptions: %v vs %v", 209 args.target.Revision, vers[2]) 210 } 211 if args.targetRevision != "" && args.targetRevision != args.target.Revision { 212 return fmt.Errorf("mismatching manager/fuzzer system call descriptions: %v vs %v", 213 args.targetRevision, args.target.Revision) 214 } 215 return nil 216} 217 218func checkSimpleProgram(args *checkArgs) error { 219 log.Logf(0, "testing simple program...") 220 env, err := ipc.MakeEnv(args.ipcConfig, 0) 221 if err != nil { 222 return fmt.Errorf("failed to create ipc env: %v", err) 223 } 224 defer env.Close() 225 p := args.target.GenerateSimpleProg() 226 output, info, failed, hanged, err := env.Exec(args.ipcExecOpts, p) 227 if err != nil { 228 return fmt.Errorf("program execution failed: %v\n%s", err, output) 229 } 230 if hanged { 231 return fmt.Errorf("program hanged:\n%s", output) 232 } 233 if failed { 234 return fmt.Errorf("program failed:\n%s", output) 235 } 236 if len(info) == 0 { 237 return fmt.Errorf("no calls executed:\n%s", output) 238 } 239 if info[0].Errno != 0 { 240 return fmt.Errorf("simple call failed: %+v\n%s", info[0], output) 241 } 242 if args.ipcConfig.Flags&ipc.FlagSignal != 0 && len(info[0].Signal) < 2 { 243 return fmt.Errorf("got no coverage:\n%s", output) 244 } 245 if len(info[0].Signal) < 1 { 246 return fmt.Errorf("got no fallback coverage:\n%s", output) 247 } 248 return nil 249} 250 251func buildCallList(target *prog.Target, enabledCalls []int, sandbox string) ( 252 enabled []int, disabled []rpctype.SyscallReason, err error) { 253 calls := make(map[*prog.Syscall]bool) 254 if len(enabledCalls) != 0 { 255 for _, n := range enabledCalls { 256 if n >= len(target.Syscalls) { 257 return nil, nil, fmt.Errorf("unknown enabled syscall %v", n) 258 } 259 calls[target.Syscalls[n]] = true 260 } 261 } else { 262 for _, c := range target.Syscalls { 263 calls[c] = true 264 } 265 } 266 _, unsupported, err := host.DetectSupportedSyscalls(target, sandbox) 267 if err != nil { 268 return nil, nil, fmt.Errorf("failed to detect host supported syscalls: %v", err) 269 } 270 for c := range calls { 271 if reason, ok := unsupported[c]; ok { 272 log.Logf(1, "unsupported syscall: %v: %v", c.Name, reason) 273 disabled = append(disabled, rpctype.SyscallReason{ 274 ID: c.ID, 275 Reason: reason, 276 }) 277 delete(calls, c) 278 } 279 } 280 _, unsupported = target.TransitivelyEnabledCalls(calls) 281 for c := range calls { 282 if reason, ok := unsupported[c]; ok { 283 log.Logf(1, "transitively unsupported: %v: %v", c.Name, reason) 284 disabled = append(disabled, rpctype.SyscallReason{ 285 ID: c.ID, 286 Reason: reason, 287 }) 288 delete(calls, c) 289 } 290 } 291 if len(calls) == 0 { 292 return nil, nil, fmt.Errorf("all system calls are disabled") 293 } 294 for c := range calls { 295 enabled = append(enabled, c.ID) 296 } 297 return enabled, disabled, nil 298} 299