forked from Josh-Murray/fuzzer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathharness.go
156 lines (135 loc) · 4.26 KB
/
harness.go
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package main
import (
syscall "golang.org/x/sys/unix"
"log"
"os"
"os/exec"
"runtime"
)
/*
* Traps on every program syscall entry/exit and records the state.
* Function should be called after the child process has been started
* with ptrace enabled and has had syscall.Wait4 called on it once.
* Continues program until exit or crash: ws will be updated with the status.
* Takes as arguments the pid of process to trace and WaitStatus to update.
* Returns an execTrace struct identifying the execution run.
*/
func traceSyscalls(pid int, ws *syscall.WaitStatus) execTrace {
var err error
var regs syscall.PtraceRegs
var curExecTrace execTrace
for {
err = syscall.PtraceSyscall(pid, 0)
if err != nil {
log.Fatal("traceSyscalls failed to call PtraceSyscall")
}
_, err = syscall.Wait4(pid, ws, syscall.WALL, nil)
if err != nil {
log.Fatal("traceSyscalls failed to call Wait4")
}
// Return on program exit or crash.
if ws.Exited() == true || ws.StopSignal() == syscall.SIGSEGV {
return curExecTrace
}
// Collect trace information.
err = syscall.PtraceGetRegs(pid, ®s)
if err != nil {
log.Fatal("traceSyscalls failed to call PtraceGetRegs")
}
traceRegs := getInterestingRegs(®s)
curExecTrace.trace = append(curExecTrace.trace, traceRegs)
}
}
/*
* Harness will run the external binary specified by cmd and feed
* it inputs from the inputCases channel. Interesting TestCases will
* be placed in the interestCases output channel.
*/
func harness(id int, cmd string,
inputCases <-chan TestCase,
interestCases chan<- TestCase) {
// List of unique execution traces for this harness.
var uniqueTraces []execTrace
for inputCase := range inputCases {
var err error
procCmd := exec.Command(cmd)
procCmd.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
procStdin, err := procCmd.StdinPipe()
if err != nil {
log.Fatalf("Harness with id %d failed to connect stdin pipe: %s\n",
id, err.Error())
}
// Lock OS thread as per syscall.SysProcAttr documentation.
runtime.LockOSThread()
err = procCmd.Start()
if err != nil {
log.Fatalf("Harness with id %d failed to start program: %s\n",
id, err.Error())
}
procPid := procCmd.Process.Pid
// Child process recieves signal on startup.
var ws syscall.WaitStatus
_, err = syscall.Wait4(procPid, &ws, syscall.WALL, nil)
if err != nil {
log.Fatalf("Harness with id %d failed to wait: %s\n",
id, err.Error())
}
_, err = procStdin.Write(inputCase.input)
if err != nil {
log.Fatalf("Harness with id %d failed to write to program: %s\n",
id, err.Error())
}
// Process may need pipe closed to continue.
err = procStdin.Close()
if err != nil {
log.Printf("Harness with id %d failed to manually close stdin pipe.\n",
id)
}
// Trace execution and report back interesting cases.
curExecTrace := traceSyscalls(procPid, &ws)
runtime.UnlockOSThread()
if isUniqueTrace(curExecTrace, uniqueTraces) {
uniqueTraces = append(uniqueTraces, curExecTrace)
// This channel is currently not used, leading to deadlock
// if given input here.
//interestCases <- inputCase
}
// Report segfaults and ignore other exit causes.
if ws.StopSignal() == syscall.SIGSEGV {
log.Printf("Harness with id %d crashed process with pid %d\n",
id, procPid)
crashReport(inputCase)
}
}
}
/*
* Creates a "bad.txt" file in the current directory containing
* the input inside crashCase
*/
func crashReport(crashCase TestCase) {
var doExit bool = true
f, err := os.OpenFile("bad.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
// Log the crashing input on any file operation failure.
if err != nil {
log.Println("Failed to create crash output file. Crashing output:")
log.Println(string(crashCase.input))
return
}
nWritten, err := f.Write(crashCase.input)
// Log crash output on failed or incomplete writes.
if err != nil || nWritten != len(crashCase.input) {
log.Println("Failed to write output to crash file. Crashing output:")
log.Println(string(crashCase.input))
// Continue execution to try hit another crash.
doExit = false
}
err = f.Close()
if err != nil {
log.Fatal("crashReport failed to close the file")
}
// Stop execution on first bad output hit unless there was an error
// in file generation.
if doExit {
os.Exit(0)
}
}