// 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 communciation 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") }