Source file src/internal/fuzz/mem.go

     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  

View as plain text