1 package main
2
3 import (
4 "flag"
5 "go/token"
6 "io/ioutil"
7 "log"
8 "os"
9 "os/exec"
10 "strings"
11 "text/template"
12
13 "golang.org/x/tools/go/packages"
14 )
15
16 var (
17 flagFunc = flag.String("func", "Fuzz", "fuzzer entry point")
18 flagO = flag.String("o", "", "output file")
19 flagPath = flag.String("abs_path", "", "absolute path to fuzzer")
20
21 flagRace = flag.Bool("race", false, "enable data race detection")
22 flagTags = flag.String("tags", "", "a comma-separated list of build tags to consider satisfied during the build")
23 flagV = flag.Bool("v", false, "print the names of packages as they are compiled")
24 flagWork = flag.Bool("work", false, "print the name of the temporary work directory and do not remove it when exiting")
25 flagX = flag.Bool("x", false, "print the commands")
26 flagOverlay = flag.String("overlay", "", "JSON config file that provides an overlay for build operations")
27
28 flagInclude = flag.String("include", "*", "a comma-separated list of import paths to instrument")
29 flagPreserve = flag.String("preserve", "", "a comma-separated list of import paths not to instrument")
30
31 LoadMode = packages.NeedName |
32 packages.NeedFiles |
33 packages.NeedCompiledGoFiles |
34 packages.NeedImports |
35 packages.NeedDeps
36 )
37
38 var include, ignore []string
39
40 func main() {
41 flag.Parse()
42
43 if !token.IsIdentifier(*flagFunc) || !token.IsExported(*flagFunc) {
44 log.Fatal("-func must be an exported identifier")
45 }
46
47 tags := "gofuzz_libfuzzer,libfuzzer"
48 if *flagTags != "" {
49 tags += "," + *flagTags
50 }
51
52 buildFlags := []string{
53 "-buildmode", "c-archive",
54 "-tags", tags,
55 "-trimpath",
56 }
57
58 if *flagRace {
59 buildFlags = append(buildFlags, "-race")
60 }
61 if *flagV {
62 buildFlags = append(buildFlags, "-v")
63 }
64 if *flagWork {
65 buildFlags = append(buildFlags, "-work")
66 }
67 if *flagX {
68 buildFlags = append(buildFlags, "-x")
69 }
70
71 if len(flag.Args()) != 1 {
72 log.Fatal("must specify exactly one package path")
73 }
74 path := flag.Args()[0]
75 if strings.Contains(path, "...") {
76 log.Fatal("package path must not contain ... wildcards")
77 }
78
79 include = strings.Split(*flagInclude, ",")
80 ignore = []string{
81 "runtime/cgo",
82 "runtime/pprof",
83 "runtime/race",
84 "syscall",
85 }
86 if *flagPreserve != "" {
87 ignore = append(ignore, strings.Split(*flagPreserve, ",")...)
88 }
89 buildFlags = append(buildFlags, "-gcflags", "all=-d=libfuzzer")
90
91
92 pkgs, err := packages.Load(&packages.Config{
93 Mode: LoadMode,
94 BuildFlags: buildFlags,
95 Tests: true,
96 }, "pattern="+path)
97 if err != nil {
98 log.Fatal("failed to load packages:", err)
99 }
100 visit := func(pkg *packages.Package) {
101 if !shouldInstrument(pkg.PkgPath) {
102 buildFlags = append(buildFlags, "-gcflags", pkg.PkgPath+"=-d=libfuzzer=0")
103 }
104 }
105 packages.Visit(pkgs, nil, visit)
106 if packages.PrintErrors(pkgs) != 0 {
107 os.Exit(1)
108 }
109
112
113 fuzzerFile, originalFuzzContents, err := rewriteTestingImports(pkgs, *flagFunc)
114 if err != nil {
115 panic(err)
116 }
117 os.Remove(fuzzerFile)
118
119 pkg := pkgs[0]
120
121 importPath := pkg.PkgPath
122 if strings.HasPrefix(importPath, "_/") {
123 importPath = path
124 }
125
126 mainFile, err := ioutil.TempFile(".", "main.*.go")
127 if err != nil {
128 log.Fatal("failed to create temporary file:", err)
129 }
130 defer os.Remove(mainFile.Name())
131
132 type Data struct {
133 PkgPath string
134 Func string
135 Declarations string
136 FuzzerParams string
137 }
138
142
143 err = mainTmpl.Execute(mainFile, &Data{
144 PkgPath: importPath,
145 Func: *flagFunc,
146 })
147 if err != nil {
148 log.Fatal("failed to execute template:", err)
149 }
150 if err := mainFile.Close(); err != nil {
151 log.Fatal(err)
152 }
153
154 out := *flagO
155 if out == "" {
156 out = pkg.Name + "-fuzz.a"
157 }
158
159 args := []string{"build", "-o", out}
160 if *flagOverlay != "" {
161 buildFlags = append(buildFlags, "-overlay", *flagOverlay)
162 }
163 args = append(args, buildFlags...)
164 args = append(args, mainFile.Name())
165 cmd := exec.Command("go", args...)
166
167 cmd.Stdout = os.Stdout
168 cmd.Stderr = os.Stderr
169
170 if err := cmd.Run(); err != nil {
171 log.Fatal("failed to build packages:", err)
172 }
173
174 newFile, err := os.Create(fuzzerFile)
175 if err != nil {
176 panic(err)
177 }
178 defer newFile.Close()
179 _, err = newFile.Write(originalFuzzContents)
180 if err != nil {
181 panic(err)
182 }
183 os.Remove(fuzzerFile + "_fuzz.go")
184 }
185
186
187
188 func shouldInstrument(pkgPath string) bool {
189 for _, incPath := range include {
190 if matchPattern(incPath, pkgPath) {
191 for _, excPath := range ignore {
192 if matchPattern(excPath, pkgPath) {
193 return false
194 }
195 }
196 return true
197 }
198 }
199 return false
200 }
201
202 func matchPattern(pattern, path string) bool {
203 if strings.HasSuffix(pattern, "*") {
204 return strings.HasPrefix(path, strings.TrimSuffix(pattern, "*"))
205 }
206 return strings.EqualFold(path, pattern)
207 }
208
209 var mainTmpl = template.Must(template.New("main").Parse(`
210 // Code generated by go-118-fuzz-build; DO NOT EDIT.
211
212 // +build ignore
213
214 package main
215
216 import (
217 "runtime"
218 "strings"
219 "unsafe"
220 target {{printf "%q" .PkgPath}}
221 "github.com/AdamKorcz/go-118-fuzz-build/testing"
222 )
223
224 // #include <stdint.h>
225 import "C"
226
227 //export LLVMFuzzerTestOneInput
228 func LLVMFuzzerTestOneInput(data *C.char, size C.size_t) C.int {
229 s := (*[1<<30]byte)(unsafe.Pointer(data))[:size:size]
230 //target.{{.Func}}(s)
231 defer catchPanics()
232 LibFuzzer{{.Func}}(s)
233 return 0
234 }
235
236 func LibFuzzer{{.Func}}(data []byte) int {
237 fuzzer := &testing.F{Data:data, T:testing.NewT()}
238 defer fuzzer.CleanupTempDirs()
239 target.{{.Func}}(fuzzer)
240 return 1
241 }
242
243 func catchPanics() {
244 if r := recover(); r != nil {
245 var err string
246 switch r.(type) {
247 case string:
248 err = r.(string)
249 case runtime.Error:
250 err = r.(runtime.Error).Error()
251 case error:
252 err = r.(error).Error()
253 }
254 if strings.Contains(err, "GO-FUZZ-BUILD-PANIC") {
255 return
256 } else {
257 panic(err)
258 }
259 }
260 }
261
262 func main() {
263 }
264 `))
265
View as plain text