package main import ( "flag" "go/token" "io/ioutil" "log" "os" "os/exec" "strings" "text/template" "golang.org/x/tools/go/packages" ) var ( flagFunc = flag.String("func", "Fuzz", "fuzzer entry point") flagO = flag.String("o", "", "output file") flagPath = flag.String("abs_path", "", "absolute path to fuzzer") flagRace = flag.Bool("race", false, "enable data race detection") flagTags = flag.String("tags", "", "a comma-separated list of build tags to consider satisfied during the build") flagV = flag.Bool("v", false, "print the names of packages as they are compiled") flagWork = flag.Bool("work", false, "print the name of the temporary work directory and do not remove it when exiting") flagX = flag.Bool("x", false, "print the commands") flagOverlay = flag.String("overlay", "", "JSON config file that provides an overlay for build operations") flagInclude = flag.String("include", "*", "a comma-separated list of import paths to instrument") flagPreserve = flag.String("preserve", "", "a comma-separated list of import paths not to instrument") LoadMode = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedDeps ) var include, ignore []string func main() { flag.Parse() if !token.IsIdentifier(*flagFunc) || !token.IsExported(*flagFunc) { log.Fatal("-func must be an exported identifier") } tags := "gofuzz_libfuzzer,libfuzzer" if *flagTags != "" { tags += "," + *flagTags } buildFlags := []string{ "-buildmode", "c-archive", "-tags", tags, "-trimpath", } if *flagRace { buildFlags = append(buildFlags, "-race") } if *flagV { buildFlags = append(buildFlags, "-v") } if *flagWork { buildFlags = append(buildFlags, "-work") } if *flagX { buildFlags = append(buildFlags, "-x") } if len(flag.Args()) != 1 { log.Fatal("must specify exactly one package path") } path := flag.Args()[0] if strings.Contains(path, "...") { log.Fatal("package path must not contain ... wildcards") } include = strings.Split(*flagInclude, ",") ignore = []string{ "runtime/cgo", // No reason to instrument these. "runtime/pprof", // No reason to instrument these. "runtime/race", // No reason to instrument these. "syscall", // https://github.com/google/oss-fuzz/issues/3639 } if *flagPreserve != "" { ignore = append(ignore, strings.Split(*flagPreserve, ",")...) } buildFlags = append(buildFlags, "-gcflags", "all=-d=libfuzzer") //fset := token.NewFileSet() pkgs, err := packages.Load(&packages.Config{ Mode: LoadMode, BuildFlags: buildFlags, Tests: true, }, "pattern="+path) if err != nil { log.Fatal("failed to load packages:", err) } visit := func(pkg *packages.Package) { if !shouldInstrument(pkg.PkgPath) { buildFlags = append(buildFlags, "-gcflags", pkg.PkgPath+"=-d=libfuzzer=0") } } packages.Visit(pkgs, nil, visit) if packages.PrintErrors(pkgs) != 0 { os.Exit(1) } /*if len(pkgs) != 1 { log.Fatal("package path matched multiple packages") }*/ fuzzerFile, originalFuzzContents, err := rewriteTestingImports(pkgs, *flagFunc) if err != nil { panic(err) } os.Remove(fuzzerFile) pkg := pkgs[0] importPath := pkg.PkgPath if strings.HasPrefix(importPath, "_/") { importPath = path } mainFile, err := ioutil.TempFile(".", "main.*.go") if err != nil { log.Fatal("failed to create temporary file:", err) } defer os.Remove(mainFile.Name()) type Data struct { PkgPath string Func string Declarations string FuzzerParams string } /*err = mainTmpl.Execute(os.Stdout, &Data{ PkgPath: importPath, Func: *flagFunc, })*/ //return err = mainTmpl.Execute(mainFile, &Data{ PkgPath: importPath, Func: *flagFunc, }) if err != nil { log.Fatal("failed to execute template:", err) } if err := mainFile.Close(); err != nil { log.Fatal(err) } out := *flagO if out == "" { out = pkg.Name + "-fuzz.a" } args := []string{"build", "-o", out} if *flagOverlay != "" { buildFlags = append(buildFlags, "-overlay", *flagOverlay) } args = append(args, buildFlags...) args = append(args, mainFile.Name()) cmd := exec.Command("go", args...) //cmd := exec.Command("gotip", args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { log.Fatal("failed to build packages:", err) } newFile, err := os.Create(fuzzerFile) if err != nil { panic(err) } defer newFile.Close() _, err = newFile.Write(originalFuzzContents) if err != nil { panic(err) } os.Remove(fuzzerFile + "_fuzz.go") } // Packages that match one of the include patterns (default is include all packages) // and none of the exclude patterns (default is none) will be instrumented. func shouldInstrument(pkgPath string) bool { for _, incPath := range include { if matchPattern(incPath, pkgPath) { for _, excPath := range ignore { if matchPattern(excPath, pkgPath) { return false } } return true } } return false } func matchPattern(pattern, path string) bool { if strings.HasSuffix(pattern, "*") { return strings.HasPrefix(path, strings.TrimSuffix(pattern, "*")) } return strings.EqualFold(path, pattern) } var mainTmpl = template.Must(template.New("main").Parse(` // Code generated by go-118-fuzz-build; DO NOT EDIT. // +build ignore package main import ( "runtime" "strings" "unsafe" target {{printf "%q" .PkgPath}} "github.com/AdamKorcz/go-118-fuzz-build/testing" ) // #include import "C" //export LLVMFuzzerTestOneInput func LLVMFuzzerTestOneInput(data *C.char, size C.size_t) C.int { s := (*[1<<30]byte)(unsafe.Pointer(data))[:size:size] //target.{{.Func}}(s) defer catchPanics() LibFuzzer{{.Func}}(s) return 0 } func LibFuzzer{{.Func}}(data []byte) int { fuzzer := &testing.F{Data:data, T:testing.NewT()} defer fuzzer.CleanupTempDirs() target.{{.Func}}(fuzzer) return 1 } func catchPanics() { if r := recover(); r != nil { var err string switch r.(type) { case string: err = r.(string) case runtime.Error: err = r.(runtime.Error).Error() case error: err = r.(error).Error() } if strings.Contains(err, "GO-FUZZ-BUILD-PANIC") { return } else { panic(err) } } } func main() { } `))