| // Copyright 2021 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. |
| |
| // file2fuzz converts binary files, such as those used by go-fuzz, to the Go |
| // fuzzing corpus format. |
| // |
| // Usage: |
| // |
| // file2fuzz [-o output] [input...] |
| // |
| // The default behavior is to read input from stdin and write the converted |
| // output to stdout. If any position arguments are provided stdin is ignored |
| // and the arguments are assumed to be input files to convert. |
| // |
| // The -o flag provides an path to write output files to. If only one positional |
| // argument is specified it may be a file path or an existing directory, if there are |
| // multiple inputs specified it must be a directory. If a directory is provided |
| // the name of the file will be the SHA-256 hash of its contents. |
| package main |
| |
| import ( |
| "crypto/sha256" |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "path/filepath" |
| ) |
| |
| // encVersion1 is version 1 Go fuzzer corpus encoding. |
| var encVersion1 = "go test fuzz v1" |
| |
| func encodeByteSlice(b []byte) []byte { |
| return []byte(fmt.Sprintf("%s\n[]byte(%q)", encVersion1, b)) |
| } |
| |
| func usage() { |
| fmt.Fprintf(os.Stderr, "usage: file2fuzz [-o output] [input...]\nconverts files to Go fuzzer corpus format\n") |
| fmt.Fprintf(os.Stderr, "\tinput: files to convert\n") |
| fmt.Fprintf(os.Stderr, "\t-o: where to write converted file(s)\n") |
| os.Exit(2) |
| } |
| func dirWriter(dir string) func([]byte) error { |
| return func(b []byte) error { |
| sum := fmt.Sprintf("%x", sha256.Sum256(b)) |
| name := filepath.Join(dir, sum) |
| if err := os.MkdirAll(dir, 0777); err != nil { |
| return err |
| } |
| if err := ioutil.WriteFile(name, b, 0666); err != nil { |
| os.Remove(name) |
| return err |
| } |
| return nil |
| } |
| } |
| |
| func convert(inputArgs []string, outputArg string) error { |
| var input []io.Reader |
| if args := inputArgs; len(args) == 0 { |
| input = []io.Reader{os.Stdin} |
| } else { |
| for _, a := range args { |
| f, err := os.Open(a) |
| if err != nil { |
| return fmt.Errorf("unable to open %q: %s", a, err) |
| } |
| defer f.Close() |
| if fi, err := f.Stat(); err != nil { |
| return fmt.Errorf("unable to open %q: %s", a, err) |
| } else if fi.IsDir() { |
| return fmt.Errorf("%q is a directory, not a file", a) |
| } |
| input = append(input, f) |
| } |
| } |
| |
| var output func([]byte) error |
| if outputArg == "" { |
| if len(inputArgs) > 1 { |
| return errors.New("-o required with multiple input files") |
| } |
| output = func(b []byte) error { |
| _, err := os.Stdout.Write(b) |
| return err |
| } |
| } else { |
| if len(inputArgs) > 1 { |
| output = dirWriter(outputArg) |
| } else { |
| if fi, err := os.Stat(outputArg); err != nil && !os.IsNotExist(err) { |
| return fmt.Errorf("unable to open %q for writing: %s", outputArg, err) |
| } else if err == nil && fi.IsDir() { |
| output = dirWriter(outputArg) |
| } else { |
| output = func(b []byte) error { |
| return ioutil.WriteFile(outputArg, b, 0666) |
| } |
| } |
| } |
| } |
| |
| for _, f := range input { |
| b, err := ioutil.ReadAll(f) |
| if err != nil { |
| return fmt.Errorf("unable to read input: %s", err) |
| } |
| if err := output(encodeByteSlice(b)); err != nil { |
| return fmt.Errorf("unable to write output: %s", err) |
| } |
| } |
| |
| return nil |
| } |
| |
| func main() { |
| log.SetFlags(0) |
| log.SetPrefix("file2fuzz: ") |
| |
| output := flag.String("o", "", "where to write converted file(s)") |
| flag.Usage = usage |
| flag.Parse() |
| |
| if err := convert(flag.Args(), *output); err != nil { |
| log.Fatal(err) |
| } |
| } |