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
7package fuzz
8
9import (
10	"fmt"
11	"os"
12	"os/exec"
13	"syscall"
14)
15
16type sharedMemSys struct{}
17
18func 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.
31func (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.
52func 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.
60func 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).
84func 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.
96func 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.
116func 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