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