| // 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. |