summaryrefslogtreecommitdiffstats
path: root/src/internal/fuzz/sys_windows.go
blob: aa85be7e1c64d7b6ad9c0d039ed264418ab3b90e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fuzz

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
	"unsafe"
)

type sharedMemSys struct {
	mapObj syscall.Handle
}

func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (mem *sharedMem, err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("mapping temporary file %s: %w", f.Name(), err)
		}
	}()

	// Create a file mapping object. The object itself is not shared.
	mapObj, err := syscall.CreateFileMapping(
		syscall.Handle(f.Fd()), // fhandle
		nil,                    // sa
		syscall.PAGE_READWRITE, // prot
		0,                      // maxSizeHigh
		0,                      // maxSizeLow
		nil,                    // name
	)
	if err != nil {
		return nil, err
	}

	// Create a view from the file mapping object.
	access := uint32(syscall.FILE_MAP_READ | syscall.FILE_MAP_WRITE)
	addr, err := syscall.MapViewOfFile(
		mapObj,        // handle
		access,        // access
		0,             // offsetHigh
		0,             // offsetLow
		uintptr(size), // length
	)
	if err != nil {
		syscall.CloseHandle(mapObj)
		return nil, err
	}

	region := unsafe.Slice((*byte)(unsafe.Pointer(addr)), size)
	return &sharedMem{
		f:             f,
		region:        region,
		removeOnClose: removeOnClose,
		sys:           sharedMemSys{mapObj: mapObj},
	}, nil
}

// Close unmaps the shared memory and closes the temporary file. If this
// sharedMem was created with sharedMemTempFile, Close also removes the file.
func (m *sharedMem) Close() error {
	// Attempt all operations, even if we get an error for an earlier operation.
	// os.File.Close may fail due to I/O errors, but we still want to delete
	// the temporary file.
	var errs []error
	errs = append(errs,
		syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&m.region[0]))),
		syscall.CloseHandle(m.sys.mapObj),
		m.f.Close())
	if m.removeOnClose {
		errs = append(errs, os.Remove(m.f.Name()))
	}
	for _, err := range errs {
		if err != nil {
			return err
		}
	}
	return nil
}

// setWorkerComm configures communication channels on the cmd that will
// run a worker process.
func setWorkerComm(cmd *exec.Cmd, comm workerComm) {
	mem := <-comm.memMu
	memName := mem.f.Name()
	comm.memMu <- mem
	syscall.SetHandleInformation(syscall.Handle(comm.fuzzIn.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
	syscall.SetHandleInformation(syscall.Handle(comm.fuzzOut.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
	cmd.Env = append(cmd.Env, fmt.Sprintf("GO_TEST_FUZZ_WORKER_HANDLES=%x,%x,%q", comm.fuzzIn.Fd(), comm.fuzzOut.Fd(), memName))
	cmd.SysProcAttr = &syscall.SysProcAttr{AdditionalInheritedHandles: []syscall.Handle{syscall.Handle(comm.fuzzIn.Fd()), syscall.Handle(comm.fuzzOut.Fd())}}
}

// getWorkerComm returns communication channels in the worker process.
func getWorkerComm() (comm workerComm, err error) {
	v := os.Getenv("GO_TEST_FUZZ_WORKER_HANDLES")
	if v == "" {
		return workerComm{}, fmt.Errorf("GO_TEST_FUZZ_WORKER_HANDLES not set")
	}
	var fuzzInFD, fuzzOutFD uintptr
	var memName string
	if _, err := fmt.Sscanf(v, "%x,%x,%q", &fuzzInFD, &fuzzOutFD, &memName); err != nil {
		return workerComm{}, fmt.Errorf("parsing GO_TEST_FUZZ_WORKER_HANDLES=%s: %v", v, err)
	}

	fuzzIn := os.NewFile(fuzzInFD, "fuzz_in")
	fuzzOut := os.NewFile(fuzzOutFD, "fuzz_out")
	tmpFile, err := os.OpenFile(memName, os.O_RDWR, 0)
	if err != nil {
		return workerComm{}, fmt.Errorf("worker opening temp file: %w", err)
	}
	fi, err := tmpFile.Stat()
	if err != nil {
		return workerComm{}, fmt.Errorf("worker checking temp file size: %w", err)
	}
	size := int(fi.Size())
	if int64(size) != fi.Size() {
		return workerComm{}, fmt.Errorf("fuzz temp file exceeds maximum size")
	}
	removeOnClose := false
	mem, err := sharedMemMapFile(tmpFile, size, removeOnClose)
	if err != nil {
		return workerComm{}, err
	}
	memMu := make(chan *sharedMem, 1)
	memMu <- mem

	return workerComm{fuzzIn: fuzzIn, fuzzOut: fuzzOut, memMu: memMu}, nil
}

func isInterruptError(err error) bool {
	// On Windows, we can't tell whether the process was interrupted by the error
	// returned by Wait. It looks like an ExitError with status 1.
	return false
}

// terminationSignal returns -1 and false because Windows doesn't have signals.
func terminationSignal(err error) (os.Signal, bool) {
	return syscall.Signal(-1), false
}

// isCrashSignal is not implemented because Windows doesn't have signals.
func isCrashSignal(signal os.Signal) bool {
	panic("not implemented: no signals on windows")
}