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 5 package fuzz 6 7 import ( 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. 23 type 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. 39 type 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. 59 func 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. 66 func 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. 91 func (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. 97 func (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. 104 func (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())). 112 func (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. 127 func (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