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	"bytes"
9	"fmt"
10	"os"
11	"unsafe"
12)
13
14// sharedMem manages access to a region of virtual memory mapped from a file,
15// shared between multiple processes. The region includes space for a header and
16// a value of variable length.
17//
18// When fuzzing, the coordinator creates a sharedMem from a temporary file for
19// each worker. This buffer is used to pass values to fuzz between processes.
20// Care must be taken to manage access to shared memory across processes;
21// sharedMem provides no synchronization on its own. See workerComm for an
22// explanation.
23type sharedMem struct {
24	// f is the file mapped into memory.
25	f *os.File
26
27	// region is the mapped region of virtual memory for f. The content of f may
28	// be read or written through this slice.
29	region []byte
30
31	// removeOnClose is true if the file should be deleted by Close.
32	removeOnClose bool
33
34	// sys contains OS-specific information.
35	sys sharedMemSys
36}
37
38// sharedMemHeader stores metadata in shared memory.
39type sharedMemHeader struct {
40	// count is the number of times the worker has called the fuzz function.
41	// May be reset by coordinator.
42	count int64
43
44	// valueLen is the number of bytes in region which should be read.
45	valueLen int
46
47	// randState and randInc hold the state of a pseudo-random number generator.
48	randState, randInc uint64
49
50	// rawInMem is true if the region holds raw bytes, which occurs during
51	// minimization. If true after the worker fails during minimization, this
52	// indicates that an unrecoverable error occurred, and the region can be
53	// used to retrieve the raw bytes that caused the error.
54	rawInMem bool
55}
56
57// sharedMemSize returns the size needed for a shared memory buffer that can
58// contain values of the given size.
59func sharedMemSize(valueSize int) int {
60	// TODO(jayconrod): set a reasonable maximum size per platform.
61	return int(unsafe.Sizeof(sharedMemHeader{})) + valueSize
62}
63
64// sharedMemTempFile creates a new temporary file of the given size, then maps
65// it into memory. The file will be removed when the Close method is called.
66func sharedMemTempFile(size int) (m *sharedMem, err error) {
67	// Create a temporary file.
68	f, err := os.CreateTemp("", "fuzz-*")
69	if err != nil {
70		return nil, err
71	}
72	defer func() {
73		if err != nil {
74			f.Close()
75			os.Remove(f.Name())
76		}
77	}()
78
79	// Resize it to the correct size.
80	totalSize := sharedMemSize(size)
81	if err := f.Truncate(int64(totalSize)); err != nil {
82		return nil, err
83	}
84
85	// Map the file into memory.
86	removeOnClose := true
87	return sharedMemMapFile(f, totalSize, removeOnClose)
88}
89
90// header returns a pointer to metadata within the shared memory region.
91func (m *sharedMem) header() *sharedMemHeader {
92	return (*sharedMemHeader)(unsafe.Pointer(&m.region[0]))
93}
94
95// valueRef returns the value currently stored in shared memory. The returned
96// slice points to shared memory; it is not a copy.
97func (m *sharedMem) valueRef() []byte {
98	length := m.header().valueLen
99	valueOffset := int(unsafe.Sizeof(sharedMemHeader{}))
100	return m.region[valueOffset : valueOffset+length]
101}
102
103// valueCopy returns a copy of the value stored in shared memory.
104func (m *sharedMem) valueCopy() []byte {
105	ref := m.valueRef()
106	return bytes.Clone(ref)
107}
108
109// setValue copies the data in b into the shared memory buffer and sets
110// the length. len(b) must be less than or equal to the capacity of the buffer
111// (as returned by cap(m.value())).
112func (m *sharedMem) setValue(b []byte) {
113	v := m.valueRef()
114	if len(b) > cap(v) {
115		panic(fmt.Sprintf("value length %d larger than shared memory capacity %d", len(b), cap(v)))
116	}
117	m.header().valueLen = len(b)
118	copy(v[:cap(v)], b)
119}
120
121// setValueLen sets the length of the shared memory buffer returned by valueRef
122// to n, which may be at most the cap of that slice.
123//
124// Note that we can only store the length in the shared memory header. The full
125// slice header contains a pointer, which is likely only valid for one process,
126// since each process can map shared memory at a different virtual address.
127func (m *sharedMem) setValueLen(n int) {
128	v := m.valueRef()
129	if n > cap(v) {
130		panic(fmt.Sprintf("length %d larger than shared memory capacity %d", n, cap(v)))
131	}
132	m.header().valueLen = n
133}
134
135// TODO(jayconrod): add method to resize the buffer. We'll need that when the
136// mutator can increase input length. Only the coordinator will be able to
137// do it, since we'll need to send a message to the worker telling it to
138// remap the file.
139