aboutsummaryrefslogtreecommitdiff
path: root/cmd/file2fuzz/main.go
blob: ed212cb9d7252e20653fe8784a6b6b6439f74a76 (plain)
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
// 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)
	}
}