1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package fuzz
6
7import (
8	"fmt"
9	"os"
10	"os/exec"
11	"syscall"
12	"unsafe"
13)
14
15type sharedMemSys struct {
16	mapObj syscall.Handle
17}
18
19func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (mem *sharedMem, err error) {
20	defer func() {
21		if err != nil {
22			err = fmt.Errorf("mapping temporary file %s: %w", f.Name(), err)
23		}
24	}()
25
26	// Create a file mapping object. The object itself is not shared.
27	mapObj, err := syscall.CreateFileMapping(
28		syscall.Handle(f.Fd()), // fhandle
29		nil,                    // sa
30		syscall.PAGE_READWRITE, // prot
31		0,                      // maxSizeHigh
32		0,                      // maxSizeLow
33		nil,                    // name
34	)
35	if err != nil {
36		return nil, err
37	}
38
39	// Create a view from the file mapping object.
40	access := uint32(syscall.FILE_MAP_READ | syscall.FILE_MAP_WRITE)
41	addr, err := syscall.MapViewOfFile(
42		mapObj,        // handle
43		access,        // access
44		0,             // offsetHigh
45		0,             // offsetLow
46		uintptr(size), // length
47	)
48	if err != nil {
49		syscall.CloseHandle(mapObj)
50		return nil, err
51	}
52
53	region := unsafe.Slice((*byte)(unsafe.Pointer(addr)), size)
54	return &sharedMem{
55		f:             f,
56		region:        region,
57		removeOnClose: removeOnClose,
58		sys:           sharedMemSys{mapObj: mapObj},
59	}, nil
60}
61
62// Close unmaps the shared memory and closes the temporary file. If this
63// sharedMem was created with sharedMemTempFile, Close also removes the file.
64func (m *sharedMem) Close() error {
65	// Attempt all operations, even if we get an error for an earlier operation.
66	// os.File.Close may fail due to I/O errors, but we still want to delete
67	// the temporary file.
68	var errs []error
69	errs = append(errs,
70		syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&m.region[0]))),
71		syscall.CloseHandle(m.sys.mapObj),
72		m.f.Close())
73	if m.removeOnClose {
74		errs = append(errs, os.Remove(m.f.Name()))
75	}
76	for _, err := range errs {
77		if err != nil {
78			return err
79		}
80	}
81	return nil
82}
83
84// setWorkerComm configures communication channels on the cmd that will
85// run a worker process.
86func setWorkerComm(cmd *exec.Cmd, comm workerComm) {
87	mem := <-comm.memMu
88	memFD := mem.f.Fd()
89	comm.memMu <- mem
90	syscall.SetHandleInformation(syscall.Handle(comm.fuzzIn.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
91	syscall.SetHandleInformation(syscall.Handle(comm.fuzzOut.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
92	syscall.SetHandleInformation(syscall.Handle(memFD), syscall.HANDLE_FLAG_INHERIT, 1)
93	cmd.Env = append(cmd.Env, fmt.Sprintf("GO_TEST_FUZZ_WORKER_HANDLES=%x,%x,%x", comm.fuzzIn.Fd(), comm.fuzzOut.Fd(), memFD))
94	cmd.SysProcAttr = &syscall.SysProcAttr{AdditionalInheritedHandles: []syscall.Handle{syscall.Handle(comm.fuzzIn.Fd()), syscall.Handle(comm.fuzzOut.Fd()), syscall.Handle(memFD)}}
95}
96
97// getWorkerComm returns communication channels in the worker process.
98func getWorkerComm() (comm workerComm, err error) {
99	v := os.Getenv("GO_TEST_FUZZ_WORKER_HANDLES")
100	if v == "" {
101		return workerComm{}, fmt.Errorf("GO_TEST_FUZZ_WORKER_HANDLES not set")
102	}
103	var fuzzInFD, fuzzOutFD, memFileFD uintptr
104	if _, err := fmt.Sscanf(v, "%x,%x,%x", &fuzzInFD, &fuzzOutFD, &memFileFD); err != nil {
105		return workerComm{}, fmt.Errorf("parsing GO_TEST_FUZZ_WORKER_HANDLES=%s: %v", v, err)
106	}
107
108	fuzzIn := os.NewFile(fuzzInFD, "fuzz_in")
109	fuzzOut := os.NewFile(fuzzOutFD, "fuzz_out")
110	memFile := os.NewFile(memFileFD, "fuzz_mem")
111	fi, err := memFile.Stat()
112	if err != nil {
113		return workerComm{}, fmt.Errorf("worker checking temp file size: %w", err)
114	}
115	size := int(fi.Size())
116	if int64(size) != fi.Size() {
117		return workerComm{}, fmt.Errorf("fuzz temp file exceeds maximum size")
118	}
119	removeOnClose := false
120	mem, err := sharedMemMapFile(memFile, size, removeOnClose)
121	if err != nil {
122		return workerComm{}, err
123	}
124	memMu := make(chan *sharedMem, 1)
125	memMu <- mem
126
127	return workerComm{fuzzIn: fuzzIn, fuzzOut: fuzzOut, memMu: memMu}, nil
128}
129
130func isInterruptError(err error) bool {
131	// On Windows, we can't tell whether the process was interrupted by the error
132	// returned by Wait. It looks like an ExitError with status 1.
133	return false
134}
135
136// terminationSignal returns -1 and false because Windows doesn't have signals.
137func terminationSignal(err error) (os.Signal, bool) {
138	return syscall.Signal(-1), false
139}
140
141// isCrashSignal is not implemented because Windows doesn't have signals.
142func isCrashSignal(signal os.Signal) bool {
143	panic("not implemented: no signals on windows")
144}
145