summaryrefslogtreecommitdiffstats
path: root/src/internal/fuzz/mem.go
blob: 4155e4e83e253d210b85a4e3d74a820f660f9725 (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
// 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 (
	"bytes"
	"fmt"
	"os"
	"unsafe"
)

// sharedMem manages access to a region of virtual memory mapped from a file,
// shared between multiple processes. The region includes space for a header and
// a value of variable length.
//
// When fuzzing, the coordinator creates a sharedMem from a temporary file for
// each worker. This buffer is used to pass values to fuzz between processes.
// Care must be taken to manage access to shared memory across processes;
// sharedMem provides no synchronization on its own. See workerComm for an
// explanation.
type sharedMem struct {
	// f is the file mapped into memory.
	f *os.File

	// region is the mapped region of virtual memory for f. The content of f may
	// be read or written through this slice.
	region []byte

	// removeOnClose is true if the file should be deleted by Close.
	removeOnClose bool

	// sys contains OS-specific information.
	sys sharedMemSys
}

// sharedMemHeader stores metadata in shared memory.
type sharedMemHeader struct {
	// count is the number of times the worker has called the fuzz function.
	// May be reset by coordinator.
	count int64

	// valueLen is the number of bytes in region which should be read.
	valueLen int

	// randState and randInc hold the state of a pseudo-random number generator.
	randState, randInc uint64

	// rawInMem is true if the region holds raw bytes, which occurs during
	// minimization. If true after the worker fails during minimization, this
	// indicates that an unrecoverable error occurred, and the region can be
	// used to retrieve the raw bytes that caused the error.
	rawInMem bool
}

// sharedMemSize returns the size needed for a shared memory buffer that can
// contain values of the given size.
func sharedMemSize(valueSize int) int {
	// TODO(jayconrod): set a reasonable maximum size per platform.
	return int(unsafe.Sizeof(sharedMemHeader{})) + valueSize
}

// sharedMemTempFile creates a new temporary file of the given size, then maps
// it into memory. The file will be removed when the Close method is called.
func sharedMemTempFile(size int) (m *sharedMem, err error) {
	// Create a temporary file.
	f, err := os.CreateTemp("", "fuzz-*")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			f.Close()
			os.Remove(f.Name())
		}
	}()

	// Resize it to the correct size.
	totalSize := sharedMemSize(size)
	if err := f.Truncate(int64(totalSize)); err != nil {
		return nil, err
	}

	// Map the file into memory.
	removeOnClose := true
	return sharedMemMapFile(f, totalSize, removeOnClose)
}

// header returns a pointer to metadata within the shared memory region.
func (m *sharedMem) header() *sharedMemHeader {
	return (*sharedMemHeader)(unsafe.Pointer(&m.region[0]))
}

// valueRef returns the value currently stored in shared memory. The returned
// slice points to shared memory; it is not a copy.
func (m *sharedMem) valueRef() []byte {
	length := m.header().valueLen
	valueOffset := int(unsafe.Sizeof(sharedMemHeader{}))
	return m.region[valueOffset : valueOffset+length]
}

// valueCopy returns a copy of the value stored in shared memory.
func (m *sharedMem) valueCopy() []byte {
	ref := m.valueRef()
	return bytes.Clone(ref)
}

// setValue copies the data in b into the shared memory buffer and sets
// the length. len(b) must be less than or equal to the capacity of the buffer
// (as returned by cap(m.value())).
func (m *sharedMem) setValue(b []byte) {
	v := m.valueRef()
	if len(b) > cap(v) {
		panic(fmt.Sprintf("value length %d larger than shared memory capacity %d", len(b), cap(v)))
	}
	m.header().valueLen = len(b)
	copy(v[:cap(v)], b)
}

// setValueLen sets the length of the shared memory buffer returned by valueRef
// to n, which may be at most the cap of that slice.
//
// Note that we can only store the length in the shared memory header. The full
// slice header contains a pointer, which is likely only valid for one process,
// since each process can map shared memory at a different virtual address.
func (m *sharedMem) setValueLen(n int) {
	v := m.valueRef()
	if n > cap(v) {
		panic(fmt.Sprintf("length %d larger than shared memory capacity %d", n, cap(v)))
	}
	m.header().valueLen = n
}

// TODO(jayconrod): add method to resize the buffer. We'll need that when the
// mutator can increase input length. Only the coordinator will be able to
// do it, since we'll need to send a message to the worker telling it to
// remap the file.