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