...

Source file src/github.com/AdamKorcz/go-118-fuzz-build/main.go

Documentation: github.com/AdamKorcz/go-118-fuzz-build

     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",   // No reason to instrument these.
    82  		"runtime/pprof", // No reason to instrument these.
    83  		"runtime/race",  // No reason to instrument these.
    84  		"syscall",       // https://github.com/google/oss-fuzz/issues/3639
    85  	}
    86  	if *flagPreserve != "" {
    87  		ignore = append(ignore, strings.Split(*flagPreserve, ",")...)
    88  	}
    89  	buildFlags = append(buildFlags, "-gcflags", "all=-d=libfuzzer")
    90  
    91  	//fset := token.NewFileSet()
    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  	/*if len(pkgs) != 1 {
   110  		log.Fatal("package path matched multiple packages")
   111  	}*/
   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  	/*err = mainTmpl.Execute(os.Stdout, &Data{
   139  		PkgPath: importPath,
   140  		Func:    *flagFunc,
   141  	})*/
   142  	//return
   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  	//cmd := exec.Command("gotip", args...)
   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  // Packages that match one of the include patterns (default is include all packages)
   187  // and none of the exclude patterns (default is none) will be instrumented.
   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