Source file src/internal/fuzz/sys_posix.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  //go:build darwin || freebsd || linux
     6  
     7  package fuzz
     8  
     9  import (
    10  	"fmt"
    11  	"os"
    12  	"os/exec"
    13  	"syscall"
    14  )
    15  
    16  type sharedMemSys struct{}
    17  
    18  func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (*sharedMem, error) {
    19  	prot := syscall.PROT_READ | syscall.PROT_WRITE
    20  	flags := syscall.MAP_FILE | syscall.MAP_SHARED
    21  	region, err := syscall.Mmap(int(f.Fd()), 0, size, prot, flags)
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	return &sharedMem{f: f, region: region, removeOnClose: removeOnClose}, nil
    27  }
    28  
    29  // Close unmaps the shared memory and closes the temporary file. If this
    30  // sharedMem was created with sharedMemTempFile, Close also removes the file.
    31  func (m *sharedMem) Close() error {
    32  	// Attempt all operations, even if we get an error for an earlier operation.
    33  	// os.File.Close may fail due to I/O errors, but we still want to delete
    34  	// the temporary file.
    35  	var errs []error
    36  	errs = append(errs,
    37  		syscall.Munmap(m.region),
    38  		m.f.Close())
    39  	if m.removeOnClose {
    40  		errs = append(errs, os.Remove(m.f.Name()))
    41  	}
    42  	for _, err := range errs {
    43  		if err != nil {
    44  			return err
    45  		}
    46  	}
    47  	return nil
    48  }
    49  
    50  // setWorkerComm configures communication channels on the cmd that will
    51  // run a worker process.
    52  func setWorkerComm(cmd *exec.Cmd, comm workerComm) {
    53  	mem := <-comm.memMu
    54  	memFile := mem.f
    55  	comm.memMu <- mem
    56  	cmd.ExtraFiles = []*os.File{comm.fuzzIn, comm.fuzzOut, memFile}
    57  }
    58  
    59  // getWorkerComm returns communication channels in the worker process.
    60  func getWorkerComm() (comm workerComm, err error) {
    61  	fuzzIn := os.NewFile(3, "fuzz_in")
    62  	fuzzOut := os.NewFile(4, "fuzz_out")
    63  	memFile := os.NewFile(5, "fuzz_mem")
    64  	fi, err := memFile.Stat()
    65  	if err != nil {
    66  		return workerComm{}, err
    67  	}
    68  	size := int(fi.Size())
    69  	if int64(size) != fi.Size() {
    70  		return workerComm{}, fmt.Errorf("fuzz temp file exceeds maximum size")
    71  	}
    72  	removeOnClose := false
    73  	mem, err := sharedMemMapFile(memFile, size, removeOnClose)
    74  	if err != nil {
    75  		return workerComm{}, err
    76  	}
    77  	memMu := make(chan *sharedMem, 1)
    78  	memMu <- mem
    79  	return workerComm{fuzzIn: fuzzIn, fuzzOut: fuzzOut, memMu: memMu}, nil
    80  }
    81  
    82  // isInterruptError returns whether an error was returned by a process that
    83  // was terminated by an interrupt signal (SIGINT).
    84  func isInterruptError(err error) bool {
    85  	exitErr, ok := err.(*exec.ExitError)
    86  	if !ok || exitErr.ExitCode() >= 0 {
    87  		return false
    88  	}
    89  	status := exitErr.Sys().(syscall.WaitStatus)
    90  	return status.Signal() == syscall.SIGINT
    91  }
    92  
    93  // terminationSignal checks if err is an exec.ExitError with a signal status.
    94  // If it is, terminationSignal returns the signal and true.
    95  // If not, -1 and false.
    96  func terminationSignal(err error) (os.Signal, bool) {
    97  	exitErr, ok := err.(*exec.ExitError)
    98  	if !ok || exitErr.ExitCode() >= 0 {
    99  		return syscall.Signal(-1), false
   100  	}
   101  	status := exitErr.Sys().(syscall.WaitStatus)
   102  	return status.Signal(), status.Signaled()
   103  }
   104  
   105  // isCrashSignal returns whether a signal was likely to have been caused by an
   106  // error in the program that received it, triggered by a fuzz input. For
   107  // example, SIGSEGV would be received after a nil pointer dereference.
   108  // Other signals like SIGKILL or SIGHUP are more likely to have been sent by
   109  // another process, and we shouldn't record a crasher if the worker process
   110  // receives one of these.
   111  //
   112  // Note that Go installs its own signal handlers on startup, so some of these
   113  // signals may only be received if signal handlers are changed. For example,
   114  // SIGSEGV is normally transformed into a panic that causes the process to exit
   115  // with status 2 if not recovered, which we handle as a crash.
   116  func isCrashSignal(signal os.Signal) bool {
   117  	switch signal {
   118  	case
   119  		syscall.SIGILL,  // illegal instruction
   120  		syscall.SIGTRAP, // breakpoint
   121  		syscall.SIGABRT, // abort() called
   122  		syscall.SIGBUS,  // invalid memory access (e.g., misaligned address)
   123  		syscall.SIGFPE,  // math error, e.g., integer divide by zero
   124  		syscall.SIGSEGV, // invalid memory access (e.g., write to read-only)
   125  		syscall.SIGPIPE: // sent data to closed pipe or socket
   126  		return true
   127  	default:
   128  		return false
   129  	}
   130  }
   131  

View as plain text