aboutsummaryrefslogtreecommitdiff
path: root/src/internal/fuzz/sys_posix.go
blob: 2473274ecf26127c66e691b8b1dfecb491da0c0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// 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.

//go:build darwin || linux
// +build darwin linux

package fuzz

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
)

type sharedMemSys struct{}

func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (*sharedMem, error) {
	prot := syscall.PROT_READ | syscall.PROT_WRITE
	flags := syscall.MAP_FILE | syscall.MAP_SHARED
	region, err := syscall.Mmap(int(f.Fd()), 0, size, prot, flags)
	if err != nil {
		return nil, err
	}

	return &sharedMem{f: f, region: region, removeOnClose: removeOnClose}, 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.Munmap(m.region),
		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
	memFile := mem.f
	comm.memMu <- mem
	cmd.ExtraFiles = []*os.File{comm.fuzzIn, comm.fuzzOut, memFile}
}

// getWorkerComm returns communication channels in the worker process.
func getWorkerComm() (comm workerComm, err error) {
	fuzzIn := os.NewFile(3, "fuzz_in")
	fuzzOut := os.NewFile(4, "fuzz_out")
	memFile := os.NewFile(5, "fuzz_mem")
	fi, err := memFile.Stat()
	if err != nil {
		return workerComm{}, 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(memFile, size, removeOnClose)
	if err != nil {
		return workerComm{}, err
	}
	memMu := make(chan *sharedMem, 1)
	memMu <- mem
	return workerComm{fuzzIn: fuzzIn, fuzzOut: fuzzOut, memMu: memMu}, nil
}

// isInterruptError returns whether an error was returned by a process that
// was terminated by an interrupt signal (SIGINT).
func isInterruptError(err error) bool {
	exitErr, ok := err.(*exec.ExitError)
	if !ok || exitErr.ExitCode() >= 0 {
		return false
	}
	status := exitErr.Sys().(syscall.WaitStatus)
	return status.Signal() == syscall.SIGINT
}

// terminationSignal checks if err is an exec.ExitError with a signal status.
// If it is, terminationSignal returns the signal and true.
// If not, -1 and false.
func terminationSignal(err error) (os.Signal, bool) {
	exitErr, ok := err.(*exec.ExitError)
	if !ok || exitErr.ExitCode() >= 0 {
		return syscall.Signal(-1), false
	}
	status := exitErr.Sys().(syscall.WaitStatus)
	return status.Signal(), status.Signaled()
}

// isCrashSignal returns whether a signal was likely to have been caused by an
// error in the program that received it, triggered by a fuzz input. For
// example, SIGSEGV would be received after a nil pointer dereference.
// Other signals like SIGKILL or SIGHUP are more likely to have been sent by
// another process, and we shouldn't record a crasher if the worker process
// receives one of these.
//
// Note that Go installs its own signal handlers on startup, so some of these
// signals may only be received if signal handlers are changed. For example,
// SIGSEGV is normally transformed into a panic that causes the process to exit
// with status 2 if not recovered, which we handle as a crash.
func isCrashSignal(signal os.Signal) bool {
	switch signal {
	case
		syscall.SIGILL,  // illegal instruction
		syscall.SIGTRAP, // breakpoint
		syscall.SIGABRT, // abort() called
		syscall.SIGBUS,  // invalid memory access (e.g., misaligned address)
		syscall.SIGFPE,  // math error, e.g., integer divide by zero
		syscall.SIGSEGV, // invalid memory access (e.g., write to read-only)
		syscall.SIGPIPE: // sent data to closed pipe or socket
		return true
	default:
		return false
	}
}