blob: 9c006b010efc2f24dfa2e0149b51a8be4d59e4c4 [file] [log] [blame]
// 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 (
"fmt"
"os"
"os/exec"
"reflect"
"syscall"
"unsafe"
)
type sharedMemSys struct {
mapObj syscall.Handle
}
func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (mem *sharedMem, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("mapping temporary file %s: %w", f.Name(), err)
}
}()
// Create a file mapping object. The object itself is not shared.
mapObj, err := syscall.CreateFileMapping(
syscall.Handle(f.Fd()), // fhandle
nil, // sa
syscall.PAGE_READWRITE, // prot
0, // maxSizeHigh
0, // maxSizeLow
nil, // name
)
if err != nil {
return nil, err
}
// Create a view from the file mapping object.
access := uint32(syscall.FILE_MAP_READ | syscall.FILE_MAP_WRITE)
addr, err := syscall.MapViewOfFile(
mapObj, // handle
access, // access
0, // offsetHigh
0, // offsetLow
uintptr(size), // length
)
if err != nil {
syscall.CloseHandle(mapObj)
return nil, err
}
var region []byte
header := (*reflect.SliceHeader)(unsafe.Pointer(&region))
header.Data = addr
header.Len = size
header.Cap = size
return &sharedMem{
f: f,
region: region,
removeOnClose: removeOnClose,
sys: sharedMemSys{mapObj: mapObj},
}, nil
}
// Close unmaps the shared memory and closes the temporary file. If this
// sharedMem was created with sharedMemTempFile, Close also removes the file.
func (m *sharedMem) Close() error {
// Attempt all operations, even if we get an error for an earlier operation.
// os.File.Close may fail due to I/O errors, but we still want to delete
// the temporary file.
var errs []error
errs = append(errs,
syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&m.region[0]))),
syscall.CloseHandle(m.sys.mapObj),
m.f.Close())
if m.removeOnClose {
errs = append(errs, os.Remove(m.f.Name()))
}
for _, err := range errs {
if err != nil {
return err
}
}
return nil
}
// setWorkerComm configures communication channels on the cmd that will
// run a worker process.
func setWorkerComm(cmd *exec.Cmd, comm workerComm) {
mem := <-comm.memMu
memName := mem.f.Name()
comm.memMu <- mem
syscall.SetHandleInformation(syscall.Handle(comm.fuzzIn.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
syscall.SetHandleInformation(syscall.Handle(comm.fuzzOut.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
cmd.Env = append(cmd.Env, fmt.Sprintf("GO_TEST_FUZZ_WORKER_HANDLES=%x,%x,%q", comm.fuzzIn.Fd(), comm.fuzzOut.Fd(), memName))
cmd.SysProcAttr = &syscall.SysProcAttr{AdditionalInheritedHandles: []syscall.Handle{syscall.Handle(comm.fuzzIn.Fd()), syscall.Handle(comm.fuzzOut.Fd())}}
}
// getWorkerComm returns communication channels in the worker process.
func getWorkerComm() (comm workerComm, err error) {
v := os.Getenv("GO_TEST_FUZZ_WORKER_HANDLES")
if v == "" {
return workerComm{}, fmt.Errorf("GO_TEST_FUZZ_WORKER_HANDLES not set")
}
var fuzzInFD, fuzzOutFD uintptr
var memName string
if _, err := fmt.Sscanf(v, "%x,%x,%q", &fuzzInFD, &fuzzOutFD, &memName); err != nil {
return workerComm{}, fmt.Errorf("parsing GO_TEST_FUZZ_WORKER_HANDLES=%s: %v", v, err)
}
fuzzIn := os.NewFile(fuzzInFD, "fuzz_in")
fuzzOut := os.NewFile(fuzzOutFD, "fuzz_out")
tmpFile, err := os.OpenFile(memName, os.O_RDWR, 0)
if err != nil {
return workerComm{}, fmt.Errorf("worker opening temp file: %w", err)
}
fi, err := tmpFile.Stat()
if err != nil {
return workerComm{}, fmt.Errorf("worker checking temp file size: %w", err)
}
size := int(fi.Size())
if int64(size) != fi.Size() {
return workerComm{}, fmt.Errorf("fuzz temp file exceeds maximum size")
}
removeOnClose := false
mem, err := sharedMemMapFile(tmpFile, size, removeOnClose)
if err != nil {
return workerComm{}, err
}
memMu := make(chan *sharedMem, 1)
memMu <- mem
return workerComm{fuzzIn: fuzzIn, fuzzOut: fuzzOut, memMu: memMu}, nil
}
func isInterruptError(err error) bool {
// On Windows, we can't tell whether the process was interrupted by the error
// returned by Wait. It looks like an ExitError with status 1.
return false
}
// terminationSignal returns -1 and false because Windows doesn't have signals.
func terminationSignal(err error) (os.Signal, bool) {
return syscall.Signal(-1), false
}
// isCrashSignal is not implemented because Windows doesn't have signals.
func isCrashSignal(signal os.Signal) bool {
panic("not implemented: no signals on windows")
}