153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
|
// 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(®ion))
|
||
|
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")
|
||
|
}
|