├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── gen
└── main.go
├── go-fuzz-build
├── cover.go
└── main.go
├── go-fuzz-defs
└── defs.go
├── go-fuzz-dep
├── cover.go
├── doc.go
├── main.go
├── main_libFuzzer.go
├── sonar.go
├── sys_posix.go
└── sys_windows.go
├── go-fuzz
├── assets
│ ├── bootstrap-theme.min.css
│ ├── bootstrap.min.css
│ ├── bootstrap.min.js
│ ├── jquery.min.js
│ └── stats.html
├── bindata_assetfs.go
├── compare.go
├── compare_amd64.go
├── compare_amd64.s
├── coordinator.go
├── cover.go
├── cover_test.go
├── cpu_amd64.go
├── cpu_amd64.s
├── exectype_string.go
├── hub.go
├── internal
│ └── pcg
│ │ └── pcg.go
├── main.go
├── mutator.go
├── persistent.go
├── sonar.go
├── sys_posix.go
├── sys_windows.go
├── testee.go
├── vendor
│ └── github.com
│ │ ├── elazarl
│ │ └── go-bindata-assetfs
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── assetfs.go
│ │ │ ├── doc.go
│ │ │ └── go-bindata-assetfs
│ │ │ └── main.go
│ │ └── stephens2424
│ │ └── writerset
│ │ ├── readme.md
│ │ └── writerset.go
├── versifier
│ ├── versifier.go
│ └── versifier_test.go
├── worker.go
└── worker_test.go
├── internal
└── go-fuzz-types
│ └── types.go
├── slides
├── README.md
├── algo.png
├── bugs.png
├── crash.go
├── fuzzing.slide
├── go-fuzz.png
├── go-fuzz.slide
├── gob.go
└── regexp.go
├── test
├── corpus
│ ├── 0
│ ├── 1
│ └── 2
├── internal
│ └── test
│ │ └── test.go
├── test.go
├── testdep
│ └── testdep.go
└── vendor
│ └── non.existent.com
│ └── foo
│ └── foo.go
└── testscripts
├── fuzz_help.txt
├── mod_go_fuzz_dep.txt
├── mod_inside_gopath.txt
├── mod_outside_gopath.txt
├── mod_v2.txt
└── mod_vendor.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *-fuzz$
3 |
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # There are currently two main sets of tests
2 | # 1. Running go-fuzz-build and go-fuzz on dvyukov/go-fuzz-corpus/png starting with an empty corpus,
3 | # and it validates >1 entry appears in the corpus. This uses what was formerly the required
4 | # arguments like -workdir and -bin.
5 | # 2. Running go-fuzz-build and go-fuzz on dvyukov/go-fuzz/test, which contains some regression tests, etc.
6 | # This uses the newer ability to not supply any required arguments to go-fuzz-build and go-fuzz.
7 |
8 | language: go
9 |
10 | # Test Go 1.16 and 1.15 across linux, osx, windows.
11 | # Test Go tip on linux, osx (and explicitly set GO111MODULE=auto for tip, given default might change).
12 | # SET_GO111MODULE is used because (at least early on) setting GO111MODULE directly interfered with the setup performed by travis.
13 | matrix:
14 | include:
15 | - os: linux
16 | go: "1.16.x"
17 | - os: linux
18 | go: "1.15.x"
19 | - os: linux
20 | go: tip
21 | - os: linux
22 | go: tip
23 | env: SET_GO111MODULE=auto
24 | - os: osx
25 | go: "1.16.x"
26 | - os: osx
27 | go: "1.15.x"
28 | - os: osx
29 | go: tip
30 | - os: osx
31 | go: tip
32 | env: SET_GO111MODULE=auto
33 | - os: windows
34 | go: "1.16.x"
35 | - os: windows
36 | go: "1.15.x"
37 |
38 | # Install coreutils for the 'timeout(1)' utility on windows and osx.
39 | before_install:
40 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then choco install gnuwin32-coreutils.install; fi
41 | - go get -u github.com/rogpeppe/go-internal/cmd/testscript
42 |
43 | # Set the import path (including to help with forks).
44 | go_import_path: github.com/dvyukov/go-fuzz
45 |
46 | script:
47 | # Sanity check 'timeout(1)'.
48 | - which timeout
49 | - "echo 'verify the timeout utility works, including that it exits with status 124 on timeout.'"
50 | - "(timeout 2 sleep 10; ret=$?; echo timeout ret=$ret; if [[ $ret -eq 124 ]]; then exit 0; fi; exit $ret)"
51 |
52 | # Sanity check that the 'testscript' cmd seems to function at all.
53 | # If all the testscripts fail, this is a good one to troubleshoot first.
54 | - cd $GOPATH/src/github.com/dvyukov/go-fuzz
55 | - testscript -v testscripts/fuzz_help.txt
56 |
57 | # Run our tests for fuzzing modules.
58 | # If multiple modules testcripts fail, probably makes sense to start by troubleshooting
59 | # the earliest failing testscript.
60 | # TODO: probably makes more sense to move this further down, but place here for now for faster iterations.
61 | - testscript -v testscripts/mod_go_fuzz_dep.txt
62 | - testscript -v testscripts/mod_outside_gopath.txt
63 | - testscript -v testscripts/mod_inside_gopath.txt
64 | - testscript -v testscripts/mod_v2.txt
65 | - testscript -v testscripts/mod_vendor.txt
66 |
67 | # Prepare to test the png example from dvyukov/go-fuzz-corpus.
68 | - git clone --depth=1 https://github.com/dvyukov/go-fuzz-corpus $GOPATH/src/github.com/dvyukov/go-fuzz-corpus
69 | - cd $GOPATH/src/github.com/dvyukov/go-fuzz-corpus/
70 | - cd png
71 | # Create a small module to test the png example.
72 | - go mod init github.com/dvyukov/go-fuzz-corpus/png
73 | - go mod tidy
74 | - go get -d github.com/dvyukov/go-fuzz/go-fuzz-dep
75 | - ls -l
76 |
77 | # Reduce chances of future surprises due to any caching.
78 | - rm -rf fuzz.zip ./freshworkdir
79 |
80 | # Explicitly set GO111MODULE if requested.
81 | # Travis and/or tip might change the default value of GO111MODULE at some point.
82 | # As of 2019-08-31, travis sets GO111MODULE=auto, and tip defaults to 'auto' if
83 | # GO111MODULE is unset.
84 | - if [[ ! -z "$SET_GO111MODULE" ]]; then export GO111MODULE="$SET_GO111MODULE"; fi
85 | - echo "GO111MODULE=$GO111MODULE"
86 |
87 | # Instrument using go-fuzz-build on the png example Fuzz function.
88 | - which go-fuzz-build
89 | - go-fuzz-build -o=./fuzz.zip github.com/dvyukov/go-fuzz-corpus/png
90 |
91 | # Run go-fuzz on the result of instrumenting the png example Fuzz function.
92 | # Stop after 20 sec of fuzzing.
93 | - which go-fuzz
94 | - "(timeout 20 go-fuzz -bin=./fuzz.zip -workdir=./freshworkdir; ret=$?; echo timeout ret=$ret; if [[ $ret -eq 124 ]]; then exit 0; fi; exit $ret)"
95 |
96 | # Lightly validate that we have more than 1 result in the corpus.
97 | # Some chance this could fail if unlucky, but so far seems unlikely to have a false failure on this validation.
98 | - ls -lrt ./freshworkdir/corpus | tail -30
99 | - find ./freshworkdir/corpus/ -type f | wc -l
100 | - "(if [[ $(find ./freshworkdir/corpus/ -type f | wc -l) -gt 1 ]]; then exit 0; fi; exit 1)"
101 |
102 | # Instrument the test package from dvyukov/go-fuzz/test.
103 | - cd $GOPATH/src/github.com/dvyukov/go-fuzz
104 | - cd test
105 | - ls -l
106 | - rm -rf test-fuzz.zip
107 | - go-fuzz-build
108 |
109 | # End early for Windows. 'timeout' does not seem to kill this fuzzing session on Windows.
110 | # Presumably we could solve that with an alternative timeout/kill mechanism, but for
111 | # now workaround by skipping that last test on Windows.
112 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then travis_terminate "${TRAVIS_TEST_RESULT}"; fi
113 |
114 | # Run go-fuzz on the result. Stop after 20 sec of fuzzing.
115 | - which go-fuzz
116 | - "(timeout 20 go-fuzz; ret=$?; echo timeout ret=$ret; if [[ $ret -eq 124 ]]; then exit 0; fi; exit $ret)"
117 |
118 | # Windows seems a bit flakey about capturing output on failure. This seems to help.
119 | after_failure:
120 | - "echo 'sleep 10 sec to help capture output. On Windows, failing output might be in wrong section, including this one.'"
121 | - sleep 10
122 |
123 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
--------------------------------------------------------------------------------
/gen/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package gen
5 |
6 | import (
7 | "flag"
8 | "fmt"
9 | "math/rand"
10 | "os"
11 | "path/filepath"
12 | "time"
13 | )
14 |
15 | var (
16 | flagOut = flag.String("out", "", "output dir")
17 | flagN = flag.Int("n", 1000, "number of inputs to generate")
18 | seq = 0
19 | )
20 |
21 | func init() {
22 | flag.Parse()
23 | if *flagOut == "" {
24 | fmt.Fprintf(os.Stderr, "output directory is not set\n")
25 | os.Exit(1)
26 | }
27 | if err := os.MkdirAll(*flagOut, 0760); err != nil {
28 | fmt.Fprintf(os.Stderr, "mkdir failed: %v\n", err)
29 | os.Exit(1)
30 | }
31 | rand.Seed(time.Now().UnixNano())
32 | }
33 |
34 | func Rand(n int) int {
35 | return rand.Intn(n)
36 | }
37 |
38 | func Emit(data, hint []byte, valid bool) {
39 | f, err := os.Create(filepath.Join(*flagOut, fmt.Sprintf("%d", seq)))
40 | if err != nil {
41 | fmt.Fprintf(os.Stderr, "failed to create file: %v\n", err)
42 | os.Exit(1)
43 | }
44 | defer f.Close()
45 |
46 | n, err := f.Write(data)
47 | if err != nil {
48 | fmt.Fprintf(os.Stderr, "failed to write to file: %v\n", err)
49 | os.Exit(1)
50 | } else if n != len(data) {
51 | fmt.Fprint(os.Stderr, "failed to write data to file\n")
52 | os.Exit(1)
53 | }
54 |
55 | if seq++; seq == *flagN {
56 | os.Exit(0)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/go-fuzz-defs/defs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // Package defs provides constants required by go-fuzz-build, go-fuzz, and instrumented code.
5 | package base
6 |
7 | // This package has a special interaction with go-fuzz-dep:
8 | // It is copied into a package with it by go-fuzz-build.
9 | // Only things that can be safely duplicated without confusion,
10 | // like constants, should be added to this package.
11 | // And any additions should be tested carefully. :)
12 |
13 | const (
14 | CoverSize = 64 << 10
15 | MaxInputSize = 1 << 20
16 | SonarRegionSize = 1 << 20
17 | )
18 |
19 | const (
20 | SonarEQL = iota
21 | SonarNEQ
22 | SonarLSS
23 | SonarGTR
24 | SonarLEQ
25 | SonarGEQ
26 |
27 | SonarOpMask = 7
28 | SonarLength = 1 << 3
29 | SonarSigned = 1 << 4
30 | SonarString = 1 << 5
31 | SonarConst1 = 1 << 6
32 | SonarConst2 = 1 << 7
33 |
34 | SonarHdrLen = 6
35 | SonarMaxLen = 20
36 | )
37 |
--------------------------------------------------------------------------------
/go-fuzz-dep/cover.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build gofuzz
5 |
6 | package gofuzzdep
7 |
8 | import (
9 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
10 | )
11 |
12 | // Bool is just a bool.
13 | // It is used by code autogenerated by go-fuzz-build
14 | // to avoid compilation errors when a user's code shadows the built-in bool.
15 | type Bool = bool
16 |
17 | // CoverTab holds code coverage.
18 | // It is initialized to a new array so that instrumentation
19 | // executed during process initialization has somewhere to write to.
20 | // It is replaced by a newly initialized array when it is
21 | // time for actual instrumentation to commence.
22 | var CoverTab = new([CoverSize]byte)
23 |
--------------------------------------------------------------------------------
/go-fuzz-dep/doc.go:
--------------------------------------------------------------------------------
1 | // Package gofuzzdep contains the business logic used to monitor the fuzzing.
2 | //
3 | // It is handled specially by go-fuzz-build; see the comments in package go-fuzz-defs.
4 | //
5 | // Be particularly careful about adding imports to go-fuzz-dep:
6 | // Any package imported by go-fuzz-dep cannot be instrumented (on pain of import cycles),
7 | // which reduces the effectiveness of go-fuzz on any other package that imports it.
8 | // That is why (e.g.) there are hand-rolled serialization functions instead of using encoding/binary,
9 | // and hand-rolled syscall-based communication instead of using package net or os.
10 | package gofuzzdep
11 |
--------------------------------------------------------------------------------
/go-fuzz-dep/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build gofuzz
5 | // +build !gofuzz_libfuzzer
6 |
7 | package gofuzzdep
8 |
9 | import (
10 | "runtime"
11 | "sync/atomic"
12 | "syscall"
13 | "time"
14 | "unsafe"
15 |
16 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
17 | )
18 |
19 | func Main(fns []func([]byte) int) {
20 | mem, inFD, outFD := setupCommFile()
21 | CoverTab = (*[CoverSize]byte)(unsafe.Pointer(&mem[0]))
22 | input := mem[CoverSize : CoverSize+MaxInputSize]
23 | sonarRegion = mem[CoverSize+MaxInputSize:]
24 | runtime.GOMAXPROCS(1) // makes coverage more deterministic, we parallelize on higher level
25 | for {
26 | fnidx, n := read(inFD)
27 | if n > uint64(len(input)) {
28 | println("invalid input length")
29 | syscall.Exit(1)
30 | }
31 | for i := range CoverTab {
32 | CoverTab[i] = 0
33 | }
34 | atomic.StoreUint32(&sonarPos, 0)
35 | t0 := time.Now()
36 | res := fns[fnidx](input[:n:n])
37 | ns := time.Since(t0)
38 | write(outFD, uint64(res), uint64(ns), uint64(atomic.LoadUint32(&sonarPos)))
39 | }
40 | }
41 |
42 | // read reads little-endian-encoded uint8+uint64 from fd.
43 | func read(fd FD) (uint8, uint64) {
44 | rd := 0
45 | var buf [9]byte
46 | for rd != len(buf) {
47 | n, err := fd.read(buf[rd:])
48 | if err == syscall.EINTR {
49 | continue
50 | }
51 | if n == 0 {
52 | syscall.Exit(1)
53 | }
54 | if err != nil {
55 | println("failed to read fd =", fd, "errno =", err.(syscall.Errno))
56 | syscall.Exit(1)
57 | }
58 | rd += n
59 | }
60 | return buf[0], deserialize64(buf[1:])
61 | }
62 |
63 | // write writes little-endian-encoded vals... to fd.
64 | func write(fd FD, vals ...uint64) {
65 | var tmp [3 * 8]byte
66 | buf := tmp[:len(vals)*8]
67 | for i, v := range vals {
68 | serialize64(buf[i*8:], v)
69 | }
70 | wr := 0
71 | for wr != len(buf) {
72 | n, err := fd.write(buf[wr:])
73 | if err == syscall.EINTR {
74 | continue
75 | }
76 | if err != nil {
77 | println("failed to read fd =", fd, "errno =", err.(syscall.Errno))
78 | syscall.Exit(1)
79 | }
80 | wr += n
81 | }
82 | }
83 |
84 | // writeStr writes strings s to fd.
85 | func writeStr(fd FD, s string) {
86 | buf := []byte(s)
87 | wr := 0
88 | for wr != len(buf) {
89 | n, err := fd.write(buf[wr:])
90 | if err == syscall.EINTR {
91 | continue
92 | }
93 | if err != nil {
94 | println("failed to read fd =", fd, "errno =", err.(syscall.Errno))
95 | syscall.Exit(1)
96 | }
97 | wr += n
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/go-fuzz-dep/main_libFuzzer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build gofuzz
5 | // +build gofuzz_libfuzzer
6 |
7 | package gofuzzdep
8 |
9 | import (
10 | "unsafe"
11 |
12 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
13 | )
14 |
15 | func Initialize(coverTabPtr unsafe.Pointer, coverTabSize uint64) {
16 | if coverTabSize != CoverSize {
17 | panic("Incorrect cover tab size")
18 | }
19 | CoverTab = (*[CoverSize]byte)(coverTabPtr)
20 | }
21 |
--------------------------------------------------------------------------------
/go-fuzz-dep/sonar.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build gofuzz
5 |
6 | package gofuzzdep
7 |
8 | import (
9 | "sync/atomic"
10 | "unsafe"
11 |
12 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
13 | )
14 |
15 | var (
16 | sonarRegion []byte
17 | sonarPos uint32
18 | )
19 |
20 | const failure = ^uint8(0)
21 |
22 | type iface struct {
23 | typ unsafe.Pointer
24 | val unsafe.Pointer
25 | }
26 |
27 | // Sonar is called by instrumentation code to notify go-fuzz about comparisons.
28 | // Low 8 bits of id are flags, the rest is unique id of a comparison.
29 | func Sonar(v1, v2 interface{}, id uint32) {
30 | var buf [SonarHdrLen + 2*SonarMaxLen]byte
31 | n1, f1 := serialize(v1, v2, buf[SonarHdrLen:])
32 | if n1 == failure {
33 | return
34 | }
35 | n2, f2 := serialize(v2, v1, buf[SonarHdrLen+n1:])
36 | if n2 == failure {
37 | return
38 | }
39 | // Ideal const operands are converted to signed int,
40 | // but it does not mean that the comparison is signed
41 | // unless the other operand is signed.
42 | if id&SonarConst1 != 0 {
43 | f1 &^= SonarSigned
44 | }
45 | if id&SonarConst2 != 0 {
46 | f2 &^= SonarSigned
47 | }
48 | id |= uint32(f1 | f2)
49 | serialize32(buf[:], id)
50 | buf[4] = n1
51 | buf[5] = n2
52 | n := uint32(SonarHdrLen + n1 + n2)
53 | pos := atomic.LoadUint32(&sonarPos)
54 | for {
55 | if pos+n > uint32(len(sonarRegion)) {
56 | return
57 | }
58 | if atomic.CompareAndSwapUint32(&sonarPos, pos, pos+n) {
59 | break
60 | }
61 | pos = atomic.LoadUint32(&sonarPos)
62 | }
63 | copy(sonarRegion[pos:pos+n], buf[:])
64 | }
65 |
66 | func serialize(v, v2 interface{}, buf []byte) (n, flags uint8) {
67 | switch vv := v.(type) {
68 | case int8:
69 | buf[0] = byte(vv)
70 | return 1, SonarSigned
71 | case uint8:
72 | buf[0] = byte(vv)
73 | return 1, 0
74 | case int16:
75 | return serialize16(buf, uint16(vv)), SonarSigned
76 | case uint16:
77 | return serialize16(buf, vv), 0
78 | case int32:
79 | return serialize32(buf, uint32(vv)), SonarSigned
80 | case uint32:
81 | return serialize32(buf, vv), 0
82 | case int64:
83 | return serialize64(buf, uint64(vv)), SonarSigned
84 | case uint64:
85 | return serialize64(buf, vv), 0
86 | case int:
87 | if unsafe.Sizeof(vv) == 4 {
88 | return serialize32(buf, uint32(vv)), SonarSigned
89 | } else {
90 | return serialize64(buf, uint64(vv)), SonarSigned
91 | }
92 | case uint:
93 | if unsafe.Sizeof(vv) == 4 {
94 | return serialize32(buf, uint32(vv)), 0
95 | } else {
96 | return serialize64(buf, uint64(vv)), 0
97 | }
98 | case string:
99 | if len(vv) > SonarMaxLen {
100 | return failure, 0
101 | }
102 | return uint8(copy(buf, vv)), SonarString
103 | case [1]byte:
104 | return uint8(copy(buf, vv[:])), SonarString
105 | case [2]byte:
106 | return uint8(copy(buf, vv[:])), SonarString
107 | case [3]byte:
108 | return uint8(copy(buf, vv[:])), SonarString
109 | case [4]byte:
110 | return uint8(copy(buf, vv[:])), SonarString
111 | case [5]byte:
112 | return uint8(copy(buf, vv[:])), SonarString
113 | case [6]byte:
114 | return uint8(copy(buf, vv[:])), SonarString
115 | case [7]byte:
116 | return uint8(copy(buf, vv[:])), SonarString
117 | case [8]byte:
118 | return uint8(copy(buf, vv[:])), SonarString
119 | case [9]byte:
120 | return uint8(copy(buf, vv[:])), SonarString
121 | case [10]byte:
122 | return uint8(copy(buf, vv[:])), SonarString
123 | case [11]byte:
124 | return uint8(copy(buf, vv[:])), SonarString
125 | case [12]byte:
126 | return uint8(copy(buf, vv[:])), SonarString
127 | case [13]byte:
128 | return uint8(copy(buf, vv[:])), SonarString
129 | case [14]byte:
130 | return uint8(copy(buf, vv[:])), SonarString
131 | case [15]byte:
132 | return uint8(copy(buf, vv[:])), SonarString
133 | case [16]byte:
134 | return uint8(copy(buf, vv[:])), SonarString
135 | case [17]byte:
136 | return uint8(copy(buf, vv[:])), SonarString
137 | case [18]byte:
138 | return uint8(copy(buf, vv[:])), SonarString
139 | case [19]byte:
140 | return uint8(copy(buf, vv[:])), SonarString
141 | case [20]byte:
142 | return uint8(copy(buf, vv[:])), SonarString
143 | default:
144 | // Special case: string literal is compared with a variable of
145 | // user type with string underlying type:
146 | // type Name string
147 | // var name Name
148 | // if name == "foo" { ... }
149 | if _, ok := v2.(string); ok {
150 | s := *(*string)((*iface)(unsafe.Pointer(&v)).val)
151 | if len(s) <= SonarMaxLen {
152 | return uint8(copy(buf[:], s)), SonarString
153 | }
154 | }
155 | return failure, 0
156 | }
157 | }
158 |
159 | // The serialization routines here match those of encoding/binary.LittleEndian.
160 | // They are copied here because importing encoding/binary creates import cycles.
161 |
162 | func serialize16(buf []byte, v uint16) uint8 {
163 | _ = buf[1]
164 | buf[0] = byte(v >> 0)
165 | buf[1] = byte(v >> 8)
166 | return 2
167 | }
168 |
169 | func serialize32(buf []byte, v uint32) uint8 {
170 | _ = buf[3]
171 | buf[0] = byte(v >> 0)
172 | buf[1] = byte(v >> 8)
173 | buf[2] = byte(v >> 16)
174 | buf[3] = byte(v >> 24)
175 | return 4
176 | }
177 |
178 | func serialize64(buf []byte, v uint64) uint8 {
179 | _ = buf[7]
180 | buf[0] = byte(v >> 0)
181 | buf[1] = byte(v >> 8)
182 | buf[2] = byte(v >> 16)
183 | buf[3] = byte(v >> 24)
184 | buf[4] = byte(v >> 32)
185 | buf[5] = byte(v >> 40)
186 | buf[6] = byte(v >> 48)
187 | buf[7] = byte(v >> 56)
188 | return 8
189 | }
190 |
191 | func deserialize64(buf []byte) uint64 {
192 | _ = buf[7]
193 | return uint64(buf[0])<<0 |
194 | uint64(buf[1])<<8 |
195 | uint64(buf[2])<<16 |
196 | uint64(buf[3])<<24 |
197 | uint64(buf[4])<<32 |
198 | uint64(buf[5])<<40 |
199 | uint64(buf[6])<<48 |
200 | uint64(buf[7])<<56
201 | }
202 |
--------------------------------------------------------------------------------
/go-fuzz-dep/sys_posix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build darwin linux freebsd dragonfly openbsd netbsd
5 | // +build gofuzz
6 |
7 | package gofuzzdep
8 |
9 | import (
10 | "syscall"
11 |
12 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
13 | )
14 |
15 | type FD int
16 |
17 | func setupCommFile() ([]byte, FD, FD) {
18 | mem, err := syscall.Mmap(3, 0, CoverSize+MaxInputSize+SonarRegionSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
19 | if err != nil {
20 | println("failed to mmap fd = 3 errno =", err.(syscall.Errno))
21 | syscall.Exit(1)
22 | }
23 | return mem, 4, 5
24 | }
25 |
26 | func (fd FD) read(buf []byte) (int, error) {
27 | return syscall.Read(int(fd), buf)
28 | }
29 |
30 | func (fd FD) write(buf []byte) (int, error) {
31 | return syscall.Write(int(fd), buf)
32 | }
33 |
--------------------------------------------------------------------------------
/go-fuzz-dep/sys_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build gofuzz
5 |
6 | package gofuzzdep
7 |
8 | import (
9 | "syscall"
10 | "unsafe"
11 |
12 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
13 | )
14 |
15 | // Can't import reflect because of import cycles.
16 | type sliceHeader struct {
17 | addr uintptr
18 | l, c int
19 | }
20 |
21 | type FD syscall.Handle
22 |
23 | func setupCommFile() ([]byte, FD, FD) {
24 | const (
25 | size = CoverSize + MaxInputSize + SonarRegionSize
26 | FILE_MAP_ALL_ACCESS = 0xF001F
27 | )
28 | mapping := readEnvParam("GO_FUZZ_COMM_FD")
29 | addr, err := syscall.MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, size)
30 | if err != nil {
31 | println("failed to mmap comm file:", err.Error())
32 | syscall.Exit(1)
33 | }
34 | hdr := sliceHeader{addr, size, size}
35 | mem := *(*[]byte)(unsafe.Pointer(&hdr))
36 | in := FD(readEnvParam("GO_FUZZ_IN_FD"))
37 | out := FD(readEnvParam("GO_FUZZ_OUT_FD"))
38 | return mem, in, out
39 | }
40 |
41 | func readEnvParam(name string) syscall.Handle {
42 | v, _ := syscall.Getenv(name)
43 | var x uintptr
44 | for i := 0; i < len(v); i++ {
45 | x = x*10 + uintptr(v[i]-'0')
46 | }
47 | return syscall.Handle(x)
48 | }
49 |
50 | func (fd FD) read(buf []byte) (int, error) {
51 | return syscall.Read(syscall.Handle(fd), buf)
52 | }
53 |
54 | func (fd FD) write(buf []byte) (int, error) {
55 | return syscall.Write(syscall.Handle(fd), buf)
56 | }
57 |
--------------------------------------------------------------------------------
/go-fuzz/assets/stats.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
Go Fuzz
11 |
12 |
13 |
14 | Workers
15 |
16 |
17 |
18 | Corpus
19 |
20 |
21 |
22 | Crashers
23 |
24 |
25 |
26 | Restarts
27 |
28 |
29 |
30 | Execs
31 |
32 |
33 |
34 | Cover
35 |
36 |
37 |
38 | Uptime
39 |
40 |
41 |
42 |
History
43 |
44 |
45 |
46 |
47 |
Workers
48 |
Corpus
49 |
Crashers
50 |
Restarts
51 |
Execs
52 |
Cover
53 |
Uptime
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
72 |
79 |
92 |
93 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/go-fuzz/compare.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build !amd64
5 |
6 | package main
7 |
8 | func compareCoverBody(base, cur []byte) bool {
9 | return compareCoverDump(base, cur)
10 | }
11 |
--------------------------------------------------------------------------------
/go-fuzz/compare_amd64.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | func compareCoverBody(base, cur []byte) bool {
7 | if hasAVX2 {
8 | return compareCoverBodyAVX2(&base[0], &cur[0])
9 | }
10 | return compareCoverBodySSE2(&base[0], &cur[0])
11 | }
12 |
13 | func compareCoverBodySSE2(base, cur *byte) bool // in compare_amd64.s
14 | func compareCoverBodyAVX2(base, cur *byte) bool // in compare_amd64.s
15 |
--------------------------------------------------------------------------------
/go-fuzz/compare_amd64.s:
--------------------------------------------------------------------------------
1 | // Copyright 2019 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | #include "textflag.h"
5 |
6 | // ·compareCoverBodySSE2 compares every corresponding byte of base and cur, and
7 | // reports whether cur has any entries bigger than base.
8 | // func ·compareCoverBodySSE2(base, cur *byte) bool
9 | TEXT ·compareCoverBodySSE2(SB), NOSPLIT, $0-17
10 | MOVQ base+0(FP), SI
11 | MOVQ cur+8(FP), DI
12 | XORQ CX, CX // loop counter
13 | XORQ R10, R10 // ret
14 |
15 | // Fill X0 with 128.
16 | MOVL $128, AX
17 | MOVD AX, X0
18 | PUNPCKLBW X0, X0
19 | PUNPCKLBW X0, X0
20 | PSHUFL $0, X0, X0
21 |
22 | // Align loop.
23 | // There is not enough control to align to 16 bytes;
24 | // the function itself might be at any 2 byte offset.
25 | // But we can at least get the loop offset to be even.
26 | BYTE $0x90
27 | loop:
28 | MOVOU (SI)(CX*1), X1
29 | MOVOU (DI)(CX*1), X2
30 | // Add -128 to each byte.
31 | // This lets us use signed comparison below to implement unsigned comparison.
32 | PSUBB X0, X1
33 | PSUBB X0, X2
34 | // Compare each byte
35 | PCMPGTB X1, X2 // X2 > X1
36 | // Extract top bit of each elem.
37 | PMOVMSKB X2, AX
38 | // If any bits were set, then some elem of X2 (cur) was bigger than some elem of X1 (base).
39 | TESTL AX, AX
40 | JNZ yes
41 | LEAQ 16(CX), CX // CX += 16
42 | BTL $16, CX // have we reached 65536 (CoverSize)?
43 | JCS ret
44 | JMP loop
45 | yes:
46 | MOVQ $1, R10
47 | ret:
48 | MOVB R10, ret+16(FP)
49 | RET
50 |
51 | // compareCoverBodyAVX2 compares every corresponding byte of base and cur, and
52 | // reports whether cur has any entries bigger than base.
53 | // func ·compareCoverBodyAVX2(base, cur *byte) bool
54 | TEXT ·compareCoverBodyAVX2(SB), NOSPLIT, $0-17
55 | MOVQ base+0(FP), SI
56 | MOVQ cur+8(FP), DI
57 | XORQ CX, CX // loop counter
58 | XORQ R10, R10 // ret
59 | MOVL $128, AX
60 | MOVD AX, X0
61 | VPBROADCASTB X0, Y0
62 |
63 | // align loop.
64 | // See comment in compareCoverBodySSE2.
65 | BYTE $0x90
66 | loop:
67 | VMOVDQU (SI)(CX*1), Y1
68 | VMOVDQU (DI)(CX*1), Y2
69 | VPSUBB Y0, Y1, Y3
70 | VPSUBB Y0, Y2, Y4
71 | VPCMPGTB Y3, Y4, Y5
72 | // Extract top bit of each elem.
73 | VPMOVMSKB Y5, AX
74 | // If any bits were set, then some elem of X2 (cur) was bigger than some elem of X1 (base).
75 | TESTL AX, AX
76 | JNZ yes
77 | LEAQ 32(CX), CX
78 | BTL $16, CX // have we reached 65536 (CoverSize)?
79 | JCS ret
80 | JMP loop
81 | yes:
82 | MOVQ $1, R10
83 | ret:
84 | VZEROUPPER
85 | MOVB R10, ret+16(FP)
86 | RET
87 |
--------------------------------------------------------------------------------
/go-fuzz/coordinator.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "bytes"
8 | "encoding/json"
9 | "errors"
10 | "fmt"
11 | "log"
12 | "net"
13 | "net/http"
14 | _ "net/http/pprof"
15 | "net/rpc"
16 | "path/filepath"
17 | "runtime"
18 | "sync"
19 | "sync/atomic"
20 | "time"
21 |
22 | "github.com/stephens2424/writerset"
23 | )
24 |
25 | // Coordinator manages persistent fuzzer state like input corpus and crashers.
26 | type Coordinator struct {
27 | mu sync.Mutex
28 | idSeq int
29 | workers map[int]*CoordinatorWorker
30 | corpus *PersistentSet
31 | suppressions *PersistentSet
32 | crashers *PersistentSet
33 |
34 | startTime time.Time
35 | lastInput time.Time
36 | statExecs uint64
37 | statRestarts uint64
38 | coverFullness int
39 |
40 | statsWriters *writerset.WriterSet
41 | }
42 |
43 | // CoordinatorWorker represents coordinator's view of a worker.
44 | type CoordinatorWorker struct {
45 | id int
46 | procs int
47 | pending []CoordinatorInput
48 | lastSync time.Time
49 | }
50 |
51 | // coordinatorMain is entry function for coordinator.
52 | func coordinatorMain(ln net.Listener) {
53 | m := &Coordinator{}
54 | m.statsWriters = writerset.New()
55 | m.startTime = time.Now()
56 | m.lastInput = time.Now()
57 | m.suppressions = newPersistentSet(filepath.Join(*flagWorkdir, "suppressions"))
58 | m.crashers = newPersistentSet(filepath.Join(*flagWorkdir, "crashers"))
59 | m.corpus = newPersistentSet(filepath.Join(*flagWorkdir, "corpus"))
60 | if len(m.corpus.m) == 0 {
61 | m.corpus.add(Artifact{[]byte{}, 0, false})
62 | }
63 |
64 | m.workers = make(map[int]*CoordinatorWorker)
65 | coordinatorListen(m)
66 |
67 | go coordinatorLoop(m)
68 |
69 | s := rpc.NewServer()
70 | s.Register(m)
71 | s.Accept(ln)
72 | }
73 |
74 | func coordinatorListen(c *Coordinator) {
75 | if *flagHTTP != "" {
76 | http.HandleFunc("/eventsource", c.eventSource)
77 | http.HandleFunc("/", c.index)
78 |
79 | go func() {
80 | fmt.Printf("Serving statistics on http://%s/\n", *flagHTTP)
81 | panic(http.ListenAndServe(*flagHTTP, nil))
82 | }()
83 | } else {
84 | runtime.MemProfileRate = 0
85 | }
86 | }
87 |
88 | func coordinatorLoop(c *Coordinator) {
89 | for range time.NewTicker(3 * time.Second).C {
90 | if atomic.LoadUint32(&shutdown) != 0 {
91 | return
92 | }
93 | c.mu.Lock()
94 | // Nuke dead workers.
95 | for id, s := range c.workers {
96 | if time.Since(s.lastSync) < syncDeadline {
97 | continue
98 | }
99 | log.Printf("worker %v died", s.id)
100 | delete(c.workers, id)
101 | }
102 | c.mu.Unlock()
103 |
104 | c.broadcastStats()
105 | }
106 | }
107 |
108 | func (c *Coordinator) broadcastStats() {
109 | stats := c.coordinatorStats()
110 |
111 | // log to stdout
112 | log.Println(stats.String())
113 |
114 | // write to any http clients
115 | b, err := json.Marshal(stats)
116 | if err != nil {
117 | panic(err)
118 | }
119 |
120 | fmt.Fprintf(c.statsWriters, "event: ping\ndata: %s\n\n", string(b))
121 | c.statsWriters.Flush()
122 | }
123 |
124 | func (c *Coordinator) eventSource(w http.ResponseWriter, r *http.Request) {
125 | w.Header().Set("Content-Type", "text/event-stream")
126 | w.WriteHeader(http.StatusOK)
127 | <-c.statsWriters.Add(w)
128 | }
129 |
130 | func (c *Coordinator) index(w http.ResponseWriter, r *http.Request) {
131 | if r.URL.Path == "/" {
132 | r.URL.Path = "/stats.html"
133 | }
134 | http.FileServer(assetFS()).ServeHTTP(w, r)
135 | }
136 |
137 | func (c *Coordinator) coordinatorStats() coordinatorStats {
138 | c.mu.Lock()
139 | defer c.mu.Unlock()
140 |
141 | stats := coordinatorStats{
142 | Corpus: uint64(len(c.corpus.m)),
143 | Crashers: uint64(len(c.crashers.m)),
144 | Uptime: fmtDuration(time.Since(c.startTime)),
145 | StartTime: c.startTime,
146 | LastNewInputTime: c.lastInput,
147 | Execs: c.statExecs,
148 | Cover: uint64(c.coverFullness),
149 | }
150 |
151 | // Print stats line.
152 | if c.statExecs != 0 && c.statRestarts != 0 {
153 | stats.RestartsDenom = c.statExecs / c.statRestarts
154 | }
155 |
156 | for _, w := range c.workers {
157 | stats.Workers += uint64(w.procs)
158 | }
159 |
160 | return stats
161 | }
162 |
163 | type coordinatorStats struct {
164 | Workers, Corpus, Crashers, Execs, Cover, RestartsDenom uint64
165 | LastNewInputTime, StartTime time.Time
166 | Uptime string
167 | }
168 |
169 | func (s coordinatorStats) String() string {
170 | return fmt.Sprintf("workers: %v, corpus: %v (%v ago), crashers: %v,"+
171 | " restarts: 1/%v, execs: %v (%.0f/sec), cover: %v, uptime: %v",
172 | s.Workers, s.Corpus, fmtDuration(time.Since(s.LastNewInputTime)),
173 | s.Crashers, s.RestartsDenom, s.Execs, s.ExecsPerSec(), s.Cover,
174 | s.Uptime,
175 | )
176 | }
177 |
178 | func (s coordinatorStats) ExecsPerSec() float64 {
179 | return float64(s.Execs) * 1e9 / float64(time.Since(s.StartTime))
180 | }
181 |
182 | func fmtDuration(d time.Duration) string {
183 | if d.Hours() >= 1 {
184 | return fmt.Sprintf("%vh%vm", int(d.Hours()), int(d.Minutes())%60)
185 | } else if d.Minutes() >= 1 {
186 | return fmt.Sprintf("%vm%vs", int(d.Minutes()), int(d.Seconds())%60)
187 | } else {
188 | return fmt.Sprintf("%vs", int(d.Seconds()))
189 | }
190 | }
191 |
192 | type ConnectArgs struct {
193 | Procs int
194 | }
195 |
196 | type ConnectRes struct {
197 | ID int
198 | Corpus []CoordinatorInput
199 | }
200 |
201 | // CoordinatorInput is description of input that is passed between coordinator and worker.
202 | type CoordinatorInput struct {
203 | Data []byte
204 | Prio uint64
205 | Type execType
206 | Minimized bool
207 | Smashed bool
208 | }
209 |
210 | // Connect attaches new worker to coordinator.
211 | func (c *Coordinator) Connect(a *ConnectArgs, r *ConnectRes) error {
212 | c.mu.Lock()
213 | defer c.mu.Unlock()
214 |
215 | c.idSeq++
216 | w := &CoordinatorWorker{
217 | id: c.idSeq,
218 | procs: a.Procs,
219 | lastSync: time.Now(),
220 | }
221 | c.workers[w.id] = w
222 | r.ID = w.id
223 | // Give the worker initial corpus.
224 | for _, a := range c.corpus.m {
225 | r.Corpus = append(r.Corpus, CoordinatorInput{a.data, a.meta, execCorpus, !a.user, true})
226 | }
227 | return nil
228 | }
229 |
230 | type NewInputArgs struct {
231 | ID int
232 | Data []byte
233 | Prio uint64
234 | }
235 |
236 | // NewInput saves new interesting input on coordinator.
237 | func (c *Coordinator) NewInput(a *NewInputArgs, r *int) error {
238 | c.mu.Lock()
239 | defer c.mu.Unlock()
240 |
241 | w := c.workers[a.ID]
242 | if w == nil {
243 | return errors.New("unknown worker")
244 | }
245 |
246 | art := Artifact{a.Data, a.Prio, false}
247 | if !c.corpus.add(art) {
248 | return nil
249 | }
250 | c.lastInput = time.Now()
251 | // Queue the input for sending to every worker.
252 | for _, w1 := range c.workers {
253 | w1.pending = append(w1.pending, CoordinatorInput{a.Data, a.Prio, execCorpus, true, w1 != w})
254 | }
255 |
256 | return nil
257 | }
258 |
259 | type NewCrasherArgs struct {
260 | Data []byte
261 | Error []byte
262 | Suppression []byte
263 | Hanging bool
264 | }
265 |
266 | // NewCrasher saves new crasher input on coordinator.
267 | func (c *Coordinator) NewCrasher(a *NewCrasherArgs, r *int) error {
268 | c.mu.Lock()
269 | defer c.mu.Unlock()
270 |
271 | if !*flagDup && !c.suppressions.add(Artifact{a.Suppression, 0, false}) {
272 | return nil // Already have this.
273 | }
274 | if !c.crashers.add(Artifact{a.Data, 0, false}) {
275 | return nil // Already have this.
276 | }
277 |
278 | // Prepare quoted version of input to simplify creation of standalone reproducers.
279 | var buf bytes.Buffer
280 | for i := 0; i < len(a.Data); i += 20 {
281 | e := i + 20
282 | if e > len(a.Data) {
283 | e = len(a.Data)
284 | }
285 | fmt.Fprintf(&buf, "\t%q", a.Data[i:e])
286 | if e != len(a.Data) {
287 | fmt.Fprintf(&buf, " +")
288 | }
289 | fmt.Fprintf(&buf, "\n")
290 | }
291 | c.crashers.addDescription(a.Data, buf.Bytes(), "quoted")
292 | c.crashers.addDescription(a.Data, a.Error, "output")
293 |
294 | return nil
295 | }
296 |
297 | type SyncArgs struct {
298 | ID int
299 | Execs uint64
300 | Restarts uint64
301 | CoverFullness int
302 | }
303 |
304 | type SyncRes struct {
305 | Inputs []CoordinatorInput // new interesting inputs
306 | }
307 |
308 | var errUnkownWorker = errors.New("unknown worker")
309 |
310 | // Sync is a periodic sync with a worker.
311 | // Worker sends statistics. Coordinator returns new inputs.
312 | func (c *Coordinator) Sync(a *SyncArgs, r *SyncRes) error {
313 | c.mu.Lock()
314 | defer c.mu.Unlock()
315 |
316 | w := c.workers[a.ID]
317 | if w == nil {
318 | return errUnkownWorker
319 | }
320 | c.statExecs += a.Execs
321 | c.statRestarts += a.Restarts
322 | if c.coverFullness < a.CoverFullness {
323 | c.coverFullness = a.CoverFullness
324 | }
325 | w.lastSync = time.Now()
326 | r.Inputs = w.pending
327 | w.pending = nil
328 | return nil
329 | }
330 |
--------------------------------------------------------------------------------
/go-fuzz/cover.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 | "os"
10 |
11 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
12 | . "github.com/dvyukov/go-fuzz/internal/go-fuzz-types"
13 | )
14 |
15 | func makeCopy(data []byte) []byte {
16 | return append([]byte{}, data...)
17 | }
18 |
19 | func compareCover(base, cur []byte) bool {
20 | if len(base) != CoverSize || len(cur) != CoverSize {
21 | log.Fatalf("bad cover table size (%v, %v)", len(base), len(cur))
22 | }
23 | res := compareCoverBody(base, cur)
24 | if false {
25 | // This check can legitimately fail if the test process has
26 | // some background goroutines that continue to write to the
27 | // cover array (cur storage is in shared memory).
28 | if compareCoverDump(base, cur) != res {
29 | panic("bad")
30 | }
31 | }
32 | return res
33 | }
34 |
35 | func compareCoverDump(base, cur []byte) bool {
36 | for i, v := range base {
37 | if cur[i] > v {
38 | return true
39 | }
40 | }
41 | return false
42 | }
43 |
44 | func updateMaxCover(base, cur []byte) int {
45 | if len(base) != CoverSize || len(cur) != CoverSize {
46 | log.Fatalf("bad cover table size (%v, %v)", len(base), len(cur))
47 | }
48 | cnt := 0
49 | for i, x := range cur {
50 | x = roundUpCover(x)
51 | v := base[i]
52 | if v != 0 || x > 0 {
53 | cnt++
54 | }
55 | if v < x {
56 | base[i] = x
57 | }
58 | }
59 | return cnt
60 | }
61 |
62 | // Quantize the counters. Otherwise we get too inflated corpus.
63 | func roundUpCover(x byte) byte {
64 | if !*flagCoverCounters && x > 0 {
65 | return 255
66 | }
67 |
68 | if x <= 5 {
69 | return x
70 | } else if x <= 8 {
71 | return 8
72 | } else if x <= 16 {
73 | return 16
74 | } else if x <= 32 {
75 | return 32
76 | } else if x <= 64 {
77 | return 64
78 | }
79 | return 255
80 | }
81 |
82 | func findNewCover(base, cover []byte) (res []byte, notEmpty bool) {
83 | res = make([]byte, CoverSize)
84 | for i, b := range base {
85 | c := cover[i]
86 | if c > b {
87 | res[i] = c
88 | notEmpty = true
89 | }
90 | }
91 | return
92 | }
93 |
94 | func worseCover(base, cover []byte) bool {
95 | for i, b := range base {
96 | c := cover[i]
97 | if c < b {
98 | return true
99 | }
100 | }
101 | return false
102 | }
103 |
104 | func dumpCover(outf string, blocks map[int][]CoverBlock, cover []byte) {
105 | // Exclude files that have no coverage at all.
106 | files := make(map[string]bool)
107 | for i, v := range cover {
108 | if v == 0 {
109 | continue
110 | }
111 | for _, b := range blocks[i] {
112 | files[b.File] = true
113 | }
114 | }
115 |
116 | out, err := os.Create(outf)
117 | if err != nil {
118 | log.Fatalf("failed to create coverage file: %v", err)
119 | }
120 | defer out.Close()
121 | const showCounters = false
122 | if showCounters {
123 | fmt.Fprintf(out, "mode: count\n")
124 | } else {
125 | fmt.Fprintf(out, "mode: set\n")
126 | }
127 | for i, v := range cover {
128 | for _, b := range blocks[i] {
129 | if !files[b.File] {
130 | continue
131 | }
132 | if !showCounters && v != 0 {
133 | v = 1
134 | }
135 | fmt.Fprintf(out, "%s:%v.%v,%v.%v %v %v\n",
136 | b.File, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, v)
137 | }
138 | }
139 | }
140 |
141 | func dumpSonar(outf string, sites []SonarSite) {
142 | out, err := os.Create(outf)
143 | if err != nil {
144 | log.Fatalf("failed to create coverage file: %v", err)
145 | }
146 | defer out.Close()
147 | fmt.Fprintf(out, "mode: set\n")
148 | for i := range sites {
149 | s := &sites[i]
150 | cnt := 0 // red color
151 | stmt := 1 // account in percentage calculation
152 | if s.takenTotal[0] == 0 && s.takenTotal[1] == 0 {
153 | stmt = 0 // don't account in percentage calculation
154 | cnt = 1 // grey color
155 | } else if s.takenTotal[0] > 0 && s.takenTotal[1] > 0 {
156 | cnt = 100 // green color
157 | }
158 | fmt.Fprintf(out, "%v %v %v\n", s.loc, stmt, cnt)
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/go-fuzz/cover_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "testing"
8 |
9 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
10 | )
11 |
12 | func BenchmarkCompareCoverBody(b *testing.B) {
13 | base := make([]byte, CoverSize)
14 | cur := make([]byte, CoverSize)
15 |
16 | // Set 1 at both ends, so that it is easy regardless of which end compareCoverBody starts from.
17 | cur[0] = 1
18 | cur[CoverSize-1] = 1
19 | b.Run("easy", func(b *testing.B) {
20 | for i := 0; i < b.N; i++ {
21 | if !compareCoverBody(base, cur) {
22 | b.Fatalf("cur should have increased coverage")
23 | }
24 | }
25 | })
26 | cur[0] = 0
27 | cur[CoverSize-1] = 0
28 |
29 | // Set 1 in the middle, so that it is hard regardless of which end compareCoverBody starts from.
30 | cur[CoverSize/2] = 1
31 | b.Run("hard", func(b *testing.B) {
32 | for i := 0; i < b.N; i++ {
33 | if !compareCoverBody(base, cur) {
34 | b.Fatalf("cur should have increased coverage")
35 | }
36 | }
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/go-fuzz/cpu_amd64.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | // Adapted from GOROOT/src/internal/cpu/cpu_x86.go.
7 |
8 | var hasAVX2 bool
9 |
10 | func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32)
11 | func xgetbv() (eax, edx uint32)
12 |
13 | const (
14 | // ecx bits
15 | cpuid_OSXSAVE = 1 << 27
16 |
17 | // ebx bits
18 | cpuid_AVX2 = 1 << 5
19 | )
20 |
21 | func init() {
22 | _, _, ecx1, _ := cpuid(1, 0)
23 | hasOSXSAVE := cpuBitIsSet(ecx1, cpuid_OSXSAVE)
24 |
25 | osSupportsAVX := false
26 | // For XGETBV, OSXSAVE bit is required and sufficient.
27 | if hasOSXSAVE {
28 | eax, _ := xgetbv()
29 | // Check if XMM and YMM registers have OS support.
30 | osSupportsAVX = cpuBitIsSet(eax, 1<<1) && cpuBitIsSet(eax, 1<<2)
31 | }
32 |
33 | _, ebx7, _, _ := cpuid(7, 0)
34 | hasAVX2 = cpuBitIsSet(ebx7, cpuid_AVX2) && osSupportsAVX
35 | }
36 |
37 | func cpuBitIsSet(hwc uint32, value uint32) bool {
38 | return hwc&value != 0
39 | }
40 |
--------------------------------------------------------------------------------
/go-fuzz/cpu_amd64.s:
--------------------------------------------------------------------------------
1 | // Copyright 2019 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | #include "textflag.h"
5 |
6 | // Adapted from GOROOT/src/internal/cpu/cpu_x86.s
7 |
8 | // func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32)
9 | TEXT ·cpuid(SB), NOSPLIT, $0-24
10 | MOVL eaxArg+0(FP), AX
11 | MOVL ecxArg+4(FP), CX
12 | CPUID
13 | MOVL AX, eax+8(FP)
14 | MOVL BX, ebx+12(FP)
15 | MOVL CX, ecx+16(FP)
16 | MOVL DX, edx+20(FP)
17 | RET
18 |
19 | // func xgetbv() (eax, edx uint32)
20 | TEXT ·xgetbv(SB),NOSPLIT,$0-8
21 | #ifdef GOOS_nacl
22 | // nacl does not support XGETBV.
23 | MOVL $0, eax+0(FP)
24 | MOVL $0, edx+4(FP)
25 | #else
26 | MOVL $0, CX
27 | XGETBV
28 | MOVL AX, eax+0(FP)
29 | MOVL DX, edx+4(FP)
30 | #endif
31 | RET
32 |
--------------------------------------------------------------------------------
/go-fuzz/exectype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type execType -trimprefix exec"; DO NOT EDIT.
2 |
3 | package main
4 |
5 | import "strconv"
6 |
7 | const _execType_name = "BootstrapCorpusMinimizeInputMinimizeCrasherTriageInputFuzzVersifierSmashSonarSonarHintTotalCount"
8 |
9 | var _execType_index = [...]uint8{0, 9, 15, 28, 43, 54, 58, 67, 72, 77, 86, 91, 96}
10 |
11 | func (i execType) String() string {
12 | if i >= execType(len(_execType_index)-1) {
13 | return "execType(" + strconv.FormatInt(int64(i), 10) + ")"
14 | }
15 | return _execType_name[_execType_index[i]:_execType_index[i+1]]
16 | }
17 |
--------------------------------------------------------------------------------
/go-fuzz/hub.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 | "net/rpc"
10 | "path/filepath"
11 | "sync"
12 | "sync/atomic"
13 | "time"
14 |
15 | "github.com/dvyukov/go-fuzz/go-fuzz/versifier"
16 |
17 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
18 | . "github.com/dvyukov/go-fuzz/internal/go-fuzz-types"
19 | )
20 |
21 | const (
22 | syncPeriod = 3 * time.Second
23 | syncDeadline = 100 * syncPeriod
24 | connectionPollInterval = 100 * time.Millisecond
25 |
26 | minScore = 1.0
27 | maxScore = 1000.0
28 | defScore = 10.0
29 | )
30 |
31 | // Hub contains data shared between all workers in the process (e.g. corpus).
32 | // This reduces memory consumption for highly parallel workers.
33 | // Hub also handles communication with the coordinator.
34 | type Hub struct {
35 | id int
36 | coordinator *rpc.Client
37 |
38 | ro atomic.Value // *ROData
39 |
40 | maxCoverMu sync.Mutex
41 | maxCover atomic.Value // []byte
42 |
43 | initialTriage uint32
44 |
45 | corpusCoverSize int
46 | corpusSigs map[Sig]struct{}
47 | corpusStale bool
48 | triageQueue []CoordinatorInput
49 |
50 | triageC chan CoordinatorInput
51 | newInputC chan Input
52 | newCrasherC chan NewCrasherArgs
53 | syncC chan Stats
54 |
55 | stats Stats
56 | corpusOrigins [execCount]uint64
57 | }
58 |
59 | type ROData struct {
60 | corpus []Input
61 | corpusCover []byte
62 | badInputs map[Sig]struct{}
63 | suppressions map[Sig]struct{}
64 | strLits [][]byte // string literals in testee
65 | intLits [][]byte // int literals in testee
66 | coverBlocks map[int][]CoverBlock
67 | sonarSites []SonarSite
68 | verse *versifier.Verse
69 | }
70 |
71 | type Stats struct {
72 | execs uint64
73 | restarts uint64
74 | }
75 |
76 | func newHub(metadata MetaData) *Hub {
77 | procs := *flagProcs
78 | hub := &Hub{
79 | corpusSigs: make(map[Sig]struct{}),
80 | triageC: make(chan CoordinatorInput, procs),
81 | newInputC: make(chan Input, procs),
82 | newCrasherC: make(chan NewCrasherArgs, procs),
83 | syncC: make(chan Stats, procs),
84 | }
85 |
86 | if err := hub.connect(); err != nil {
87 | log.Fatalf("failed to connect to coordinator: %v", err)
88 | }
89 |
90 | coverBlocks := make(map[int][]CoverBlock)
91 | for _, b := range metadata.Blocks {
92 | coverBlocks[b.ID] = append(coverBlocks[b.ID], b)
93 | }
94 | sonarSites := make([]SonarSite, len(metadata.Sonar))
95 | for i, b := range metadata.Sonar {
96 | if i != b.ID {
97 | log.Fatalf("corrupted sonar metadata")
98 | }
99 | sonarSites[i].id = b.ID
100 | sonarSites[i].loc = fmt.Sprintf("%v:%v.%v,%v.%v", b.File, b.StartLine, b.StartCol, b.EndLine, b.EndCol)
101 | }
102 | hub.maxCover.Store(make([]byte, CoverSize))
103 |
104 | ro := &ROData{
105 | corpusCover: make([]byte, CoverSize),
106 | badInputs: make(map[Sig]struct{}),
107 | suppressions: make(map[Sig]struct{}),
108 | coverBlocks: coverBlocks,
109 | sonarSites: sonarSites,
110 | }
111 | // Prepare list of string and integer literals.
112 | for _, lit := range metadata.Literals {
113 | if lit.IsStr {
114 | ro.strLits = append(ro.strLits, []byte(lit.Val))
115 | } else {
116 | ro.intLits = append(ro.intLits, []byte(lit.Val))
117 | }
118 | }
119 | hub.ro.Store(ro)
120 |
121 | go hub.loop()
122 |
123 | return hub
124 | }
125 |
126 | func (hub *Hub) connect() error {
127 | var c *rpc.Client
128 | var err error
129 |
130 | t := time.Now()
131 | for {
132 | c, err = rpc.Dial("tcp", *flagWorker)
133 | if err == nil || time.Since(t) > *flagConnectionTimeout {
134 | break
135 | }
136 | time.Sleep(connectionPollInterval)
137 | }
138 | if err != nil {
139 | return err
140 | }
141 | var res ConnectRes
142 | if err := c.Call("Coordinator.Connect", &ConnectArgs{Procs: *flagProcs}, &res); err != nil {
143 | return err
144 | }
145 |
146 | hub.coordinator = c
147 | hub.id = res.ID
148 | hub.initialTriage = uint32(len(res.Corpus))
149 | hub.triageQueue = res.Corpus
150 | return nil
151 | }
152 |
153 | func (hub *Hub) loop() {
154 | // Local buffer helps to avoid deadlocks on chan overflows.
155 | var triageC chan CoordinatorInput
156 | var triageInput CoordinatorInput
157 |
158 | syncTicker := time.NewTicker(syncPeriod).C
159 | for {
160 | if len(hub.triageQueue) > 0 && triageC == nil {
161 | n := len(hub.triageQueue) - 1
162 | triageInput = hub.triageQueue[n]
163 | hub.triageQueue[n] = CoordinatorInput{}
164 | hub.triageQueue = hub.triageQueue[:n]
165 | triageC = hub.triageC
166 | }
167 |
168 | select {
169 | case <-syncTicker:
170 | // Sync with the coordinator.
171 | if *flagV >= 1 {
172 | ro := hub.ro.Load().(*ROData)
173 | log.Printf("hub: corpus=%v bootstrap=%v fuzz=%v minimize=%v versifier=%v smash=%v sonar=%v",
174 | len(ro.corpus), hub.corpusOrigins[execBootstrap]+hub.corpusOrigins[execCorpus],
175 | hub.corpusOrigins[execFuzz]+hub.corpusOrigins[execSonar],
176 | hub.corpusOrigins[execMinimizeInput]+hub.corpusOrigins[execMinimizeCrasher],
177 | hub.corpusOrigins[execVersifier], hub.corpusOrigins[execSmash],
178 | hub.corpusOrigins[execSonarHint])
179 | }
180 | args := &SyncArgs{
181 | ID: hub.id,
182 | Execs: hub.stats.execs,
183 | Restarts: hub.stats.restarts,
184 | CoverFullness: hub.corpusCoverSize,
185 | }
186 | hub.stats.execs = 0
187 | hub.stats.restarts = 0
188 | var res SyncRes
189 | if err := hub.coordinator.Call("Coordinator.Sync", args, &res); err != nil {
190 | log.Printf("sync call failed: %v, reconnection to coordinator", err)
191 | if err := hub.connect(); err != nil {
192 | log.Printf("failed to connect to coordinator: %v, killing worker", err)
193 | return
194 | }
195 | }
196 | if len(res.Inputs) > 0 {
197 | hub.triageQueue = append(hub.triageQueue, res.Inputs...)
198 | }
199 | if hub.corpusStale {
200 | hub.updateScores()
201 | hub.corpusStale = false
202 | }
203 |
204 | case triageC <- triageInput:
205 | // Send new input to workers for triage.
206 | if len(hub.triageQueue) > 0 {
207 | n := len(hub.triageQueue) - 1
208 | triageInput = hub.triageQueue[n]
209 | hub.triageQueue[n] = CoordinatorInput{}
210 | hub.triageQueue = hub.triageQueue[:n]
211 | } else {
212 | triageC = nil
213 | triageInput = CoordinatorInput{}
214 | }
215 |
216 | case s := <-hub.syncC:
217 | // Sync from a worker.
218 | hub.stats.execs += s.execs
219 | hub.stats.restarts += s.restarts
220 |
221 | case input := <-hub.newInputC:
222 | // New interesting input from workers.
223 | ro := hub.ro.Load().(*ROData)
224 | if !compareCover(ro.corpusCover, input.cover) {
225 | break
226 | }
227 | sig := hash(input.data)
228 | if _, ok := hub.corpusSigs[sig]; ok {
229 | break
230 | }
231 |
232 | // Passed deduplication, taking it.
233 | if *flagV >= 2 {
234 | log.Printf("hub received new input [%v]%v mine=%v", len(input.data), hash(input.data), input.mine)
235 | }
236 | hub.corpusSigs[sig] = struct{}{}
237 | ro1 := new(ROData)
238 | *ro1 = *ro
239 | // Assign it the default score, but mark corpus for score recalculation.
240 | hub.corpusStale = true
241 | scoreSum := 0
242 | if len(ro1.corpus) > 0 {
243 | scoreSum = ro1.corpus[len(ro1.corpus)-1].runningScoreSum
244 | }
245 | input.score = defScore
246 | input.runningScoreSum = scoreSum + defScore
247 | ro1.corpus = append(ro1.corpus, input)
248 | hub.updateMaxCover(input.cover)
249 | ro1.corpusCover = makeCopy(ro.corpusCover)
250 | hub.corpusCoverSize = updateMaxCover(ro1.corpusCover, input.cover)
251 | if input.res > 0 || input.typ == execBootstrap {
252 | ro1.verse = versifier.BuildVerse(ro.verse, input.data)
253 | }
254 | hub.ro.Store(ro1)
255 | hub.corpusOrigins[input.typ]++
256 |
257 | if input.mine {
258 | if err := hub.coordinator.Call("Coordinator.NewInput", NewInputArgs{hub.id, input.data, uint64(input.depth)}, nil); err != nil {
259 | log.Printf("new input call failed: %v, reconnecting to coordinator", err)
260 | if err := hub.connect(); err != nil {
261 | log.Printf("failed to connect to coordinator: %v, killing worker", err)
262 | return
263 | }
264 | }
265 | }
266 |
267 | if *flagDumpCover {
268 | dumpCover(filepath.Join(*flagWorkdir, "coverprofile"), ro.coverBlocks, ro.corpusCover)
269 | }
270 |
271 | case crash := <-hub.newCrasherC:
272 | // New crasher from workers. Woohoo!
273 | if crash.Hanging || !*flagDup {
274 | ro := hub.ro.Load().(*ROData)
275 | ro1 := new(ROData)
276 | *ro1 = *ro
277 | if crash.Hanging {
278 | ro1.badInputs = make(map[Sig]struct{})
279 | for k, v := range ro.badInputs {
280 | ro1.badInputs[k] = v
281 | }
282 | ro1.badInputs[hash(crash.Data)] = struct{}{}
283 | }
284 | if !*flagDup {
285 | ro1.suppressions = make(map[Sig]struct{})
286 | for k, v := range ro.suppressions {
287 | ro1.suppressions[k] = v
288 | }
289 | ro1.suppressions[hash(crash.Suppression)] = struct{}{}
290 | }
291 | hub.ro.Store(ro1)
292 | }
293 | if err := hub.coordinator.Call("Coordinator.NewCrasher", crash, nil); err != nil {
294 | log.Printf("new crasher call failed: %v", err)
295 | }
296 | }
297 | }
298 | }
299 |
300 | // Preliminary cover update to prevent new input thundering herd.
301 | // This function is synchronous to reduce latency.
302 | func (hub *Hub) updateMaxCover(cover []byte) bool {
303 | oldMaxCover := hub.maxCover.Load().([]byte)
304 | if !compareCover(oldMaxCover, cover) {
305 | return false
306 | }
307 | hub.maxCoverMu.Lock()
308 | defer hub.maxCoverMu.Unlock()
309 | oldMaxCover = hub.maxCover.Load().([]byte)
310 | if !compareCover(oldMaxCover, cover) {
311 | return false
312 | }
313 | maxCover := makeCopy(oldMaxCover)
314 | updateMaxCover(maxCover, cover)
315 | hub.maxCover.Store(maxCover)
316 | return true
317 | }
318 |
319 | func (hub *Hub) updateScores() {
320 | ro := hub.ro.Load().(*ROData)
321 | ro1 := new(ROData)
322 | *ro1 = *ro
323 | corpus := make([]Input, len(ro.corpus))
324 | copy(corpus, ro.corpus)
325 | ro1.corpus = corpus
326 |
327 | var sumExecTime, sumCoverSize uint64
328 | for _, inp := range corpus {
329 | sumExecTime += inp.execTime
330 | sumCoverSize += uint64(inp.coverSize)
331 | }
332 | n := uint64(len(corpus))
333 | avgExecTime := sumExecTime / n
334 | avgCoverSize := sumCoverSize / n
335 |
336 | // Phase 1: calculate score for each input independently.
337 | for i, inp := range corpus {
338 | score := defScore
339 |
340 | // Execution time multiplier 0.1-3x.
341 | // Fuzzing faster inputs increases efficiency.
342 | execTime := float64(inp.execTime) / float64(avgExecTime)
343 | if execTime > 10 {
344 | score /= 10
345 | } else if execTime > 4 {
346 | score /= 4
347 | } else if execTime > 2 {
348 | score /= 2
349 | } else if execTime < 0.25 {
350 | score *= 3
351 | } else if execTime < 0.33 {
352 | score *= 2
353 | } else if execTime < 0.5 {
354 | score *= 1.5
355 | }
356 |
357 | // Coverage size multiplier 0.25-3x.
358 | // Inputs with larger coverage are more interesting.
359 | coverSize := float64(inp.coverSize) / float64(avgCoverSize)
360 | if coverSize > 3 {
361 | score *= 3
362 | } else if coverSize > 2 {
363 | score *= 2
364 | } else if coverSize > 1.5 {
365 | score *= 1.5
366 | } else if coverSize < 0.3 {
367 | score /= 4
368 | } else if coverSize < 0.5 {
369 | score /= 2
370 | } else if coverSize < 0.75 {
371 | score /= 1.5
372 | }
373 |
374 | // Input depth multiplier 1-5x.
375 | // Deeper inputs have higher chances of digging deeper into code.
376 | if inp.depth < 10 {
377 | // no boost for you
378 | } else if inp.depth < 20 {
379 | score *= 2
380 | } else if inp.depth < 40 {
381 | score *= 3
382 | } else if inp.depth < 80 {
383 | score *= 4
384 | } else {
385 | score *= 5
386 | }
387 |
388 | // User boost (Fuzz function return value) multiplier 1-2x.
389 | // We don't know what it is, but user said so.
390 | if inp.res > 0 {
391 | // Assuming this is a correct input (e.g. deserialized successfully).
392 | score *= 2
393 | }
394 |
395 | if score < minScore {
396 | score = minScore
397 | } else if score > maxScore {
398 | score = maxScore
399 | }
400 | corpus[i].score = int(score)
401 | }
402 |
403 | // Phase 2: Choose a minimal set of (favored) inputs that give full coverage.
404 | // Non-favored inputs receive minimal score.
405 | type Candidate struct {
406 | index int
407 | score int
408 | chosen bool
409 | }
410 | candidates := make([]Candidate, CoverSize)
411 | for idx, inp := range corpus {
412 | corpus[idx].favored = false
413 | for i, c := range inp.cover {
414 | if c == 0 {
415 | continue
416 | }
417 | c = roundUpCover(c)
418 | if c != ro.corpusCover[i] {
419 | continue
420 | }
421 | if c > ro.corpusCover[i] {
422 | log.Fatalf("bad")
423 | }
424 | if candidates[i].score < inp.score {
425 | candidates[i].index = idx
426 | candidates[i].score = inp.score
427 | }
428 | }
429 | }
430 | for ci, cand := range candidates {
431 | if cand.score == 0 {
432 | continue
433 | }
434 | inp := &corpus[cand.index]
435 | inp.favored = true
436 | for i := ci + 1; i < CoverSize; i++ {
437 | c := inp.cover[i]
438 | if c == 0 {
439 | continue
440 | }
441 | c = roundUpCover(c)
442 | if c != ro.corpusCover[i] {
443 | continue
444 | }
445 | candidates[i].score = 0
446 | }
447 | }
448 | scoreSum := 0
449 | for i, inp := range corpus {
450 | if !inp.favored {
451 | inp.score = minScore
452 | }
453 | scoreSum += inp.score
454 | corpus[i].runningScoreSum = scoreSum
455 | }
456 |
457 | hub.ro.Store(ro1)
458 | }
459 |
--------------------------------------------------------------------------------
/go-fuzz/internal/pcg/pcg.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // Package pcg implements a 32 bit PRNG with a 64 bit period: pcg xsh rr 64 32.
5 | // See https://www.pcg-random.org/ for more information.
6 | // This implementation is geared specifically towards go-fuzz's needs:
7 | // Simple creation and use, no reproducibility, no concurrency safety,
8 | // just the methods go-fuzz needs, optimized for speed.
9 | package pcg
10 |
11 | import (
12 | "math/bits"
13 | "sync/atomic"
14 | "time"
15 | )
16 |
17 | var globalInc uint64 // PCG stream
18 |
19 | const multiplier uint64 = 6364136223846793005
20 |
21 | // Rand is a PRNG.
22 | // It should not be copied or shared.
23 | // No Rand methods are concurrency safe.
24 | // They are small, and cheap to create.
25 | // If in doubt: Just make another one.
26 | type Rand struct {
27 | noCopy noCopy // help avoid mistakes: ask vet to ensure that we don't make a copy
28 | state uint64
29 | inc uint64
30 | }
31 |
32 | // New generates a new, seeded Rand, ready for use.
33 | func New() *Rand {
34 | r := new(Rand)
35 | now := uint64(time.Now().UnixNano())
36 | inc := atomic.AddUint64(&globalInc, 1)
37 | r.state = now
38 | r.inc = (inc << 1) | 1
39 | r.step()
40 | r.state += now
41 | r.step()
42 | return r
43 | }
44 |
45 | func (r *Rand) step() {
46 | r.state *= multiplier
47 | r.state += r.inc
48 | }
49 |
50 | // Uint32 returns a pseudo-random uint32.
51 | func (r *Rand) Uint32() uint32 {
52 | x := r.state
53 | r.step()
54 | return bits.RotateLeft32(uint32(((x>>18)^x)>>27), -int(x>>59))
55 | }
56 |
57 | // Intn returns a pseudo-random number in [0, n).
58 | // n must fit in a uint32.
59 | func (r *Rand) Intn(n int) int {
60 | if int(uint32(n)) != n {
61 | panic("large Intn")
62 | }
63 | return int(r.Uint32n(uint32(n)))
64 | }
65 |
66 | // Uint32n returns a pseudo-random number in [0, n).
67 | //
68 | // For implementation details, see:
69 | // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
70 | // https://lemire.me/blog/2016/06/30/fast-random-shuffling
71 | func (r *Rand) Uint32n(n uint32) uint32 {
72 | v := r.Uint32()
73 | prod := uint64(v) * uint64(n)
74 | low := uint32(prod)
75 | if low < n {
76 | thresh := uint32(-int32(n)) % n
77 | for low < thresh {
78 | v = r.Uint32()
79 | prod = uint64(v) * uint64(n)
80 | low = uint32(prod)
81 | }
82 | }
83 | return uint32(prod >> 32)
84 | }
85 |
86 | // Exp2 generates n with probability 1/2^(n+1).
87 | func (r *Rand) Exp2() int {
88 | return bits.TrailingZeros32(r.Uint32())
89 | }
90 |
91 | // Bool generates a random bool.
92 | func (r *Rand) Bool() bool {
93 | return r.Uint32()&1 == 0
94 | }
95 |
96 | // noCopy may be embedded into structs which must not be copied
97 | // after the first use.
98 | //
99 | // See https://golang.org/issues/8005#issuecomment-190753527
100 | // for details.
101 | type noCopy struct{}
102 |
103 | // Lock is a no-op used by -copylocks checker from `go vet`.
104 | func (*noCopy) Lock() {}
105 | func (*noCopy) Unlock() {}
106 |
--------------------------------------------------------------------------------
/go-fuzz/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "flag"
8 | "log"
9 | "net"
10 | "os"
11 | "os/signal"
12 | "os/user"
13 | "path/filepath"
14 | "runtime"
15 | "runtime/debug"
16 | "sync/atomic"
17 | "syscall"
18 | "time"
19 |
20 | "golang.org/x/tools/go/packages"
21 | )
22 |
23 | //go:generate go build github.com/dvyukov/go-fuzz/go-fuzz/vendor/github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs
24 | //go:generate ./go-bindata-assetfs assets/...
25 | //go:generate goimports -w bindata_assetfs.go
26 | //go:generate rm go-bindata-assetfs
27 |
28 | var (
29 | flagWorkdir = flag.String("workdir", ".", "dir with persistent work data")
30 | flagProcs = flag.Int("procs", runtime.NumCPU(), "parallelism level")
31 | flagTimeout = flag.Int("timeout", 10, "test timeout, in seconds")
32 | flagMinimize = flag.Duration("minimize", 1*time.Minute, "time limit for input minimization")
33 | flagCoordinator = flag.String("coordinator", "", "coordinator mode (value is coordinator address)")
34 | flagWorker = flag.String("worker", "", "worker mode (value is coordinator address)")
35 | flagConnectionTimeout = flag.Duration("connectiontimeout", 1*time.Minute, "time limit for worker to try to connect coordinator")
36 | flagBin = flag.String("bin", "", "test binary built with go-fuzz-build")
37 | flagFunc = flag.String("func", "", "function to fuzz")
38 | flagDumpCover = flag.Bool("dumpcover", false, "dump coverage profile into workdir")
39 | flagDup = flag.Bool("dup", false, "collect duplicate crashers")
40 | flagTestOutput = flag.Bool("testoutput", false, "print test binary output to stdout (for debugging only)")
41 | flagCoverCounters = flag.Bool("covercounters", true, "use coverage hit counters")
42 | flagSonar = flag.Bool("sonar", true, "use sonar hints")
43 | flagV = flag.Int("v", 0, "verbosity level")
44 | flagHTTP = flag.String("http", "", "HTTP server listen address (coordinator mode only)")
45 |
46 | shutdown uint32
47 | shutdownC = make(chan struct{})
48 | shutdownCleanup []func()
49 | )
50 |
51 | func main() {
52 | flag.Parse()
53 | if *flagCoordinator != "" && *flagWorker != "" {
54 | log.Fatalf("both -coordinator and -worker are specified")
55 | }
56 | if *flagHTTP != "" && *flagWorker != "" {
57 | log.Fatalf("both -http and -worker are specified")
58 | }
59 |
60 | go func() {
61 | c := make(chan os.Signal, 1)
62 | signal.Notify(c, syscall.SIGINT)
63 | <-c
64 | atomic.StoreUint32(&shutdown, 1)
65 | close(shutdownC)
66 | log.Printf("shutting down...")
67 | time.Sleep(2 * time.Second)
68 | for _, f := range shutdownCleanup {
69 | f()
70 | }
71 | os.Exit(0)
72 | }()
73 |
74 | runtime.GOMAXPROCS(min(*flagProcs, runtime.NumCPU()))
75 | debug.SetGCPercent(50) // most memory is in large binary blobs
76 | lowerProcessPrio()
77 |
78 | *flagWorkdir = expandHomeDir(*flagWorkdir)
79 | *flagBin = expandHomeDir(*flagBin)
80 |
81 | if *flagCoordinator != "" || *flagWorker == "" {
82 | if *flagWorkdir == "" {
83 | log.Fatalf("-workdir is not set")
84 | }
85 | if *flagCoordinator == "" {
86 | *flagCoordinator = "localhost:0"
87 | }
88 | ln, err := net.Listen("tcp", *flagCoordinator)
89 | if err != nil {
90 | log.Fatalf("failed to listen: %v", err)
91 | }
92 | if *flagCoordinator == "localhost:0" && *flagWorker == "" {
93 | *flagWorker = ln.Addr().String()
94 | }
95 | go coordinatorMain(ln)
96 | }
97 |
98 | if *flagWorker != "" {
99 | if *flagBin == "" {
100 | // Try the default. Best effort only.
101 | var bin string
102 | cfg := new(packages.Config)
103 | // Note that we do not set GO111MODULE here in order to respect any GO111MODULE
104 | // setting by the user as we are finding dependencies. See modules support
105 | // comments in go-fuzz-build/main.go for more details.
106 | cfg.Env = os.Environ()
107 | pkgs, err := packages.Load(cfg, ".")
108 | if err == nil && len(pkgs) == 1 {
109 | bin = pkgs[0].Name + "-fuzz.zip"
110 | _, err := os.Stat(bin)
111 | if err != nil {
112 | bin = ""
113 | }
114 | }
115 | if bin == "" {
116 | log.Fatalf("-bin is not set")
117 | }
118 | *flagBin = bin
119 | }
120 | go workerMain()
121 | }
122 |
123 | select {}
124 | }
125 |
126 | // expandHomeDir expands the tilde sign and replaces it
127 | // with current users home directory and returns it.
128 | func expandHomeDir(path string) string {
129 | if len(path) > 2 && path[:2] == "~/" {
130 | usr, _ := user.Current()
131 | path = filepath.Join(usr.HomeDir, path[2:])
132 | }
133 | return path
134 | }
135 |
--------------------------------------------------------------------------------
/go-fuzz/mutator.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "encoding/binary"
8 | "sort"
9 | "strconv"
10 |
11 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
12 | "github.com/dvyukov/go-fuzz/go-fuzz/internal/pcg"
13 | )
14 |
15 | type Mutator struct {
16 | r *pcg.Rand
17 | }
18 |
19 | func newMutator() *Mutator {
20 | return &Mutator{r: pcg.New()}
21 | }
22 |
23 | func (m *Mutator) rand(n int) int {
24 | return m.r.Intn(n)
25 | }
26 |
27 | // randbig generates a number in [0, 2³⁰).
28 | func (m *Mutator) randbig() int64 {
29 | return int64(m.r.Uint32() >> 2)
30 | }
31 |
32 | func (m *Mutator) randByteOrder() binary.ByteOrder {
33 | if m.r.Bool() {
34 | return binary.LittleEndian
35 | }
36 | return binary.BigEndian
37 | }
38 |
39 | func (m *Mutator) generate(ro *ROData) ([]byte, int) {
40 | corpus := ro.corpus
41 | scoreSum := corpus[len(corpus)-1].runningScoreSum
42 | weightedIdx := m.rand(scoreSum)
43 | idx := sort.Search(len(corpus), func(i int) bool {
44 | return corpus[i].runningScoreSum > weightedIdx
45 | })
46 | input := &corpus[idx]
47 | return m.mutate(input.data, ro), input.depth + 1
48 | }
49 |
50 | func (m *Mutator) mutate(data []byte, ro *ROData) []byte {
51 | corpus := ro.corpus
52 | res := make([]byte, len(data))
53 | copy(res, data)
54 | nm := 1 + m.r.Exp2()
55 | for iter := 0; iter < nm; iter++ {
56 | switch m.rand(20) {
57 | case 0:
58 | // Remove a range of bytes.
59 | if len(res) <= 1 {
60 | iter--
61 | continue
62 | }
63 | pos0 := m.rand(len(res))
64 | pos1 := pos0 + m.chooseLen(len(res)-pos0)
65 | copy(res[pos0:], res[pos1:])
66 | res = res[:len(res)-(pos1-pos0)]
67 | case 1:
68 | // Insert a range of random bytes.
69 | pos := m.rand(len(res) + 1)
70 | n := m.chooseLen(10)
71 | for i := 0; i < n; i++ {
72 | res = append(res, 0)
73 | }
74 | copy(res[pos+n:], res[pos:])
75 | for i := 0; i < n; i++ {
76 | res[pos+i] = byte(m.rand(256))
77 | }
78 | case 2:
79 | // Duplicate a range of bytes.
80 | if len(res) <= 1 {
81 | iter--
82 | continue
83 | }
84 | src := m.rand(len(res))
85 | dst := m.rand(len(res))
86 | for dst == src {
87 | dst = m.rand(len(res))
88 | }
89 | n := m.chooseLen(len(res) - src)
90 | tmp := make([]byte, n)
91 | copy(tmp, res[src:])
92 | for i := 0; i < n; i++ {
93 | res = append(res, 0)
94 | }
95 | copy(res[dst+n:], res[dst:])
96 | for i := 0; i < n; i++ {
97 | res[dst+i] = tmp[i]
98 | }
99 | case 3:
100 | // Copy a range of bytes.
101 | if len(res) <= 1 {
102 | iter--
103 | continue
104 | }
105 | src := m.rand(len(res))
106 | dst := m.rand(len(res))
107 | for dst == src {
108 | dst = m.rand(len(res))
109 | }
110 | n := m.chooseLen(len(res) - src)
111 | if dst > len(res) || src+n > len(res) {
112 | println(len(res), dst, src, n)
113 | }
114 | copy(res[dst:], res[src:src+n])
115 | case 4:
116 | // Bit flip. Spooky!
117 | if len(res) == 0 {
118 | iter--
119 | continue
120 | }
121 | pos := m.rand(len(res))
122 | res[pos] ^= 1 << uint(m.rand(8))
123 | case 5:
124 | // Set a byte to a random value.
125 | if len(res) == 0 {
126 | iter--
127 | continue
128 | }
129 | pos := m.rand(len(res))
130 | res[pos] ^= byte(m.rand(255)) + 1
131 | case 6:
132 | // Swap 2 bytes.
133 | if len(res) <= 1 {
134 | iter--
135 | continue
136 | }
137 | src := m.rand(len(res))
138 | dst := m.rand(len(res))
139 | for dst == src {
140 | dst = m.rand(len(res))
141 | }
142 | res[src], res[dst] = res[dst], res[src]
143 | case 7:
144 | // Add/subtract from a byte.
145 | if len(res) == 0 {
146 | iter--
147 | continue
148 | }
149 | pos := m.rand(len(res))
150 | v := byte(m.rand(35) + 1)
151 | if m.r.Bool() {
152 | res[pos] += v
153 | } else {
154 | res[pos] -= v
155 | }
156 | case 8:
157 | // Add/subtract from a uint16.
158 | if len(res) < 2 {
159 | iter--
160 | continue
161 | }
162 | pos := m.rand(len(res) - 1)
163 | buf := res[pos:]
164 | v := uint16(m.rand(35) + 1)
165 | if m.r.Bool() {
166 | v = 0 - v
167 | }
168 | enc := m.randByteOrder()
169 | enc.PutUint16(buf, enc.Uint16(buf)+v)
170 | case 9:
171 | // Add/subtract from a uint32.
172 | if len(res) < 4 {
173 | iter--
174 | continue
175 | }
176 | pos := m.rand(len(res) - 3)
177 | buf := res[pos:]
178 | v := uint32(m.rand(35) + 1)
179 | if m.r.Bool() {
180 | v = 0 - v
181 | }
182 | enc := m.randByteOrder()
183 | enc.PutUint32(buf, enc.Uint32(buf)+v)
184 | case 10:
185 | // Add/subtract from a uint64.
186 | if len(res) < 8 {
187 | iter--
188 | continue
189 | }
190 | pos := m.rand(len(res) - 7)
191 | buf := res[pos:]
192 | v := uint64(m.rand(35) + 1)
193 | if m.r.Bool() {
194 | v = 0 - v
195 | }
196 | enc := m.randByteOrder()
197 | enc.PutUint64(buf, enc.Uint64(buf)+v)
198 | case 11:
199 | // Replace a byte with an interesting value.
200 | if len(res) == 0 {
201 | iter--
202 | continue
203 | }
204 | pos := m.rand(len(res))
205 | res[pos] = byte(interesting8[m.rand(len(interesting8))])
206 | case 12:
207 | // Replace an uint16 with an interesting value.
208 | if len(res) < 2 {
209 | iter--
210 | continue
211 | }
212 | pos := m.rand(len(res) - 1)
213 | buf := res[pos:]
214 | v := uint16(interesting16[m.rand(len(interesting16))])
215 | m.randByteOrder().PutUint16(buf, v)
216 | case 13:
217 | // Replace an uint32 with an interesting value.
218 | if len(res) < 4 {
219 | iter--
220 | continue
221 | }
222 | pos := m.rand(len(res) - 3)
223 | buf := res[pos:]
224 | v := uint32(interesting32[m.rand(len(interesting32))])
225 | m.randByteOrder().PutUint32(buf, v)
226 | case 14:
227 | // Replace an ascii digit with another digit.
228 | var digits []int
229 | for i, v := range res {
230 | if v >= '0' && v <= '9' {
231 | digits = append(digits, i)
232 | }
233 | }
234 | if len(digits) == 0 {
235 | iter--
236 | continue
237 | }
238 | pos := m.rand(len(digits))
239 | was := res[digits[pos]]
240 | now := was
241 | for was == now {
242 | now = byte(m.rand(10)) + '0'
243 | }
244 | res[digits[pos]] = now
245 | case 15:
246 | // Replace a multi-byte ASCII number with another number.
247 | type arange struct {
248 | start int
249 | end int
250 | }
251 | var numbers []arange
252 | start := -1
253 | for i, v := range res {
254 | if (v >= '0' && v <= '9') || (start == -1 && v == '-') {
255 | if start == -1 {
256 | start = i
257 | } else if i == len(res)-1 {
258 | // At final byte of input.
259 | if i-start > 0 {
260 | numbers = append(numbers, arange{start, i + 1})
261 | }
262 | }
263 | } else {
264 | if start != -1 && i-start > 1 {
265 | numbers = append(numbers, arange{start, i})
266 | start = -1
267 | }
268 | }
269 | }
270 | if len(numbers) == 0 {
271 | iter--
272 | continue
273 | }
274 | var v int64
275 | switch m.rand(4) {
276 | case 0:
277 | v = int64(m.rand(1000))
278 | case 1:
279 | v = m.randbig()
280 | case 2:
281 | v = m.randbig() * m.randbig()
282 | case 3:
283 | v = -m.randbig()
284 | }
285 | r := numbers[m.rand(len(numbers))]
286 | if res[r.start] == '-' {
287 | // If we started with a negative number, invert the sign of v.
288 | // The idea here is that negative numbers will mostly stay negative;
289 | // we only generate a negative (positive) replacement 1/4th of the time.
290 | v *= -1
291 | }
292 | str := strconv.FormatInt(v, 10)
293 | tmp := make([]byte, len(res)-(r.end-r.start)+len(str))
294 | copy(tmp, res[:r.start])
295 | copy(tmp[r.start:], str)
296 | copy(tmp[r.start+len(str):], res[r.end:])
297 | res = tmp
298 | case 16:
299 | // Splice another input.
300 | if len(res) < 4 || len(corpus) < 2 {
301 | iter--
302 | continue
303 | }
304 | other := corpus[m.rand(len(corpus))].data
305 | if len(other) < 4 || &res[0] == &other[0] {
306 | iter--
307 | continue
308 | }
309 | // Find common prefix and suffix.
310 | idx0 := 0
311 | for idx0 < len(res) && idx0 < len(other) && res[idx0] == other[idx0] {
312 | idx0++
313 | }
314 | idx1 := 0
315 | for idx1 < len(res) && idx1 < len(other) && res[len(res)-idx1-1] == other[len(other)-idx1-1] {
316 | idx1++
317 | }
318 | // If diffing parts are too small, there is no sense in splicing, rely on byte flipping.
319 | diff := min(len(res)-idx0-idx1, len(other)-idx0-idx1)
320 | if diff < 4 {
321 | iter--
322 | continue
323 | }
324 | copy(res[idx0:idx0+m.rand(diff-2)+1], other[idx0:])
325 | case 17:
326 | // Insert a part of another input.
327 | if len(res) < 4 || len(corpus) < 2 {
328 | iter--
329 | continue
330 | }
331 | other := corpus[m.rand(len(corpus))].data
332 | if len(other) < 4 || &res[0] == &other[0] {
333 | iter--
334 | continue
335 | }
336 | pos0 := m.rand(len(res) + 1)
337 | pos1 := m.rand(len(other) - 2)
338 | n := m.chooseLen(len(other)-pos1-2) + 2
339 | for i := 0; i < n; i++ {
340 | res = append(res, 0)
341 | }
342 | copy(res[pos0+n:], res[pos0:])
343 | for i := 0; i < n; i++ {
344 | res[pos0+i] = other[pos1+i]
345 | }
346 | case 18:
347 | // Insert a literal.
348 | // TODO: encode int literals in big-endian, base-128, etc.
349 | if len(ro.intLits) == 0 && len(ro.strLits) == 0 {
350 | iter--
351 | continue
352 | }
353 | var lit []byte
354 | if len(ro.strLits) != 0 && m.r.Bool() {
355 | lit = []byte(ro.strLits[m.rand(len(ro.strLits))])
356 | } else {
357 | lit = ro.intLits[m.rand(len(ro.intLits))]
358 | if m.rand(3) == 0 {
359 | lit = reverse(lit)
360 | }
361 | }
362 | pos := m.rand(len(res) + 1)
363 | for i := 0; i < len(lit); i++ {
364 | res = append(res, 0)
365 | }
366 | copy(res[pos+len(lit):], res[pos:])
367 | copy(res[pos:], lit)
368 | case 19:
369 | // Replace with literal.
370 | if len(ro.intLits) == 0 && len(ro.strLits) == 0 {
371 | iter--
372 | continue
373 | }
374 | var lit []byte
375 | if len(ro.strLits) != 0 && m.r.Bool() {
376 | lit = []byte(ro.strLits[m.rand(len(ro.strLits))])
377 | } else {
378 | lit = ro.intLits[m.rand(len(ro.intLits))]
379 | if m.rand(3) == 0 {
380 | lit = reverse(lit)
381 | }
382 | }
383 | if len(lit) >= len(res) {
384 | iter--
385 | continue
386 | }
387 | pos := m.rand(len(res) - len(lit))
388 | copy(res[pos:], lit)
389 | }
390 | }
391 | if len(res) > MaxInputSize {
392 | res = res[:MaxInputSize]
393 | }
394 | return res
395 | }
396 |
397 | // chooseLen chooses length of range mutation.
398 | // It gives preference to shorter ranges.
399 | func (m *Mutator) chooseLen(n int) int {
400 | switch x := m.rand(100); {
401 | case x < 90:
402 | return m.rand(min(8, n)) + 1
403 | case x < 99:
404 | return m.rand(min(32, n)) + 1
405 | default:
406 | return m.rand(n) + 1
407 | }
408 | }
409 |
410 | func min(a, b int) int {
411 | if a < b {
412 | return a
413 | }
414 | return b
415 | }
416 |
417 | var (
418 | interesting8 = []int8{-128, -1, 0, 1, 16, 32, 64, 100, 127}
419 | interesting16 = []int16{-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767}
420 | interesting32 = []int32{-2147483648, -100663046, -32769, 32768, 65535, 65536, 100663045, 2147483647}
421 | )
422 |
423 | func init() {
424 | for _, v := range interesting8 {
425 | interesting16 = append(interesting16, int16(v))
426 | }
427 | for _, v := range interesting16 {
428 | interesting32 = append(interesting32, int32(v))
429 | }
430 | }
431 |
--------------------------------------------------------------------------------
/go-fuzz/persistent.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "crypto/sha1"
8 | "encoding/hex"
9 | "fmt"
10 | "io/ioutil"
11 | "log"
12 | "os"
13 | "path/filepath"
14 | "strconv"
15 | )
16 |
17 | // PersistentSet is a set of binary blobs with a persistent mirror on disk.
18 | type PersistentSet struct {
19 | dir string
20 | m map[Sig]Artifact
21 | }
22 |
23 | type Artifact struct {
24 | data []byte
25 | meta uint64 // arbitrary user payload
26 | user bool // file created by user
27 | }
28 |
29 | type Sig [sha1.Size]byte
30 |
31 | func hash(data []byte) Sig {
32 | return Sig(sha1.Sum(data))
33 | }
34 |
35 | func newPersistentSet(dir string) *PersistentSet {
36 | ps := &PersistentSet{
37 | dir: dir,
38 | m: make(map[Sig]Artifact),
39 | }
40 | os.MkdirAll(dir, 0770)
41 | ps.readInDir(dir)
42 | return ps
43 | }
44 |
45 | func (ps *PersistentSet) readInDir(dir string) {
46 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
47 | if err != nil {
48 | log.Printf("error during dir walk: %v\n", err)
49 | return nil
50 | }
51 | if info.IsDir() {
52 | return nil
53 | }
54 | data, err := ioutil.ReadFile(path)
55 | if err != nil {
56 | log.Printf("error during file read: %v\n", err)
57 | return nil
58 | }
59 | sig := hash(data)
60 | if _, ok := ps.m[sig]; ok {
61 | return nil
62 | }
63 | name := info.Name()
64 | const hexLen = 2 * sha1.Size
65 | if len(name) > hexLen+1 && isHexString(name[:hexLen]) && name[hexLen] == '.' {
66 | return nil // description file
67 | }
68 | var meta uint64
69 | if len(name) > hexLen+1 && isHexString(name[:hexLen]) && name[hexLen] == '-' {
70 | meta, _ = strconv.ParseUint(name[2*sha1.Size+1:], 10, 64)
71 | }
72 | a := Artifact{data, meta, len(name) < hexLen || !isHexString(name[:hexLen])}
73 | ps.m[sig] = a
74 | return nil
75 | })
76 | }
77 |
78 | func persistentFilename(dir string, a Artifact, sig Sig) string {
79 | fname := filepath.Join(dir, hex.EncodeToString(sig[:]))
80 | if a.meta != 0 {
81 | fname += fmt.Sprintf("-%v", a.meta)
82 | }
83 | return fname
84 | }
85 |
86 | func isHexString(s string) bool {
87 | for _, v := range []byte(s) {
88 | if v >= '0' && v <= '9' || v >= 'a' && v <= 'f' {
89 | continue
90 | }
91 | return false
92 | }
93 | return true
94 | }
95 |
96 | func (ps *PersistentSet) add(a Artifact) bool {
97 | sig := hash(a.data)
98 | if _, ok := ps.m[sig]; ok {
99 | return false
100 | }
101 | ps.m[sig] = a
102 | fname := persistentFilename(ps.dir, a, sig)
103 | if err := ioutil.WriteFile(fname, a.data, 0660); err != nil {
104 | log.Printf("failed to write file: %v", err)
105 | }
106 | return true
107 | }
108 |
109 | // addDescription creates a complementary to data file on disk.
110 | func (ps *PersistentSet) addDescription(data []byte, desc []byte, typ string) {
111 | sig := hash(data)
112 | fname := filepath.Join(ps.dir, fmt.Sprintf("%v.%v", hex.EncodeToString(sig[:]), typ))
113 | if err := ioutil.WriteFile(fname, desc, 0660); err != nil {
114 | log.Printf("failed to write file: %v", err)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/go-fuzz/sonar.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "bytes"
8 | "encoding/binary"
9 | "encoding/hex"
10 | "log"
11 | "path/filepath"
12 | "strconv"
13 | "sync"
14 | "unicode"
15 |
16 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
17 | )
18 |
19 | type SonarSite struct {
20 | id int // unique site id (const)
21 | loc string // file:line.pos,line.pos (const)
22 | sync.Mutex
23 | dynamic bool // both operands are not constant
24 | takenFuzz [2]int // number of times condition evaluated to false/true during fuzzing
25 | takenTotal [2]int // number of times condition evaluated to false/true in total
26 | val [2][]byte
27 | }
28 |
29 | type SonarSample struct {
30 | site *SonarSite
31 | flags byte
32 | val [2][]byte
33 | }
34 |
35 | func (w *Worker) parseSonarData(sonar []byte) (res []SonarSample) {
36 | ro := w.hub.ro.Load().(*ROData)
37 | sonar = makeCopy(sonar)
38 | for len(sonar) > SonarHdrLen {
39 | id := binary.LittleEndian.Uint32(sonar)
40 | flags := byte(id)
41 | id >>= 8
42 | n1 := sonar[4]
43 | n2 := sonar[5]
44 | sonar = sonar[SonarHdrLen:]
45 | if n1 > SonarMaxLen || n2 > SonarMaxLen || len(sonar) < int(n1)+int(n2) {
46 | log.Fatalf("corrupted sonar data: hdr=[%v/%v/%v] data=%v", flags, n1, n2, len(sonar))
47 | }
48 | v1 := makeCopy(sonar[:n1])
49 | v2 := makeCopy(sonar[n1 : n1+n2])
50 | sonar = sonar[n1+n2:]
51 |
52 | // Trim trailing 0x00 and 0xff bytes (we don't know exact size of operands).
53 | if flags&SonarString == 0 {
54 | for len(v1) > 0 || len(v2) > 0 {
55 | i := len(v1) - 1
56 | if len(v2) > len(v1) {
57 | i = len(v2) - 1
58 | }
59 | var c1, c2 byte
60 | if i < len(v1) {
61 | c1 = v1[i]
62 | }
63 | if i < len(v2) {
64 | c2 = v2[i]
65 | }
66 | if (c1 == 0 || c1 == 0xff) && (c2 == 0 || c2 == 0xff) {
67 | if i < len(v1) {
68 | v1 = v1[:i]
69 | }
70 | if i < len(v2) {
71 | v2 = v2[:i]
72 | }
73 | continue
74 | }
75 | break
76 | }
77 | }
78 |
79 | res = append(res, SonarSample{&ro.sonarSites[id], flags, [2][]byte{v1, v2}})
80 | }
81 | return res
82 | }
83 |
84 | func (w *Worker) processSonarData(data, sonar []byte, depth int, smash bool) {
85 | ro := w.hub.ro.Load().(*ROData)
86 | updated := false
87 | checked := make(map[string]struct{})
88 | samples := w.parseSonarData(sonar)
89 | for _, sam := range samples {
90 | // TODO: extract literal corpus from sonar instead of from source.
91 | // This should give smaller, better corpus which does not contain literals from dead code.
92 |
93 | // TODO: detect loop counters (small incrementing/decrementing values on the same site).
94 | // Either ignore them or handle differently (e.g. alter a string length).
95 |
96 | site := sam.site
97 | flags := sam.flags
98 | v1 := sam.val[0]
99 | v2 := sam.val[1]
100 | res := sam.evaluate()
101 | // Ignore sites that has at least one const operand and
102 | // are already taken both ways enough times.
103 | upd, skip := site.update(sam, smash, res)
104 | if upd {
105 | updated = true
106 | }
107 | if skip {
108 | continue
109 | }
110 | if smash && bytes.Equal(v1, v2) {
111 | // We systematically mutate all bytes during smashing,
112 | // no point in trying to break equality here.
113 | continue
114 | }
115 | testInput := func(tmp []byte) {
116 | w.testInput(tmp, depth+1, execSonarHint)
117 | }
118 | check := func(indexdata, v1, v2 []byte) {
119 | if len(v1) == 0 || bytes.Equal(v1, v2) || !bytes.Contains(indexdata, v1) {
120 | return
121 | }
122 | if len(indexdata) != len(data) {
123 | // We use indices into indexdata to construct indices into data below.
124 | // If they don't have the same size, this operation is nonsensical and may panic.
125 | panic("len(indexdata) != len(data)")
126 | }
127 | vv := string(v1) + "\t|\t" + string(v2)
128 | if _, ok := checked[vv]; ok {
129 | return
130 | }
131 | checked[vv] = struct{}{}
132 | pos := 0
133 | for {
134 | i := bytes.Index(indexdata[pos:], v1)
135 | if i == -1 {
136 | break
137 | }
138 | i += pos
139 | pos = i + 1
140 | tmp := make([]byte, len(data)-len(v1)+len(v2))
141 | copy(tmp, data[:i])
142 | copy(tmp[i:], v2)
143 | copy(tmp[i+len(v2):], data[i+len(v1):])
144 | if len(tmp) > CoverSize {
145 | tmp = tmp[:CoverSize]
146 | }
147 | testInput(tmp)
148 | if flags&SonarString != 0 && len(v1) != len(v2) && len(tmp) < CoverSize {
149 | // Update length field.
150 | // TODO: handle multi-byte/big-endian/base-128 length fields.
151 | diff := byte(len(v2) - len(v1))
152 | for idx := i - 1; idx >= 0 && idx+5 >= i; idx-- {
153 | tmp[idx] += diff
154 | testInput(tmp)
155 | tmp[idx] -= diff
156 | }
157 | }
158 | }
159 | }
160 | check1 := func(v1, v2 []byte) {
161 | check(data, v1, v2)
162 | // TODO: for strings check upper/lower case.
163 | if flags&SonarString != 0 {
164 | if bytes.Equal(v1, bytes.ToLower(v1)) && bytes.Equal(v2, bytes.ToLower(v2)) {
165 | if lower := bytes.ToLower(data); len(lower) == len(data) {
166 | check(lower, v1, v2)
167 | }
168 | }
169 | if bytes.Equal(v1, bytes.ToUpper(v1)) && bytes.Equal(v2, bytes.ToUpper(v2)) {
170 | if upper := bytes.ToUpper(data); len(upper) == len(data) {
171 | check(upper, v1, v2)
172 | }
173 | }
174 | } else {
175 | // Try several common wire encodings of the values:
176 | // network format (big endian), hex, base-128.
177 | // TODO: try more encodings if it proves to be useful:
178 | // base-64, quoted-printable, xml-escaping, hex+increment/decrement.
179 |
180 | if len(v1) == 1 && len(v2) == 1 && unicode.IsLower(rune(v1[0])) && unicode.IsLower(rune(v2[0])) {
181 | if lower := bytes.ToLower(data); len(lower) == len(data) {
182 | check(lower, v1, v2)
183 | }
184 | }
185 | if len(v1) == 1 && len(v2) == 1 && unicode.IsUpper(rune(v1[0])) && unicode.IsUpper(rune(v2[0])) {
186 | if upper := bytes.ToUpper(data); len(upper) == len(data) {
187 | check(upper, v1, v2)
188 | }
189 | }
190 |
191 | // Increment and decrement take care of less and greater comparison operators
192 | // as well as of off-by-one bugs.
193 | check(data, v1, increment(v2))
194 | check(data, v1, decrement(v2))
195 |
196 | // Also try big-endian increments/decrements.
197 | if len(v1) > 1 {
198 | check(data, reverse(v1), reverse(v2))
199 | check(data, reverse(v1), reverse(increment(v2)))
200 | check(data, reverse(v1), reverse(decrement(v2)))
201 | check(data, v1, reverse(increment(reverse(v2))))
202 | check(data, v1, reverse(decrement(reverse(v2))))
203 | }
204 |
205 | // Base-128.
206 | // TODO: try to treat the value as negative.
207 | var u1, u2 uint64
208 | for i := 0; i < len(v1); i++ {
209 | u1 += uint64(v1[i]) << uint(i*8)
210 | }
211 | for i := 0; i < len(v2); i++ {
212 | u2 += uint64(v2[i]) << uint(i*8)
213 | }
214 | if u1 > 127 || u2 > 127 { // otherwise it's the same as byte replacement
215 | var vv1, vv2 [10]byte
216 | n1 := binary.PutUvarint(vv1[:], u1)
217 | n2 := binary.PutUvarint(vv2[:], u2)
218 | check(data, vv1[:n1], vv2[:n2])
219 |
220 | // Increment/decrement in base-128.
221 | n1 = binary.PutUvarint(vv1[:], u1+1)
222 | n2 = binary.PutUvarint(vv2[:], u2+1)
223 | check(data, vv1[:n1], vv2[:n2])
224 | n1 = binary.PutUvarint(vv1[:], u1-1)
225 | n2 = binary.PutUvarint(vv2[:], u2-1)
226 | check(data, vv1[:n1], vv2[:n2])
227 | }
228 |
229 | // Ascii-encoding.
230 | // TODO: try to treat the value as negative.
231 | s1 := strconv.FormatUint(u1, 10)
232 | s2 := strconv.FormatUint(u2, 10)
233 | check(data, []byte(s1), []byte(s2))
234 | }
235 | check(data, []byte(hex.EncodeToString(v1)), []byte(hex.EncodeToString(v2)))
236 | }
237 | if flags&SonarConst1 == 0 {
238 | check1(v1, v2)
239 | }
240 | if flags&SonarConst2 == 0 {
241 | check1(v2, v1)
242 | }
243 | }
244 | if updated && *flagDumpCover {
245 | dumpMu.Lock()
246 | defer dumpMu.Unlock()
247 | dumpSonar(filepath.Join(*flagWorkdir, "sonarprofile"), ro.sonarSites)
248 | }
249 | }
250 |
251 | var dumpMu sync.Mutex
252 |
253 | func (site *SonarSite) update(sam SonarSample, smash, resb bool) (updated, skip bool) {
254 | res := 0
255 | if resb {
256 | res = 1
257 | }
258 | site.Lock()
259 | defer site.Unlock()
260 | v1 := sam.val[0]
261 | v2 := sam.val[1]
262 | if !site.dynamic && sam.flags&SonarConst1+sam.flags&SonarConst2 == 0 {
263 | if site.val[0] == nil {
264 | site.val[0] = makeCopy(v1)
265 | }
266 | if site.val[1] == nil {
267 | site.val[1] = makeCopy(v2)
268 | }
269 | if !bytes.Equal(site.val[0], v1) && !bytes.Equal(site.val[1], v2) {
270 | site.val[0] = nil
271 | site.val[1] = nil
272 | site.dynamic = true
273 | }
274 | }
275 | if site.takenTotal[res] == 0 {
276 | updated = true
277 | }
278 | site.takenTotal[res]++
279 | if !smash {
280 | site.takenFuzz[res]++
281 | }
282 | if !site.dynamic && !smash {
283 | // Skip this site if it has at least one const operand
284 | // and is taken both ways enough times.
285 | // Check sites that don't have const operands always,
286 | // because it can be a CRC-like verification, which
287 | // won't be cracked otherwise.
288 | if site.takenFuzz[0] > 10 && site.takenFuzz[1] > 10 || site.takenFuzz[0]+site.takenFuzz[1] > 100 {
289 | skip = true
290 | return
291 | }
292 | }
293 | return
294 | }
295 |
296 | func (sam *SonarSample) evaluate() bool {
297 | v1 := sam.val[0]
298 | v2 := sam.val[1]
299 | if sam.flags&SonarString != 0 {
300 | s1 := string(v1)
301 | s2 := string(v2)
302 | switch sam.flags & SonarOpMask {
303 | case SonarEQL:
304 | return s1 == s2
305 | case SonarNEQ:
306 | return s1 != s2
307 | case SonarLSS:
308 | return s1 < s2
309 | case SonarGTR:
310 | return s1 > s2
311 | case SonarLEQ:
312 | return s1 <= s2
313 | case SonarGEQ:
314 | return s1 >= s2
315 | default:
316 | panic("bad")
317 | }
318 | }
319 | if len(v1) == 0 || len(v2) == 0 || len(v1) > 8 || len(v2) > 8 || len(v1) != len(v2) {
320 | return false
321 | }
322 | v1 = makeCopy(v1)
323 | for len(v1) < 8 {
324 | if int8(v1[len(v1)-1]) >= 0 {
325 | v1 = append(v1, 0)
326 | } else {
327 | v1 = append(v1, 0xff)
328 | }
329 | }
330 | v2 = makeCopy(v2)
331 | for len(v2) < 8 {
332 | if int8(v2[len(v2)-1]) >= 0 {
333 | v2 = append(v2, 0)
334 | } else {
335 | v2 = append(v2, 0xff)
336 | }
337 | }
338 | // Note: assuming le machine.
339 | if sam.flags&SonarSigned == 0 {
340 | s1 := binary.LittleEndian.Uint64(v1)
341 | s2 := binary.LittleEndian.Uint64(v2)
342 | switch sam.flags & SonarOpMask {
343 | case SonarEQL:
344 | return s1 == s2
345 | case SonarNEQ:
346 | return s1 != s2
347 | case SonarLSS:
348 | return s1 < s2
349 | case SonarGTR:
350 | return s1 > s2
351 | case SonarLEQ:
352 | return s1 <= s2
353 | case SonarGEQ:
354 | return s1 >= s2
355 | default:
356 | panic("bad")
357 | }
358 | } else {
359 | s1 := int64(binary.LittleEndian.Uint64(v1))
360 | s2 := int64(binary.LittleEndian.Uint64(v2))
361 | switch sam.flags & SonarOpMask {
362 | case SonarEQL:
363 | return s1 == s2
364 | case SonarNEQ:
365 | return s1 != s2
366 | case SonarLSS:
367 | return s1 < s2
368 | case SonarGTR:
369 | return s1 > s2
370 | case SonarLEQ:
371 | return s1 <= s2
372 | case SonarGEQ:
373 | return s1 >= s2
374 | default:
375 | panic("bad")
376 | }
377 | }
378 | }
379 |
380 | func dumpSonarData(site *SonarSite, flags byte, v1, v2 []byte) {
381 | // Debug output.
382 | op := ""
383 | switch flags & SonarOpMask {
384 | case SonarEQL:
385 | op = "=="
386 | case SonarNEQ:
387 | op = "!="
388 | case SonarLSS:
389 | op = "<"
390 | case SonarGTR:
391 | op = ">"
392 | case SonarLEQ:
393 | op = "<="
394 | case SonarGEQ:
395 | op = ">="
396 | default:
397 | log.Fatalf("bad")
398 | }
399 | sign := ""
400 | if flags&SonarSigned != 0 {
401 | sign = "(signed)"
402 | }
403 | isstr := ""
404 | if flags&SonarString != 0 {
405 | isstr = "(string)"
406 | }
407 | const1 := ""
408 | if flags&SonarConst1 != 0 {
409 | const1 = "c"
410 | }
411 | const2 := ""
412 | if flags&SonarConst2 != 0 {
413 | const2 = "c"
414 | }
415 | log.Printf("SONAR %v%v %v %v%v %v%v %v",
416 | hex.EncodeToString(v1), const1, op, hex.EncodeToString(v2), const2, sign, isstr, site.loc)
417 | }
418 |
--------------------------------------------------------------------------------
/go-fuzz/sys_posix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // +build darwin linux freebsd dragonfly openbsd netbsd
5 |
6 | package main
7 |
8 | import (
9 | "log"
10 | "os"
11 | "os/exec"
12 | "syscall"
13 | )
14 |
15 | func lowerProcessPrio() {
16 | syscall.Setpriority(syscall.PRIO_PROCESS, 0, 19)
17 | }
18 |
19 | type Mapping struct {
20 | f *os.File
21 | }
22 |
23 | func createMapping(name string, size int) (*Mapping, []byte) {
24 | f, err := os.OpenFile(name, os.O_RDWR, 0)
25 | if err != nil {
26 | log.Fatalf("failed to open comm file: %v", err)
27 | }
28 | mem, err := syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
29 | if err != nil {
30 | log.Fatalf("failed to mmap comm file: %v", err)
31 | }
32 | return &Mapping{f}, mem
33 | }
34 |
35 | func (m *Mapping) destroy() {
36 | m.f.Close()
37 | }
38 |
39 | func setupCommMapping(cmd *exec.Cmd, comm *Mapping, rOut, wIn *os.File) {
40 | cmd.ExtraFiles = append(cmd.ExtraFiles, comm.f)
41 | cmd.ExtraFiles = append(cmd.ExtraFiles, rOut)
42 | cmd.ExtraFiles = append(cmd.ExtraFiles, wIn)
43 | }
44 |
--------------------------------------------------------------------------------
/go-fuzz/sys_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "log"
9 | "os"
10 | "os/exec"
11 | "reflect"
12 | "syscall"
13 | "unsafe"
14 | )
15 |
16 | func lowerProcessPrio() {
17 | // TODO: implement me
18 | }
19 |
20 | type Mapping struct {
21 | mapping syscall.Handle
22 | addr uintptr
23 | }
24 |
25 | func createMapping(name string, size int) (*Mapping, []byte) {
26 | f, err := os.OpenFile(name, os.O_RDWR, 0)
27 | if err != nil {
28 | log.Fatalf("failed to open comm file: %v", err)
29 | }
30 | defer f.Close()
31 | mapping, err := syscall.CreateFileMapping(syscall.InvalidHandle, nil, syscall.PAGE_READWRITE, 0, uint32(size), nil)
32 | if err != nil {
33 | log.Fatalf("failed to create file mapping: %v", err)
34 | }
35 | const FILE_MAP_ALL_ACCESS = 0xF001F
36 | addr, err := syscall.MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, uintptr(size))
37 | if err != nil {
38 | log.Fatalf("failed to mmap comm file: %v", err)
39 | }
40 | hdr := reflect.SliceHeader{addr, size, size}
41 | mem := *(*[]byte)(unsafe.Pointer(&hdr))
42 | mem[0] = 1 // test access
43 | return &Mapping{mapping, addr}, mem
44 | }
45 |
46 | func (m *Mapping) destroy() {
47 | syscall.UnmapViewOfFile(m.addr)
48 | syscall.CloseHandle(m.mapping)
49 | }
50 |
51 | func setupCommMapping(cmd *exec.Cmd, comm *Mapping, rOut, wIn *os.File) {
52 | syscall.SetHandleInformation(syscall.Handle(comm.mapping), syscall.HANDLE_FLAG_INHERIT, 1)
53 | syscall.SetHandleInformation(syscall.Handle(rOut.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
54 | syscall.SetHandleInformation(syscall.Handle(wIn.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
55 | cmd.Env = append(cmd.Env, fmt.Sprintf("GO_FUZZ_COMM_FD=%v", comm.mapping))
56 | cmd.Env = append(cmd.Env, fmt.Sprintf("GO_FUZZ_IN_FD=%v", rOut.Fd()))
57 | cmd.Env = append(cmd.Env, fmt.Sprintf("GO_FUZZ_OUT_FD=%v", wIn.Fd()))
58 | }
59 |
--------------------------------------------------------------------------------
/go-fuzz/testee.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "encoding/binary"
8 | "fmt"
9 | "io"
10 | "io/ioutil"
11 | "log"
12 | "os"
13 | "os/exec"
14 | "sync/atomic"
15 | "syscall"
16 | "time"
17 | "unsafe"
18 |
19 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
20 | )
21 |
22 | // Testee is a wrapper around one testee subprocess.
23 | // It manages communication with the testee, timeouts and output collection.
24 | type Testee struct {
25 | coverRegion []byte
26 | inputRegion []byte
27 | sonarRegion []byte
28 | cmd *exec.Cmd
29 | inPipe *os.File
30 | outPipe *os.File
31 | stdoutPipe *os.File
32 | writebuf [9]byte // reusable write buffer
33 | resbuf [24]byte // reusable results buffer
34 | startTime int64
35 | execs int
36 | outputC chan []byte
37 | downC chan bool
38 | down bool
39 | fnidx uint8
40 | }
41 |
42 | // TestBinary handles communication with and restring of testee subprocesses.
43 | type TestBinary struct {
44 | fileName string
45 | commFile string
46 | comm *Mapping
47 | periodicCheck func()
48 |
49 | coverRegion []byte
50 | inputRegion []byte
51 | sonarRegion []byte
52 |
53 | testee *Testee
54 | testeeBuffer []byte // reusable buffer for collecting testee output
55 |
56 | stats *Stats
57 |
58 | fnidx uint8
59 | }
60 |
61 | func init() {
62 | if unsafe.Offsetof(Testee{}.startTime)%8 != 0 {
63 | println(unsafe.Offsetof(Testee{}.startTime))
64 | panic("bad atomic field offset")
65 | }
66 | }
67 |
68 | // testeeBufferSize is how much output a test binary can emit
69 | // before we start to overwrite old output.
70 | const testeeBufferSize = 1 << 20
71 |
72 | func newTestBinary(fileName string, periodicCheck func(), stats *Stats, fnidx uint8) *TestBinary {
73 | comm, err := ioutil.TempFile("", "go-fuzz-comm")
74 | if err != nil {
75 | log.Fatalf("failed to create comm file: %v", err)
76 | }
77 | comm.Truncate(CoverSize + MaxInputSize + SonarRegionSize)
78 | comm.Close()
79 | mapping, mem := createMapping(comm.Name(), CoverSize+MaxInputSize+SonarRegionSize)
80 | return &TestBinary{
81 | fileName: fileName,
82 | commFile: comm.Name(),
83 | comm: mapping,
84 | periodicCheck: periodicCheck,
85 | coverRegion: mem[:CoverSize],
86 | inputRegion: mem[CoverSize : CoverSize+MaxInputSize],
87 | sonarRegion: mem[CoverSize+MaxInputSize:],
88 | stats: stats,
89 | fnidx: fnidx,
90 | testeeBuffer: make([]byte, testeeBufferSize),
91 | }
92 | }
93 |
94 | func (bin *TestBinary) close() {
95 | if bin.testee != nil {
96 | bin.testee.shutdown()
97 | bin.testee = nil
98 | }
99 | bin.comm.destroy()
100 | os.Remove(bin.commFile)
101 | }
102 |
103 | func (bin *TestBinary) test(data []byte) (res int, ns uint64, cover, sonar, output []byte, crashed, hanged bool) {
104 | if len(data) > MaxInputSize {
105 | panic("input is too large")
106 | }
107 | for {
108 | // This is the only function that is executed regularly,
109 | // so we tie some periodic checks to it.
110 | bin.periodicCheck()
111 |
112 | bin.stats.execs++
113 | if bin.testee == nil {
114 | bin.stats.restarts++
115 | bin.testee = newTestee(bin.fileName, bin.comm, bin.coverRegion, bin.inputRegion, bin.sonarRegion, bin.fnidx, bin.testeeBuffer)
116 | }
117 | var retry bool
118 | res, ns, cover, sonar, crashed, hanged, retry = bin.testee.test(data)
119 | if retry {
120 | bin.testee.shutdown()
121 | bin.testee = nil
122 | continue
123 | }
124 | if crashed {
125 | output = bin.testee.shutdown()
126 | if hanged {
127 | hdr := fmt.Sprintf("program hanged (timeout %v seconds)\n\n", *flagTimeout)
128 | output = append([]byte(hdr), output...)
129 | }
130 | bin.testee = nil
131 | return
132 | }
133 | return
134 | }
135 | }
136 |
137 | func newTestee(bin string, comm *Mapping, coverRegion, inputRegion, sonarRegion []byte, fnidx uint8, buffer []byte) *Testee {
138 | retry:
139 | rIn, wIn, err := os.Pipe()
140 | if err != nil {
141 | log.Fatalf("failed to pipe: %v", err)
142 | }
143 | rOut, wOut, err := os.Pipe()
144 | if err != nil {
145 | log.Fatalf("failed to pipe: %v", err)
146 | }
147 | rStdout, wStdout, err := os.Pipe()
148 | if err != nil {
149 | log.Fatalf("failed to pipe: %v", err)
150 | }
151 | cmd := exec.Command(bin)
152 | if *flagTestOutput {
153 | // For debugging of testee failures.
154 | cmd.Stdout = os.Stdout
155 | cmd.Stderr = os.Stdout
156 | } else {
157 | cmd.Stdout = wStdout
158 | cmd.Stderr = wStdout
159 | }
160 | cmd.Env = append([]string{}, os.Environ()...)
161 | cmd.Env = append(cmd.Env, "GOTRACEBACK=1")
162 | setupCommMapping(cmd, comm, rOut, wIn)
163 | if err = cmd.Start(); err != nil {
164 | // This can be a transient failure like "cannot allocate memory" or "text file is busy".
165 | log.Printf("failed to start test binary: %v", err)
166 | rIn.Close()
167 | wIn.Close()
168 | rOut.Close()
169 | wOut.Close()
170 | rStdout.Close()
171 | wStdout.Close()
172 | time.Sleep(time.Second)
173 | goto retry
174 | }
175 | rOut.Close()
176 | wIn.Close()
177 | wStdout.Close()
178 | t := &Testee{
179 | coverRegion: coverRegion,
180 | inputRegion: inputRegion,
181 | sonarRegion: sonarRegion,
182 | cmd: cmd,
183 | inPipe: rIn,
184 | outPipe: wOut,
185 | stdoutPipe: rStdout,
186 | outputC: make(chan []byte),
187 | downC: make(chan bool),
188 | fnidx: fnidx,
189 | }
190 | // Stdout reader goroutine.
191 | go func() {
192 | // The testee should not output unless it crashes.
193 | // But there are still chances that it does. If so, it can overflow
194 | // the stdout pipe during testing and deadlock. To prevent the
195 | // deadlock we periodically read out stdout.
196 | // This goroutine also collects crash output.
197 | ticker := time.NewTicker(time.Second)
198 | data := buffer
199 | filled := 0
200 | for {
201 | select {
202 | case <-ticker.C:
203 | case <-t.downC:
204 | }
205 | n, err := t.stdoutPipe.Read(data[filled:])
206 | if *flagV >= 3 {
207 | log.Printf("testee: %v\n", string(data[filled:filled+n]))
208 | }
209 | filled += n
210 | if filled > testeeBufferSize/4*3 {
211 | copy(data, data[testeeBufferSize/2:filled])
212 | filled -= testeeBufferSize / 2
213 | }
214 | if err != nil {
215 | break
216 | }
217 | }
218 | ticker.Stop()
219 | trimmed := make([]byte, filled)
220 | copy(trimmed, data)
221 | t.outputC <- trimmed
222 | }()
223 | // Hang watcher goroutine.
224 | go func() {
225 | timeout := time.Duration(*flagTimeout) * time.Second
226 | ticker := time.NewTicker(timeout / 2)
227 | for {
228 | select {
229 | case <-ticker.C:
230 | start := atomic.LoadInt64(&t.startTime)
231 | if start != 0 && time.Now().UnixNano()-start > int64(timeout) {
232 | atomic.StoreInt64(&t.startTime, -1)
233 | t.cmd.Process.Signal(syscall.SIGABRT)
234 | time.Sleep(time.Second)
235 | t.cmd.Process.Signal(syscall.SIGKILL)
236 | ticker.Stop()
237 | return
238 | }
239 | case <-t.downC:
240 | ticker.Stop()
241 | return
242 | }
243 |
244 | }
245 | }()
246 | // Shutdown watcher goroutine.
247 | go func() {
248 | select {
249 | case <-t.downC:
250 | case <-shutdownC:
251 | t.cmd.Process.Signal(syscall.SIGKILL)
252 | }
253 | }()
254 | return t
255 | }
256 |
257 | // test passes data for testing.
258 | func (t *Testee) test(data []byte) (res int, ns uint64, cover, sonar []byte, crashed, hanged, retry bool) {
259 | if t.down {
260 | log.Fatalf("cannot test: testee is already shutdown")
261 | }
262 |
263 | // The test binary can accumulate significant amount of memory,
264 | // so we recreate it periodically.
265 | t.execs++
266 | if t.execs > 10000 {
267 | t.cmd.Process.Signal(syscall.SIGKILL)
268 | retry = true
269 | return
270 | }
271 |
272 | copy(t.inputRegion[:], data)
273 | atomic.StoreInt64(&t.startTime, time.Now().UnixNano())
274 | t.writebuf[0] = t.fnidx
275 | binary.LittleEndian.PutUint64(t.writebuf[1:], uint64(len(data)))
276 | if _, err := t.outPipe.Write(t.writebuf[:]); err != nil {
277 | if *flagV >= 1 {
278 | log.Printf("write to testee failed: %v", err)
279 | }
280 | retry = true
281 | return
282 | }
283 | // Once we do the write, the test is running.
284 | // Once we read the reply below, the test is done.
285 | type Reply struct {
286 | Res uint64
287 | Ns uint64
288 | Sonar uint64
289 | }
290 | _, err := io.ReadFull(t.inPipe, t.resbuf[:])
291 | r := Reply{
292 | Res: binary.LittleEndian.Uint64(t.resbuf[:]),
293 | Ns: binary.LittleEndian.Uint64(t.resbuf[8:]),
294 | Sonar: binary.LittleEndian.Uint64(t.resbuf[16:]),
295 | }
296 | hanged = atomic.LoadInt64(&t.startTime) == -1
297 | atomic.StoreInt64(&t.startTime, 0)
298 | if err != nil || hanged {
299 | // Should have been crashed.
300 | crashed = true
301 | return
302 | }
303 | res = int(r.Res)
304 | ns = r.Ns
305 | cover = t.coverRegion
306 | sonar = t.sonarRegion[:r.Sonar]
307 | return
308 | }
309 |
310 | func (t *Testee) shutdown() (output []byte) {
311 | if t.down {
312 | log.Fatalf("cannot shutdown: testee is already shutdown")
313 | }
314 | t.down = true
315 | t.cmd.Process.Kill() // it is probably already dead, but kill it again to be sure
316 | close(t.downC) // wakeup stdout reader
317 | out := <-t.outputC
318 | if err := t.cmd.Wait(); err != nil {
319 | out = append(out, err.Error()...)
320 | }
321 | t.inPipe.Close()
322 | t.outPipe.Close()
323 | t.stdoutPipe.Close()
324 | return out
325 | }
326 |
--------------------------------------------------------------------------------
/go-fuzz/vendor/github.com/elazarl/go-bindata-assetfs/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Elazar Leibovich
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/go-fuzz/vendor/github.com/elazarl/go-bindata-assetfs/README.md:
--------------------------------------------------------------------------------
1 | # go-bindata-assetfs
2 |
3 | Serve embedded files from [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) with `net/http`.
4 |
5 | [GoDoc](http://godoc.org/github.com/elazarl/go-bindata-assetfs)
6 |
7 | ### Installation
8 |
9 | Install with
10 |
11 | $ go get github.com/jteeuwen/go-bindata/...
12 | $ go get github.com/elazarl/go-bindata-assetfs/...
13 |
14 | ### Creating embedded data
15 |
16 | Usage is identical to [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) usage,
17 | instead of running `go-bindata` run `go-bindata-assetfs`.
18 |
19 | The tool will create a `bindata_assetfs.go` file, which contains the embedded data.
20 |
21 | A typical use case is
22 |
23 | $ go-bindata-assetfs data/...
24 |
25 | ### Using assetFS in your code
26 |
27 | The generated file provides an `assetFS()` function that returns a `http.Filesystem`
28 | wrapping the embedded files. What you usually want to do is:
29 |
30 | http.Handle("/", http.FileServer(assetFS()))
31 |
32 | This would run an HTTP server serving the embedded files.
33 |
34 | ## Without running binary tool
35 |
36 | You can always just run the `go-bindata` tool, and then
37 |
38 | use
39 |
40 | import "github.com/elazarl/go-bindata-assetfs"
41 | ...
42 | http.Handle("/",
43 | http.FileServer(
44 | &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "data"}))
45 |
46 | to serve files embedded from the `data` directory.
47 |
--------------------------------------------------------------------------------
/go-fuzz/vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go:
--------------------------------------------------------------------------------
1 | package assetfs
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "io"
7 | "io/ioutil"
8 | "net/http"
9 | "os"
10 | "path"
11 | "path/filepath"
12 | "time"
13 | )
14 |
15 | var (
16 | defaultFileTimestamp = time.Now()
17 | )
18 |
19 | // FakeFile implements os.FileInfo interface for a given path and size
20 | type FakeFile struct {
21 | // Path is the path of this file
22 | Path string
23 | // Dir marks of the path is a directory
24 | Dir bool
25 | // Len is the length of the fake file, zero if it is a directory
26 | Len int64
27 | // Timestamp is the ModTime of this file
28 | Timestamp time.Time
29 | }
30 |
31 | func (f *FakeFile) Name() string {
32 | _, name := filepath.Split(f.Path)
33 | return name
34 | }
35 |
36 | func (f *FakeFile) Mode() os.FileMode {
37 | mode := os.FileMode(0644)
38 | if f.Dir {
39 | return mode | os.ModeDir
40 | }
41 | return mode
42 | }
43 |
44 | func (f *FakeFile) ModTime() time.Time {
45 | return f.Timestamp
46 | }
47 |
48 | func (f *FakeFile) Size() int64 {
49 | return f.Len
50 | }
51 |
52 | func (f *FakeFile) IsDir() bool {
53 | return f.Mode().IsDir()
54 | }
55 |
56 | func (f *FakeFile) Sys() interface{} {
57 | return nil
58 | }
59 |
60 | // AssetFile implements http.File interface for a no-directory file with content
61 | type AssetFile struct {
62 | *bytes.Reader
63 | io.Closer
64 | FakeFile
65 | }
66 |
67 | func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile {
68 | if timestamp.IsZero() {
69 | timestamp = defaultFileTimestamp
70 | }
71 | return &AssetFile{
72 | bytes.NewReader(content),
73 | ioutil.NopCloser(nil),
74 | FakeFile{name, false, int64(len(content)), timestamp}}
75 | }
76 |
77 | func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) {
78 | return nil, errors.New("not a directory")
79 | }
80 |
81 | func (f *AssetFile) Size() int64 {
82 | return f.FakeFile.Size()
83 | }
84 |
85 | func (f *AssetFile) Stat() (os.FileInfo, error) {
86 | return f, nil
87 | }
88 |
89 | // AssetDirectory implements http.File interface for a directory
90 | type AssetDirectory struct {
91 | AssetFile
92 | ChildrenRead int
93 | Children []os.FileInfo
94 | }
95 |
96 | func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory {
97 | fileinfos := make([]os.FileInfo, 0, len(children))
98 | for _, child := range children {
99 | _, err := fs.AssetDir(filepath.Join(name, child))
100 | fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0, time.Time{}})
101 | }
102 | return &AssetDirectory{
103 | AssetFile{
104 | bytes.NewReader(nil),
105 | ioutil.NopCloser(nil),
106 | FakeFile{name, true, 0, time.Time{}},
107 | },
108 | 0,
109 | fileinfos}
110 | }
111 |
112 | func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) {
113 | if count <= 0 {
114 | return f.Children, nil
115 | }
116 | if f.ChildrenRead+count > len(f.Children) {
117 | count = len(f.Children) - f.ChildrenRead
118 | }
119 | rv := f.Children[f.ChildrenRead : f.ChildrenRead+count]
120 | f.ChildrenRead += count
121 | return rv, nil
122 | }
123 |
124 | func (f *AssetDirectory) Stat() (os.FileInfo, error) {
125 | return f, nil
126 | }
127 |
128 | // AssetFS implements http.FileSystem, allowing
129 | // embedded files to be served from net/http package.
130 | type AssetFS struct {
131 | // Asset should return content of file in path if exists
132 | Asset func(path string) ([]byte, error)
133 | // AssetDir should return list of files in the path
134 | AssetDir func(path string) ([]string, error)
135 | // AssetInfo should return the info of file in path if exists
136 | AssetInfo func(path string) (os.FileInfo, error)
137 | // Prefix would be prepended to http requests
138 | Prefix string
139 | }
140 |
141 | func (fs *AssetFS) Open(name string) (http.File, error) {
142 | name = path.Join(fs.Prefix, name)
143 | if len(name) > 0 && name[0] == '/' {
144 | name = name[1:]
145 | }
146 | if b, err := fs.Asset(name); err == nil {
147 | timestamp := defaultFileTimestamp
148 | if info, err := fs.AssetInfo(name); err == nil {
149 | timestamp = info.ModTime()
150 | }
151 | return NewAssetFile(name, b, timestamp), nil
152 | }
153 | if children, err := fs.AssetDir(name); err == nil {
154 | return NewAssetDirectory(name, children, fs), nil
155 | } else {
156 | return nil, err
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/go-fuzz/vendor/github.com/elazarl/go-bindata-assetfs/doc.go:
--------------------------------------------------------------------------------
1 | // assetfs allows packages to serve static content embedded
2 | // with the go-bindata tool with the standard net/http package.
3 | //
4 | // See https://github.com/jteeuwen/go-bindata for more information
5 | // about embedding binary data with go-bindata.
6 | //
7 | // Usage example, after running
8 | // $ go-bindata data/...
9 | // use:
10 | // http.Handle("/",
11 | // http.FileServer(
12 | // &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"}))
13 | package assetfs
14 |
--------------------------------------------------------------------------------
/go-fuzz/vendor/github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "flag"
7 | "fmt"
8 | "os"
9 | "os/exec"
10 | "strings"
11 | )
12 |
13 | const bindatafile = "bindata.go"
14 |
15 | func isDebug(args []string) bool {
16 | flagset := flag.NewFlagSet("", flag.ContinueOnError)
17 | debug := flagset.Bool("debug", false, "")
18 | debugArgs := make([]string, 0)
19 | for _, arg := range args {
20 | if strings.HasPrefix(arg, "-debug") {
21 | debugArgs = append(debugArgs, arg)
22 | }
23 | }
24 | flagset.Parse(debugArgs)
25 | if debug == nil {
26 | return false
27 | }
28 | return *debug
29 | }
30 |
31 | func main() {
32 | if _, err := exec.LookPath("go-bindata"); err != nil {
33 | fmt.Println("Cannot find go-bindata executable in path")
34 | fmt.Println("Maybe you need: go get github.com/elazarl/go-bindata-assetfs/...")
35 | os.Exit(1)
36 | }
37 | cmd := exec.Command("go-bindata", os.Args[1:]...)
38 | cmd.Stdin = os.Stdin
39 | cmd.Stdout = os.Stdout
40 | cmd.Stderr = os.Stderr
41 | if err := cmd.Run(); err != nil {
42 | os.Exit(1)
43 | }
44 | in, err := os.Open(bindatafile)
45 | if err != nil {
46 | fmt.Fprintln(os.Stderr, "Cannot read", bindatafile, err)
47 | return
48 | }
49 | out, err := os.Create("bindata_assetfs.go")
50 | if err != nil {
51 | fmt.Fprintln(os.Stderr, "Cannot write 'bindata_assetfs.go'", err)
52 | return
53 | }
54 | debug := isDebug(os.Args[1:])
55 | r := bufio.NewReader(in)
56 | done := false
57 | for line, isPrefix, err := r.ReadLine(); err == nil; line, isPrefix, err = r.ReadLine() {
58 | if !isPrefix {
59 | line = append(line, '\n')
60 | }
61 | if _, err := out.Write(line); err != nil {
62 | fmt.Fprintln(os.Stderr, "Cannot write to 'bindata_assetfs.go'", err)
63 | return
64 | }
65 | if !done && !isPrefix && bytes.HasPrefix(line, []byte("import (")) {
66 | if debug {
67 | fmt.Fprintln(out, "\t\"net/http\"")
68 | } else {
69 | fmt.Fprintln(out, "\t\"github.com/elazarl/go-bindata-assetfs\"")
70 | }
71 | done = true
72 | }
73 | }
74 | if debug {
75 | fmt.Fprintln(out, `
76 | func assetFS() http.FileSystem {
77 | for k := range _bintree.Children {
78 | return http.Dir(k)
79 | }
80 | panic("unreachable")
81 | }`)
82 | } else {
83 | fmt.Fprintln(out, `
84 | func assetFS() *assetfs.AssetFS {
85 | for k := range _bintree.Children {
86 | return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: k}
87 | }
88 | panic("unreachable")
89 | }`)
90 | }
91 | // Close files BEFORE remove calls (don't use defer).
92 | in.Close()
93 | out.Close()
94 | if err := os.Remove(bindatafile); err != nil {
95 | fmt.Fprintln(os.Stderr, "Cannot remove", bindatafile, err)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/go-fuzz/vendor/github.com/stephens2424/writerset/readme.md:
--------------------------------------------------------------------------------
1 | # writerset
2 | --
3 | import "github.com/stephens2424/writerset"
4 |
5 | Package writerset implements a mechanism to add and remove writers from a
6 | construct similar to io.MultiWriter.
7 |
8 | ## Usage
9 |
10 | #### type WriterSet
11 |
12 | ```go
13 | type WriterSet struct {
14 | }
15 | ```
16 |
17 | WriterSet wraps multiple writers like io.MultiWriter, but such that individual
18 | writers are easy to add or remove as necessary.
19 |
20 | #### func New
21 |
22 | ```go
23 | func New() *WriterSet
24 | ```
25 | New initializes a new empty writer set.
26 |
27 | #### func (*WriterSet) Add
28 |
29 | ```go
30 | func (ws *WriterSet) Add(w io.Writer) <-chan error
31 | ```
32 | Add ensures w is in the set.
33 |
34 | #### func (*WriterSet) Contains
35 |
36 | ```go
37 | func (ws *WriterSet) Contains(w io.Writer) bool
38 | ```
39 | Contains determines if w is in the set.
40 |
41 | #### func (*WriterSet) Flush
42 |
43 | ```go
44 | func (ws *WriterSet) Flush()
45 | ```
46 | Flush implements http.Flusher by calling flush on all the underlying writers if
47 | they are also http.Flushers.
48 |
49 | #### func (*WriterSet) Remove
50 |
51 | ```go
52 | func (ws *WriterSet) Remove(w io.Writer)
53 | ```
54 | Remove ensures w is not in the set.
55 |
56 | #### func (*WriterSet) Write
57 |
58 | ```go
59 | func (ws *WriterSet) Write(b []byte) (int, error)
60 | ```
61 | Write writes data to each underlying writer. If an error occurs on an underlying
62 | writer, that writer is removed from the set. The error will be sent on the
63 | channel created when the writer was added.
64 |
--------------------------------------------------------------------------------
/go-fuzz/vendor/github.com/stephens2424/writerset/writerset.go:
--------------------------------------------------------------------------------
1 | // Package writerset implements a mechanism to add and remove writers from a construct
2 | // similar to io.MultiWriter.
3 | package writerset
4 |
5 | import (
6 | "io"
7 | "net/http"
8 | "sync"
9 | )
10 |
11 | // ErrPartialWrite encapsulates an error from a WriterSet.
12 | type ErrPartialWrite struct {
13 | Writer io.Writer
14 | Err error
15 | Expected, Wrote int
16 | }
17 |
18 | // Error returns the error string from the underlying error.
19 | func (e ErrPartialWrite) Error() string {
20 | return e.Err.Error()
21 | }
22 |
23 | // WriterSet wraps multiple writers like io.MultiWriter, but such that individual
24 | // writers are easy to add or remove as necessary.
25 | type WriterSet struct {
26 | m map[io.Writer]chan error
27 | mu sync.Mutex
28 | }
29 |
30 | // New initializes a new empty writer set.
31 | func New() *WriterSet {
32 | return &WriterSet{
33 | m: make(map[io.Writer]chan error),
34 | mu: sync.Mutex{},
35 | }
36 | }
37 |
38 | // Add ensures w is in the set.
39 | func (ws *WriterSet) Add(w io.Writer) <-chan error {
40 | ws.mu.Lock()
41 | defer ws.mu.Unlock()
42 |
43 | c, ok := ws.m[w]
44 | if ok {
45 | return c
46 | }
47 |
48 | c = make(chan error, 1)
49 | ws.m[w] = c
50 | return c
51 | }
52 |
53 | // Contains determines if w is in the set.
54 | func (ws *WriterSet) Contains(w io.Writer) bool {
55 | ws.mu.Lock()
56 | defer ws.mu.Unlock()
57 |
58 | _, ok := ws.m[w]
59 | return ok
60 | }
61 |
62 | // Remove ensures w is not in the set.
63 | func (ws *WriterSet) Remove(w io.Writer) {
64 | ws.mu.Lock()
65 | defer ws.mu.Unlock()
66 | delete(ws.m, w)
67 | }
68 |
69 | // Write writes data to each underlying writer. If an error occurs on an underlying writer,
70 | // that writer is removed from the set. The error will be wrapped as an ErrPartialWrite and
71 | // sent on the channel created when the writer was added.
72 | func (ws *WriterSet) Write(b []byte) (int, error) {
73 | ws.mu.Lock()
74 | defer ws.mu.Unlock()
75 |
76 | for w, c := range ws.m {
77 | bs, err := w.Write(b)
78 | if err != nil {
79 | c <- ErrPartialWrite{
80 | Err: err,
81 | Wrote: bs,
82 | Expected: len(b),
83 | Writer: w,
84 | }
85 | close(c)
86 | delete(ws.m, w)
87 | }
88 | }
89 |
90 | return len(b), nil
91 | }
92 |
93 | // Flush implements http.Flusher by calling flush on all the underlying writers if they are
94 | // also http.Flushers.
95 | func (ws *WriterSet) Flush() {
96 | ws.mu.Lock()
97 | defer ws.mu.Unlock()
98 |
99 | for w := range ws.m {
100 | if w, ok := w.(http.Flusher); ok {
101 | w.Flush()
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/go-fuzz/versifier/versifier_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package versifier
5 |
6 | import (
7 | "os"
8 | "testing"
9 | )
10 |
11 | func dump(data string) {
12 | v := BuildVerse(nil, []byte(data))
13 | v.Print(os.Stdout)
14 | }
15 |
16 | func TestNumber(t *testing.T) {
17 | dump(`abc -10 def 0xab1 0x123 1e10 asd 1e2 22e-78 -11e72`)
18 | }
19 |
20 | func TestList1(t *testing.T) {
21 | dump(`{"f1": "v1", "f2": "v2", "f3": "v3"}`)
22 | }
23 |
24 | func TestList2(t *testing.T) {
25 | dump(`1,2.0,3e3`)
26 | }
27 |
28 | func TestBracket(t *testing.T) {
29 | dump(`[] [afal] ( ) (afaf)`)
30 | }
31 |
32 | func TestKeyValue(t *testing.T) {
33 | dump(`a=1 a=b 2 (aa=bb) a bb:cc:dd,a=b,c=d,e=f`)
34 | dump(`:a`)
35 | }
36 |
--------------------------------------------------------------------------------
/go-fuzz/worker.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package main
5 |
6 | import (
7 | "archive/zip"
8 | "bufio"
9 | "bytes"
10 | "encoding/json"
11 | "io"
12 | "io/ioutil"
13 | "log"
14 | "math/bits"
15 | "os"
16 | "strings"
17 | "sync/atomic"
18 | "time"
19 | "unsafe"
20 |
21 | . "github.com/dvyukov/go-fuzz/go-fuzz-defs"
22 | . "github.com/dvyukov/go-fuzz/internal/go-fuzz-types"
23 | )
24 |
25 | type execType byte
26 |
27 | //go:generate stringer -type execType -trimprefix exec
28 | const (
29 | execBootstrap execType = iota
30 | execCorpus
31 | execMinimizeInput
32 | execMinimizeCrasher
33 | execTriageInput
34 | execFuzz
35 | execVersifier
36 | execSmash
37 | execSonar
38 | execSonarHint
39 | execTotal
40 | execCount
41 | )
42 |
43 | // Worker manages one testee.
44 | type Worker struct {
45 | id int
46 | hub *Hub
47 | mutator *Mutator
48 |
49 | coverBin *TestBinary
50 | sonarBin *TestBinary
51 |
52 | triageQueue []CoordinatorInput
53 | crasherQueue []NewCrasherArgs
54 |
55 | lastSync time.Time
56 | stats Stats
57 | execs [execCount]uint64
58 | }
59 |
60 | type Input struct {
61 | mine bool
62 | data []byte
63 | cover []byte
64 | coverSize int
65 | res int
66 | depth int
67 | typ execType
68 | execTime uint64
69 | favored bool
70 | score int
71 | runningScoreSum int
72 | }
73 |
74 | func workerMain() {
75 | zipr, err := zip.OpenReader(*flagBin)
76 | if err != nil {
77 | log.Fatalf("failed to open bin file: %v", err)
78 | }
79 | var coverBin, sonarBin string
80 | var metadata MetaData
81 | for _, zipf := range zipr.File {
82 | r, err := zipf.Open()
83 | if err != nil {
84 | log.Fatalf("failed to unzip file from input archive: %v", err)
85 | }
86 | if zipf.Name == "metadata" {
87 | if err := json.NewDecoder(r).Decode(&metadata); err != nil {
88 | log.Fatalf("failed to decode metadata: %v", err)
89 | }
90 | } else {
91 | f, err := ioutil.TempFile("", "go-fuzz")
92 | if err != nil {
93 | log.Fatalf("failed to create temp file: %v", err)
94 | }
95 | f.Close()
96 | os.Remove(f.Name())
97 | f, err = os.OpenFile(f.Name()+".exe", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0700)
98 | if err != nil {
99 | log.Fatalf("failed to create temp file: %v", err)
100 | }
101 | if _, err := io.Copy(f, r); err != nil {
102 | log.Fatalf("failed to uzip bin file: %v", err)
103 | }
104 | f.Close()
105 | switch zipf.Name {
106 | case "cover.exe":
107 | coverBin = f.Name()
108 | case "sonar.exe":
109 | sonarBin = f.Name()
110 | default:
111 | log.Fatalf("unknown file '%v' in input archive", f.Name())
112 | }
113 | }
114 | r.Close()
115 | }
116 | zipr.Close()
117 | if coverBin == "" || sonarBin == "" || len(metadata.Blocks) == 0 || len(metadata.Funcs) == 0 {
118 | log.Fatalf("bad input archive: missing file")
119 | }
120 |
121 | cleanup := func() {
122 | os.Remove(coverBin)
123 | os.Remove(sonarBin)
124 | }
125 |
126 | // Which function should we fuzz?
127 | fnname := *flagFunc
128 | if fnname == "" {
129 | fnname = metadata.DefaultFunc
130 | }
131 | if fnname == "" && len(metadata.Funcs) == 1 {
132 | fnname = metadata.Funcs[0]
133 | }
134 | if fnname == "" {
135 | cleanup()
136 | log.Fatalf("-func flag not provided, but multiple fuzz functions available: %v", strings.Join(metadata.Funcs, ", "))
137 | }
138 | fnidx := -1
139 | for i, n := range metadata.Funcs {
140 | if n == fnname {
141 | fnidx = i
142 | break
143 | }
144 | }
145 | if fnidx == -1 {
146 | cleanup()
147 | log.Fatalf("function %v not found, available functions are: %v", fnname, strings.Join(metadata.Funcs, ", "))
148 | }
149 | if int(uint8(fnidx)) != fnidx {
150 | cleanup()
151 | log.Fatalf("internal consistency error, please file an issue: too many fuzz functions: %v", metadata.Funcs)
152 | }
153 |
154 | shutdownCleanup = append(shutdownCleanup, cleanup)
155 |
156 | hub := newHub(metadata)
157 | for i := 0; i < *flagProcs; i++ {
158 | w := &Worker{
159 | id: i,
160 | hub: hub,
161 | mutator: newMutator(),
162 | }
163 | w.coverBin = newTestBinary(coverBin, w.periodicCheck, &w.stats, uint8(fnidx))
164 | w.sonarBin = newTestBinary(sonarBin, w.periodicCheck, &w.stats, uint8(fnidx))
165 | go w.loop()
166 | }
167 | }
168 |
169 | func (w *Worker) loop() {
170 | iter, fuzzSonarIter, versifierSonarIter := 0, 0, 0
171 | for atomic.LoadUint32(&shutdown) == 0 {
172 | if len(w.crasherQueue) > 0 {
173 | n := len(w.crasherQueue) - 1
174 | crash := w.crasherQueue[n]
175 | w.crasherQueue[n] = NewCrasherArgs{}
176 | w.crasherQueue = w.crasherQueue[:n]
177 | if *flagV >= 2 {
178 | log.Printf("worker %v processes crasher [%v]%v", w.id, len(crash.Data), hash(crash.Data))
179 | }
180 | w.processCrasher(crash)
181 | continue
182 | }
183 |
184 | select {
185 | case input := <-w.hub.triageC:
186 | if *flagV >= 2 {
187 | log.Printf("worker %v triages coordinator input [%v]%v minimized=%v smashed=%v", w.id, len(input.Data), hash(input.Data), input.Minimized, input.Smashed)
188 | }
189 | w.triageInput(input)
190 | for {
191 | x := atomic.LoadUint32(&w.hub.initialTriage)
192 | if x == 0 || atomic.CompareAndSwapUint32(&w.hub.initialTriage, x, x-1) {
193 | break
194 | }
195 | }
196 | continue
197 | default:
198 | }
199 |
200 | if atomic.LoadUint32(&w.hub.initialTriage) != 0 {
201 | // Other workers are still triaging initial inputs.
202 | // Wait until they finish, otherwise we can generate
203 | // as if new interesting inputs that are not actually new
204 | // and thus unnecessary inflate corpus on every run.
205 | time.Sleep(100 * time.Millisecond)
206 | continue
207 | }
208 |
209 | if len(w.triageQueue) > 0 {
210 | n := len(w.triageQueue) - 1
211 | input := w.triageQueue[n]
212 | w.triageQueue[n] = CoordinatorInput{}
213 | w.triageQueue = w.triageQueue[:n]
214 | if *flagV >= 2 {
215 | log.Printf("worker %v triages local input [%v]%v minimized=%v smashed=%v", w.id, len(input.Data), hash(input.Data), input.Minimized, input.Smashed)
216 | }
217 | w.triageInput(input)
218 | continue
219 | }
220 |
221 | ro := w.hub.ro.Load().(*ROData)
222 | if len(ro.corpus) == 0 {
223 | // Some other worker triages corpus inputs.
224 | time.Sleep(100 * time.Millisecond)
225 | continue
226 | }
227 |
228 | // 9 out of 10 iterations are random fuzzing.
229 | iter++
230 | if iter%10 != 0 || ro.verse == nil {
231 | data, depth := w.mutator.generate(ro)
232 | // Every 1000-th iteration goes to sonar.
233 | fuzzSonarIter++
234 | if *flagSonar && fuzzSonarIter%1000 == 0 {
235 | // TODO: ensure that generated hint inputs does not actually take 99% of time.
236 | sonar := w.testInputSonar(data, depth)
237 | w.processSonarData(data, sonar, depth, false)
238 | } else {
239 | // Plain old blind fuzzing.
240 | w.testInput(data, depth, execFuzz)
241 | }
242 | } else {
243 | // 1 out of 10 iterations goes to versifier.
244 | data := ro.verse.Rhyme()
245 | const maxSize = MaxInputSize - 5*SonarMaxLen // need some gap for sonar replacements
246 | if len(data) > maxSize {
247 | data = data[:maxSize]
248 | }
249 | // Every 100-th versifier input goes to sonar.
250 | versifierSonarIter++
251 | if *flagSonar && versifierSonarIter%100 == 0 {
252 | sonar := w.testInputSonar(data, 0)
253 | w.processSonarData(data, sonar, 0, false)
254 | } else {
255 | w.testInput(data, 0, execVersifier)
256 | }
257 | }
258 | }
259 | w.shutdown()
260 | }
261 |
262 | // triageInput processes every new input.
263 | // It calculates per-input metrics like execution time, coverage mask,
264 | // and minimizes the input to the minimal input with the same coverage.
265 | func (w *Worker) triageInput(input CoordinatorInput) {
266 | if len(input.Data) > MaxInputSize {
267 | input.Data = input.Data[:MaxInputSize]
268 | }
269 | inp := Input{
270 | data: input.Data,
271 | depth: int(input.Prio),
272 | typ: input.Type,
273 | execTime: 1 << 60,
274 | }
275 | // Calculate min exec time, min coverage and max result of 3 runs.
276 | for i := 0; i < 3; i++ {
277 | w.execs[execTriageInput]++
278 | res, ns, cover, _, output, crashed, hanged := w.coverBin.test(inp.data)
279 | if crashed {
280 | // Inputs in corpus should not crash.
281 | w.noteCrasher(inp.data, output, hanged)
282 | return
283 | }
284 | if inp.cover == nil {
285 | inp.cover = make([]byte, CoverSize)
286 | copy(inp.cover, cover)
287 | } else {
288 | for i, v := range cover {
289 | x := inp.cover[i]
290 | if v > x {
291 | inp.cover[i] = v
292 | }
293 | }
294 | }
295 | if inp.res < res {
296 | inp.res = res
297 | }
298 | if inp.execTime > ns {
299 | inp.execTime = ns
300 | }
301 | }
302 | if !input.Minimized {
303 | inp.mine = true
304 | ro := w.hub.ro.Load().(*ROData)
305 | // When minimizing new inputs we don't pursue exactly the same coverage,
306 | // instead we pursue just the "novelty" in coverage.
307 | // Here we use corpusCover, because maxCover already includes the input coverage.
308 | newCover, ok := findNewCover(ro.corpusCover, inp.cover)
309 | if !ok {
310 | return // covered by somebody else
311 | }
312 | inp.data = w.minimizeInput(inp.data, false, func(candidate, cover, output []byte, res int, crashed, hanged bool) bool {
313 | if crashed {
314 | w.noteCrasher(candidate, output, hanged)
315 | return false
316 | }
317 | if inp.res != res || worseCover(newCover, cover) {
318 | w.noteNewInput(candidate, cover, res, inp.depth+1, execMinimizeInput)
319 | return false
320 | }
321 | return true
322 | })
323 | } else if !input.Smashed {
324 | w.smash(inp.data, inp.depth)
325 | }
326 | inp.coverSize = 0
327 | for _, v := range inp.cover {
328 | if v != 0 {
329 | inp.coverSize++
330 | }
331 | }
332 | w.hub.newInputC <- inp
333 | }
334 |
335 | // processCrasher minimizes new crashers and sends them to the hub.
336 | func (w *Worker) processCrasher(crash NewCrasherArgs) {
337 | // Hanging inputs can take very long time to minimize.
338 | if !crash.Hanging {
339 | crash.Data = w.minimizeInput(crash.Data, true, func(candidate, cover, output []byte, res int, crashed, hanged bool) bool {
340 | if !crashed {
341 | return false
342 | }
343 | supp := extractSuppression(output)
344 | if hanged || !bytes.Equal(crash.Suppression, supp) {
345 | w.noteCrasher(candidate, output, hanged)
346 | return false
347 | }
348 | crash.Error = output
349 | return true
350 | })
351 | }
352 | w.hub.newCrasherC <- crash
353 | }
354 |
355 | // minimizeInput applies series of minimizing transformations to data
356 | // and asks pred whether the input is equivalent to the original one or not.
357 | func (w *Worker) minimizeInput(data []byte, canonicalize bool, pred func(candidate, cover, output []byte, result int, crashed, hanged bool) bool) []byte {
358 | res := make([]byte, len(data))
359 | copy(res, data)
360 | start := time.Now()
361 | stat := &w.execs[execMinimizeInput]
362 | if canonicalize {
363 | stat = &w.execs[execMinimizeCrasher]
364 | }
365 |
366 | // First, try to cut tail.
367 | for n := 1024; n != 0; n /= 2 {
368 | for len(res) > n {
369 | if time.Since(start) > *flagMinimize {
370 | return res
371 | }
372 | candidate := res[:len(res)-n]
373 | *stat++
374 | result, _, cover, _, output, crashed, hanged := w.coverBin.test(candidate)
375 | if !pred(candidate, cover, output, result, crashed, hanged) {
376 | break
377 | }
378 | res = candidate
379 | }
380 | }
381 |
382 | // Then, try to remove each individual byte.
383 | tmp := make([]byte, len(res))
384 | for i := 0; i < len(res); i++ {
385 | if time.Since(start) > *flagMinimize {
386 | return res
387 | }
388 | candidate := tmp[:len(res)-1]
389 | copy(candidate[:i], res[:i])
390 | copy(candidate[i:], res[i+1:])
391 | *stat++
392 | result, _, cover, _, output, crashed, hanged := w.coverBin.test(candidate)
393 | if !pred(candidate, cover, output, result, crashed, hanged) {
394 | continue
395 | }
396 | res = makeCopy(candidate)
397 | i--
398 | }
399 |
400 | // Then, try to remove each possible subset of bytes.
401 | for i := 0; i < len(res)-1; i++ {
402 | copy(tmp, res[:i])
403 | for j := len(res); j > i+1; j-- {
404 | if time.Since(start) > *flagMinimize {
405 | return res
406 | }
407 | candidate := tmp[:len(res)-j+i]
408 | copy(candidate[i:], res[j:])
409 | *stat++
410 | result, _, cover, _, output, crashed, hanged := w.coverBin.test(candidate)
411 | if !pred(candidate, cover, output, result, crashed, hanged) {
412 | continue
413 | }
414 | res = makeCopy(candidate)
415 | j = len(res)
416 | }
417 | }
418 |
419 | // Then, try to replace each individual byte with '0'.
420 | if canonicalize {
421 | for i := 0; i < len(res); i++ {
422 | if res[i] == '0' {
423 | continue
424 | }
425 | if time.Since(start) > *flagMinimize {
426 | return res
427 | }
428 | candidate := tmp[:len(res)]
429 | copy(candidate, res)
430 | candidate[i] = '0'
431 | *stat++
432 | result, _, cover, _, output, crashed, hanged := w.coverBin.test(candidate)
433 | if !pred(candidate, cover, output, result, crashed, hanged) {
434 | continue
435 | }
436 | res = makeCopy(candidate)
437 | }
438 | }
439 |
440 | return res
441 | }
442 |
443 | // smash gives some minimal attention to every new input.
444 | func (w *Worker) smash(data []byte, depth int) {
445 | ro := w.hub.ro.Load().(*ROData)
446 |
447 | // Pass it through sonar.
448 | if *flagSonar {
449 | sonar := w.testInputSonar(data, depth)
450 | w.processSonarData(data, sonar, depth, true)
451 | }
452 |
453 | // Flip each bit one-by-one.
454 | for i := 0; i < len(data)*8; i++ {
455 | data[i/8] ^= 1 << uint(i%8)
456 | w.testInput(data, depth, execSmash)
457 | data[i/8] ^= 1 << uint(i%8)
458 | }
459 |
460 | // Two walking bits.
461 | for i := 0; i < len(data)*8-1; i++ {
462 | data[i/8] ^= 1 << uint(i%8)
463 | data[(i+1)/8] ^= 1 << uint((i+1)%8)
464 | w.testInput(data, depth, execSmash)
465 | data[i/8] ^= 1 << uint(i%8)
466 | data[(i+1)/8] ^= 1 << uint((i+1)%8)
467 | }
468 |
469 | // Four walking bits.
470 | for i := 0; i < len(data)*8-3; i++ {
471 | data[i/8] ^= 1 << uint(i%8)
472 | data[(i+1)/8] ^= 1 << uint((i+1)%8)
473 | data[(i+2)/8] ^= 1 << uint((i+2)%8)
474 | data[(i+3)/8] ^= 1 << uint((i+3)%8)
475 | w.testInput(data, depth, execSmash)
476 | data[i/8] ^= 1 << uint(i%8)
477 | data[(i+1)/8] ^= 1 << uint((i+1)%8)
478 | data[(i+2)/8] ^= 1 << uint((i+2)%8)
479 | data[(i+3)/8] ^= 1 << uint((i+3)%8)
480 | }
481 |
482 | // Byte flip.
483 | for i := 0; i < len(data); i++ {
484 | data[i] ^= 0xff
485 | w.testInput(data, depth, execSmash)
486 | data[i] ^= 0xff
487 | }
488 |
489 | // Two walking bytes.
490 | for i := 0; i < len(data)-1; i++ {
491 | data[i] ^= 0xff
492 | data[i+1] ^= 0xff
493 | w.testInput(data, depth, execSmash)
494 | data[i] ^= 0xff
495 | data[i+1] ^= 0xff
496 | }
497 |
498 | // Four walking bytes.
499 | for i := 0; i < len(data)-3; i++ {
500 | data[i] ^= 0xff
501 | data[i+1] ^= 0xff
502 | data[i+2] ^= 0xff
503 | data[i+3] ^= 0xff
504 | w.testInput(data, depth, execSmash)
505 | data[i] ^= 0xff
506 | data[i+1] ^= 0xff
507 | data[i+2] ^= 0xff
508 | data[i+3] ^= 0xff
509 | }
510 |
511 | // Increment/decrement every byte.
512 | for i := 0; i < len(data); i++ {
513 | for j := uint8(1); j <= 4; j++ {
514 | data[i] += j
515 | w.testInput(data, depth, execSmash)
516 | data[i] -= j
517 | data[i] -= j
518 | w.testInput(data, depth, execSmash)
519 | data[i] += j
520 | }
521 | }
522 |
523 | // Set bytes to interesting values.
524 | for i := 0; i < len(data); i++ {
525 | v := data[i]
526 | for _, x := range interesting8 {
527 | data[i] = uint8(x)
528 | w.testInput(data, depth, execSmash)
529 | }
530 | data[i] = v
531 | }
532 |
533 | // Set words to interesting values.
534 | for i := 0; i < len(data)-1; i++ {
535 | p := (*int16)(unsafe.Pointer(&data[i]))
536 | v := *p
537 | for _, x := range interesting16 {
538 | *p = x
539 | w.testInput(data, depth, execSmash)
540 | if x != 0 && x != -1 {
541 | *p = int16(bits.ReverseBytes16(uint16(x)))
542 | w.testInput(data, depth, execSmash)
543 | }
544 | }
545 | *p = v
546 | }
547 |
548 | // Set double-words to interesting values.
549 | for i := 0; i < len(data)-3; i++ {
550 | p := (*int32)(unsafe.Pointer(&data[i]))
551 | v := *p
552 | for _, x := range interesting32 {
553 | *p = x
554 | w.testInput(data, depth, execSmash)
555 | if x != 0 && x != -1 {
556 | *p = int32(bits.ReverseBytes32(uint32(x)))
557 | w.testInput(data, depth, execSmash)
558 | }
559 | }
560 | *p = v
561 | }
562 |
563 | // Trim after every byte.
564 | for i := 1; i < len(data); i++ {
565 | tmp := data[:i]
566 | w.testInput(tmp, depth, execSmash)
567 | }
568 |
569 | // Insert a byte after every byte.
570 | tmp := make([]byte, len(data)+1)
571 | if len(tmp) > MaxInputSize {
572 | tmp = tmp[:MaxInputSize]
573 | }
574 | for i := 0; i <= len(data) && i < MaxInputSize-1; i++ {
575 | copy(tmp, data[:i])
576 | copy(tmp[i+1:], data[i:])
577 | tmp[i] = 0
578 | w.testInput(tmp, depth, execSmash)
579 | tmp[i] = 'a'
580 | w.testInput(tmp, depth, execSmash)
581 | }
582 |
583 | // Do a bunch of random mutations so that this input catches up with the rest.
584 | for i := 0; i < 1e4; i++ {
585 | tmp := w.mutator.mutate(data, ro)
586 | w.testInput(tmp, depth+1, execFuzz)
587 | }
588 | }
589 |
590 | func (w *Worker) testInput(data []byte, depth int, typ execType) {
591 | w.testInputImpl(w.coverBin, data, depth, typ)
592 | }
593 |
594 | func (w *Worker) testInputSonar(data []byte, depth int) (sonar []byte) {
595 | return w.testInputImpl(w.sonarBin, data, depth, execSonar)
596 | }
597 |
598 | func (w *Worker) testInputImpl(bin *TestBinary, data []byte, depth int, typ execType) (sonar []byte) {
599 | ro := w.hub.ro.Load().(*ROData)
600 | if len(ro.badInputs) > 0 {
601 | if _, ok := ro.badInputs[hash(data)]; ok {
602 | return nil // no, thanks
603 | }
604 | }
605 | w.execs[typ]++
606 | res, _, cover, sonar, output, crashed, hanged := bin.test(data)
607 | if crashed {
608 | w.noteCrasher(data, output, hanged)
609 | return nil
610 | }
611 | w.noteNewInput(data, cover, res, depth, typ)
612 | return sonar
613 | }
614 |
615 | func (w *Worker) noteNewInput(data, cover []byte, res, depth int, typ execType) {
616 | if res < 0 {
617 | // User said to not add this input to corpus.
618 | return
619 | }
620 | if w.hub.updateMaxCover(cover) {
621 | w.triageQueue = append(w.triageQueue, CoordinatorInput{makeCopy(data), uint64(depth), typ, false, false})
622 | }
623 | }
624 |
625 | func (w *Worker) noteCrasher(data, output []byte, hanged bool) {
626 | ro := w.hub.ro.Load().(*ROData)
627 | supp := extractSuppression(output)
628 | if _, ok := ro.suppressions[hash(supp)]; ok {
629 | return
630 | }
631 | w.crasherQueue = append(w.crasherQueue, NewCrasherArgs{
632 | Data: makeCopy(data),
633 | Error: output,
634 | Suppression: supp,
635 | Hanging: hanged,
636 | })
637 | }
638 |
639 | func (w *Worker) periodicCheck() {
640 | if atomic.LoadUint32(&shutdown) != 0 {
641 | w.shutdown()
642 | select {}
643 | }
644 | if time.Since(w.lastSync) < syncPeriod {
645 | return
646 | }
647 | w.execs[execTotal] += w.stats.execs
648 | w.lastSync = time.Now()
649 | w.hub.syncC <- w.stats
650 | w.stats.execs = 0
651 | w.stats.restarts = 0
652 | if *flagV >= 2 {
653 | log.Printf("worker %v: triageq=%v execs=%v mininp=%v mincrash=%v triage=%v fuzz=%v versifier=%v smash=%v sonar=%v hint=%v",
654 | w.id, len(w.triageQueue),
655 | w.execs[execTotal], w.execs[execMinimizeInput], w.execs[execMinimizeCrasher],
656 | w.execs[execTriageInput], w.execs[execFuzz], w.execs[execVersifier], w.execs[execSmash],
657 | w.execs[execSonar], w.execs[execSonarHint])
658 | }
659 | }
660 |
661 | // shutdown cleanups after worker, it is not guaranteed to be called.
662 | func (w *Worker) shutdown() {
663 | w.coverBin.close()
664 | w.sonarBin.close()
665 | }
666 |
667 | func extractSuppression(out []byte) []byte {
668 | var supp []byte
669 | seenPanic := false
670 | collect := false
671 | s := bufio.NewScanner(bytes.NewReader(out))
672 | for s.Scan() {
673 | line := s.Text()
674 | if !seenPanic && (strings.HasPrefix(line, "panic: ") ||
675 | strings.HasPrefix(line, "fatal error: ") ||
676 | strings.HasPrefix(line, "SIG") && strings.Index(line, ": ") != 0) {
677 | // Start of a crash message.
678 | seenPanic = true
679 | supp = append(supp, line...)
680 | supp = append(supp, '\n')
681 | if line == "SIGABRT: abort" || line == "signal: killed" {
682 | return supp // timeout stacks are flaky
683 | }
684 | }
685 | if collect && line == "runtime stack:" {
686 | // Skip runtime stack.
687 | // Unless it is a runtime bug, user stack is more descriptive.
688 | collect = false
689 | }
690 | if collect && len(line) > 0 && (line[0] >= 'a' && line[0] <= 'z' ||
691 | line[0] >= 'A' && line[0] <= 'Z') {
692 | // Function name line.
693 | idx := strings.LastIndex(line, "(")
694 | if idx != -1 {
695 | supp = append(supp, line[:idx]...)
696 | supp = append(supp, '\n')
697 | }
698 | }
699 | if collect && line == "" {
700 | // End of first goroutine stack.
701 | break
702 | }
703 | if seenPanic && !collect && line == "" {
704 | // Start of first goroutine stack.
705 | collect = true
706 | }
707 | }
708 | if len(supp) == 0 {
709 | supp = out
710 | }
711 | return supp
712 | }
713 |
714 | func reverse(data []byte) []byte {
715 | tmp := make([]byte, len(data))
716 | for i, v := range data {
717 | tmp[len(data)-i-1] = v
718 | }
719 | return tmp
720 | }
721 |
722 | func increment(data []byte) []byte {
723 | tmp := make([]byte, len(data))
724 | copy(tmp, data)
725 | for i, v := range data {
726 | tmp[i] = v + 1
727 | if v != 0xff {
728 | break
729 | }
730 | }
731 | return tmp
732 | }
733 |
734 | func decrement(data []byte) []byte {
735 | tmp := make([]byte, len(data))
736 | copy(tmp, data)
737 | for i, v := range data {
738 | tmp[i] = v - 1
739 | if v != 0 {
740 | break
741 | }
742 | }
743 | return tmp
744 | }
745 |
--------------------------------------------------------------------------------
/go-fuzz/worker_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "math"
6 | "testing"
7 | )
8 |
9 | func TestIncrementDecrement(t *testing.T) {
10 | b := make([]byte, 2)
11 | for i := 0; i < math.MaxUint16; i++ {
12 | u := uint16(i)
13 | binary.LittleEndian.PutUint16(b, u)
14 |
15 | b1 := increment(b)
16 | u1 := binary.LittleEndian.Uint16(b1)
17 | if u+1 != u1 {
18 | t.Fatalf("increment(%d) = %d, want %d", u, u1, u+1)
19 | }
20 |
21 | b1 = decrement(b)
22 | u1 = binary.LittleEndian.Uint16(b1)
23 | if u-1 != u1 {
24 | t.Fatalf("decrement(%d) = %d, want %d", u, u1, u-1)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/internal/go-fuzz-types/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // Package types provides types shared between go-fuzz-build and go-fuzz.
5 | package types
6 |
7 | type CoverBlock struct {
8 | ID int
9 | File string
10 | StartLine int
11 | StartCol int
12 | EndLine int
13 | EndCol int
14 | NumStmt int
15 | }
16 |
17 | type Literal struct {
18 | Val string
19 | IsStr bool
20 | }
21 |
22 | type MetaData struct {
23 | Literals []Literal
24 | Blocks []CoverBlock
25 | Sonar []CoverBlock
26 | Funcs []string // fuzz function names; must have length > 0
27 | DefaultFunc string // default function to fuzz
28 | }
29 |
--------------------------------------------------------------------------------
/slides/README.md:
--------------------------------------------------------------------------------
1 | # go-fuzz presentations
2 |
3 | - [go-fuzz: randomized testing system for Go](http://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/go-fuzz.slide) (extended version of GopherCon 2015 talk)
4 | - [Fuzzing: the new unit testing](http://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/fuzzing.slide) (GopherCon Russia 2018)
5 |
--------------------------------------------------------------------------------
/slides/algo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvyukov/go-fuzz/e577bee5275c42f9a3ff598ade77fe9806191345/slides/algo.png
--------------------------------------------------------------------------------
/slides/bugs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvyukov/go-fuzz/e577bee5275c42f9a3ff598ade77fe9806191345/slides/bugs.png
--------------------------------------------------------------------------------
/slides/crash.go:
--------------------------------------------------------------------------------
1 | // Prevent go install ./... from complaining about different packages in the same dir.
2 | // +build
3 |
4 | package n
5 |
6 | type R interface{ S }
7 | type S = interface{ R }
8 |
--------------------------------------------------------------------------------
/slides/fuzzing.slide:
--------------------------------------------------------------------------------
1 | Fuzzing
2 | the new unit testing
3 | 17 Mar 2018
4 |
5 | Dmitry Vyukov
6 | Bug Slaughterer, Google
7 | @dvyukov
8 |
9 | * Agenda
10 |
11 | - What is fuzzing?
12 | - Coverage-guided fuzzing
13 | - go-fuzz tutorial
14 | - Effective fuzzers
15 |
16 | * Wikipedia
17 |
18 | *Fuzzing* is a software *testing*technique*, often automated or semi-automated,
19 | that involves providing *invalid*, *unexpected*, or *random* data to the inputs
20 | of a computer program.
21 |
22 | * A brief history of fuzzing
23 |
24 | *1950s*:
25 |
26 | We didn't call it fuzzing back in the 1950s, but it was our standard practice
27 | to test programs by inputting decks of punch cards taken from the trash.
28 | This type of testing was so common that it had no name.
29 |
30 | [[http://secretsofconsulting.blogspot.de/2017/02/fuzz-testing-and-fuzz-history.html][_Gerald_M._Weinberg_]]
31 |
32 | : Some called it monkey testing or random_testing
33 |
34 | *1988*: term *fuzzing* is coined by Barton Miller
35 |
36 | *1991*: *crashme*: generate random bytes and jump to them
37 |
38 | *2012*: ClusterFuzz
39 |
40 | *2014-2015*: AFL, libFuzzer, go-fuzz
41 |
42 | *2016*: Project Springfield, OSS-Fuzz
43 |
44 | * What can be fuzzed?
45 |
46 | Anything that consumes complex inputs:
47 |
48 | - deserialization (xml, json, gob, asn.1)
49 | - network protocols (HTTP, SMTP, MIME)
50 | - media codecs (audio, video, raster & vector images, pdf)
51 | - crypto (boringssl, openssl)
52 | - compression (zip, gzip, bzip2, brotli)
53 | - text/UTF/font processing (fmt, regexp, text/template, icu, truetype)
54 | - compilers, interpreters, databases, kernels, supervisors, etc
55 |
56 | *Must*have* for everything that consumes untrusted inputs, open to internet or
57 | is otherwise *security* sensitive.
58 |
59 | * Why fuzz?
60 |
61 | - more code coverage
62 | - no bias
63 | - cheap to use
64 |
65 | Complementary to unit-testing!
66 |
67 | * But who cares?
68 |
69 | - We are not testing/checking anything!
70 | - Random data will not trigger any bugs!
71 |
72 | * Types of bugs fuzzing can find
73 |
74 | - nil derefs
75 | - out-of-bounds accesses
76 | - division by 0
77 | - floating-point exceptions
78 | - huge slice allocation requests
79 | - infinite recursion
80 | - infinite loops
81 | - deadlocks
82 | - data races
83 | - send on closed chans
84 | - chan double close
85 | - logical bugs (asserts, but stay tuned for more)
86 |
87 | * Types of fuzzers
88 |
89 | - generation-based or mutation-based
90 | - aware of input structure or not
91 | - aware of program structure or not
92 |
93 | : Mutation-based generally require a pre-collected corpus of inputs.
94 | : Input structure can be provided or learned.
95 | : Program structure can be learned dynamically (code coverage)
96 | : or statically (code analysis) or via symbolic execution.
97 |
98 | * Coverage-guided fuzzing
99 |
100 | Instrument program for code coverage
101 | Collect initial corpus of inputs
102 | for {
103 | Randomly mutate an input from the corpus
104 | Execute and collect coverage
105 | if the input gives new coverage {
106 | Add the input to corpus
107 | }
108 | }
109 |
110 | * Coverage-guiding in action
111 |
112 | The following code wants "ABCD" input:
113 |
114 | if input[0] == 'A' {
115 | if input[1] == 'B' {
116 | if input[2] == 'C' {
117 | if input[3] == 'D' {
118 | slice[input[4]] = 1 // out-of-bounds here
119 | }}}}
120 |
121 | Blind generation needs O(2^8^4) = O(2^32) tries.
122 |
123 | Corpus progression:
124 |
125 | 0. {}
126 | 1. {"A"}
127 | 2. {"A", "AB"}
128 | 3. {"A", "AB", "ABC"}
129 | 4. {"A", "AB", "ABC", "ABCD"}
130 |
131 | Coverage-guided fuzzer needs O(4 * 2^8) = O(2^10) tries.
132 |
133 | : Coverage-guiding turns exponential problem into polynomial.
134 | : + incremental progress
135 | : + persisted progress
136 |
137 | * go-fuzz tutorial
138 |
139 | * go-fuzz tutorial
140 |
141 | 1. Write fuzz function:
142 |
143 | func Fuzz(data []byte) int {
144 | gob.NewDecoder(bytes.NewReader(data)).Decode(new(interface{}))
145 | return 0
146 | }
147 |
148 | 2. Build:
149 |
150 | go get github.com/dvyukov/go-fuzz/...
151 | go-fuzz-build github.com/dvyukov/go-fuzz-corpus/gob
152 |
153 | 3. Run:
154 |
155 | go-fuzz -bin gob-fuzz.zip -workdir ./workdir
156 |
157 | workers: 8, corpus: 1525 (6s ago), crashers: 6, execs: 0 (0/sec), cover: 1651, uptime: 6s
158 | workers: 8, corpus: 1525 (9s ago), crashers: 6, execs: 16787 (1860/sec), cover: 1651, uptime: 9s
159 | workers: 8, corpus: 1525 (12s ago), crashers: 6, execs: 29840 (2482/sec), cover: 1651, uptime: 12s
160 |
161 | * [Some] bugs in standard library
162 |
163 | .image bugs.png 550 _
164 |
165 | * Quick Quiz: what std lib package did not contain a bug?
166 |
167 | - compress/flate
168 | - fmt
169 | - go/format
170 | - html/template
171 | - image/bmp
172 | - image/png
173 | - time
174 |
175 | * [This slide intentionally left blank]
176 |
177 | * Quick quiz: what std lib package did not contain a bug?
178 |
179 | - compress/flate
180 | - fmt
181 | - go/format
182 | - html/template
183 | - *image/bmp* _(_because_it_does_not_exist_)_
184 | - image/png
185 | - time
186 |
187 | * encoding/gob DoS
188 |
189 | .play gob.go /START OMIT/,/END OMIT/
190 |
191 | * encoding/gob crash
192 |
193 | fatal error: runtime: out of memory
194 |
195 | goroutine 1 [running]:
196 | runtime.mallocgc(0x3740000000, ...)
197 | runtime/malloc.go:895
198 | runtime.newarray(..., 0x44000000, ...)
199 | runtime/malloc.go:1030
200 | reflect.MakeMapWithSize(..., 0xff003030, ...)
201 | reflect/value.go:2136
202 | encoding/gob.(*Decoder).decodeMap(...)
203 | encoding/gob/decode.go:562
204 | encoding/gob.(*Decoder).decodeStruct(...)
205 | encoding/gob/decode.go:471
206 | encoding/gob.(*Decoder).Decode(...)
207 | encoding/gob/decoder.go:187
208 | main.main()
209 | gob.go:26
210 |
211 | * Go compiler crash
212 |
213 | .play crash.go
214 |
215 | panic: runtime error: invalid memory address or nil pointer dereference
216 |
217 | goroutine 1 [running]:
218 | cmd/compile/internal/gc.dowidth()
219 | cmd/compile/internal/types.(*Type).Fields()
220 | cmd/compile/internal/gc.expandiface()
221 | cmd/compile/internal/gc.dowidth()
222 | cmd/compile/internal/types.(*Type).Fields()
223 | cmd/compile/internal/gc.expandiface()
224 | cmd/compile/internal/gc.dowidth()
225 | cmd/compile/internal/gc.resumecheckwidth()
226 | cmd/compile/internal/gc.Main()
227 | main.main()
228 |
229 | * How to write effective fuzzers
230 |
231 | * Involve as much code as possble
232 |
233 | The more code fuzzer runs, the more bugs it finds:
234 |
235 | - just bugs in the additional code
236 | - additional code provides coverage-guidance help
237 |
238 | For example:
239 |
240 | - run code in different modes (compression levels, syntax variations)
241 | - run auxiliary functions (code formatting, protobuf text serialization)
242 | - run functions that act on parsing result (regexp match)
243 |
244 | * regexp example
245 |
246 | for _, ctor := range []func(string) (*regexp.Regexp, error){
247 | regexp.Compile,
248 | regexp.CompilePOSIX,
249 | CompileLongest,
250 | CompilePOSIXLongest
251 | } {
252 | re, err := ctor(restr)
253 | if err != nil { continue }
254 | re.LiteralPrefix()
255 | re.SubexpNames()
256 | re.NumSubexp()
257 | re.Split(sstr, 1)
258 | re.Split(sstr, -1)
259 | re.FindAll(str, 1)
260 | re.FindAll(str, -1)
261 | re.FindAllSubmatch(str, 1)
262 | re.FindAllSubmatch(str, -1)
263 | str1, str2 := str[:len(str)/2], str[len(str)/2:]
264 | match := re.FindSubmatchIndex(str1)
265 | re.Expand(nil, str2, str1, match)
266 | re.ReplaceAll(str1, str2)
267 | re.ReplaceAllLiteral(str1, str2)
268 | }
269 |
270 | * Logical bugs
271 |
272 | Not only security/stability bugs. But we don't know the right result!
273 |
274 | Sanity checks on results:
275 |
276 | - know that some substring must be present in output, but it is not
277 | - result must be in `[0.0, 1.0)` range
278 | - uncompressed image decoder: 100 byte input -> 100 MB output?
279 | - encrypt, check that decryption with wrong key fails
280 | - function returns both error and object, or no error and no object
281 | - any sorting: check that each element is present, check that it's not descending
282 | - building a trie: check size, all elements are present
283 |
284 | * Round-trip test
285 |
286 | Pair functions like:
287 |
288 | - serialize <-> deserialize
289 | - compress <-> decompress
290 | - encrypt <-> decrypt
291 |
292 | Round-trip is:
293 |
294 | deserialize -> serialize -> deserialize
295 |
296 | Can check that:
297 |
298 | - `serialize` does not fail
299 | - second `deserialize` does not fail
300 | - `deserialize` results are equal
301 |
302 | Finds tons of bugs!
303 |
304 | * Golden implementation
305 |
306 | Comparing two (or more) implementations gives phenomenal results:
307 |
308 | - check that output is equal
309 | - or at least check that ok/fail result is the same
310 |
311 | But I don't want to write the second impl!
312 |
313 | - there can be several packages already (`json` vs `jsonfast` vs `jsonveryfast`)
314 | - implementation in a different language (Go's `regexp` vs C++ `re2`)
315 | - compare "fast but complex" with "slow but dumb" (sometimes easy to write)
316 | - compare different functions (`marshalBinary` vs `marshalText`)
317 |
318 | * Corpus
319 |
320 | Providing a good corpus helps:
321 |
322 | - collect inputs from tests
323 | - samples from production
324 | - examples from web (HTTP/XML)
325 | - hand write
326 | - write a generator (PNGs with different sizes/contents/palettes)
327 |
328 | * Complex inputs
329 |
330 | What if input is not `[]byte`?
331 |
332 | .code regexp.go /START OMIT/,/END OMIT/
333 |
334 | * Complex inputs (2)
335 |
336 | Unmarshalling a complex struct:
337 |
338 | var input struct {
339 | X float32
340 | Y float32
341 | Z int64
342 | F bool
343 | }
344 | binary.Read(bytes.NewReader([]byte(data)), binary.LittleEndian, &input)
345 |
346 | Without changing input format:
347 |
348 | x := crc32.ChecksumIEEE(data)
349 | f1 := x&1 != 0
350 | f2 := x&2 != 0
351 |
352 | * Quick Quiz: how to fuzz go fmt?
353 |
354 | Let's imaging destiny of mankind depends on correctness of go fmt!
355 |
356 | How would you fuzz test it?
357 |
358 | * [This slide intentionally left blank]
359 |
360 | * Quick Quiz: how to fuzz go fmt?
361 |
362 | - format twice, compare results (e.g. relies on map order)
363 | - format, then format result (must be idempotent)
364 | - strip all whitespaces, compare before/after
365 | - parse with go/ast, compare AST before/after
366 | - compile before/after (formatting breaks/unbreaks code)
367 | - also parse with go/types to improve coverage feedback
368 | - provide corpus (compiler tests)
369 |
370 | * go tool support
371 |
372 | Issue #19109: proposal: cmd/go: make fuzzing a first class citizen
373 |
374 | encoding/hex/fuzz_test.go:
375 |
376 | package hex
377 |
378 | import "testing"
379 |
380 | func FuzzDecodeString(f *testing.F, data []byte) {
381 | DecodeString(string(data))
382 | }
383 |
384 | Running:
385 |
386 | go test -fuzz=FuzzDecodeString
387 |
388 | Regression testing (runs all inputs from corpus):
389 |
390 | go test ./...
391 |
392 | * The bottom line
393 |
394 | - fuzzing is complimentary to any other testing technique
395 | - fuzzing is mandatory for anything security-related
396 | - fuzzing finds LOTS of bugs
397 | - fuzzing is easy to use
398 |
399 | * Call to action
400 |
401 | - choose 1 package that uses complex inputs
402 | - write a fuzzer using go-fuzz
403 | - run it and see what happens
404 |
--------------------------------------------------------------------------------
/slides/go-fuzz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dvyukov/go-fuzz/e577bee5275c42f9a3ff598ade77fe9806191345/slides/go-fuzz.png
--------------------------------------------------------------------------------
/slides/go-fuzz.slide:
--------------------------------------------------------------------------------
1 | go-fuzz
2 | Randomized testing system for Go
3 |
4 | Dmitry Vyukov
5 | Google
6 | dvyukov@
7 |
8 | * Agenda
9 |
10 | - High-level architecture
11 | - Driver/program interface
12 | - Coverage
13 | - Mutations
14 | - Corpus management
15 | - Sonar
16 | - Versifier
17 |
18 | .image go-fuzz.png
19 |
20 | * Introduction
21 |
22 | A different appraoch to testing that finds [lots of] bugs that other testing approaches do not. Intended mostly for programs that parse complex inputs.
23 |
24 | Generate random blob -> feed into program -> see if it crashes -> profit!
25 |
26 | - cheap to use
27 | - does not have any bias
28 |
29 | Completely random blobs won't uncover lots of bugs.
30 |
31 | How can we generate diverse but meaningful inputs that will trigger
32 | nil derefs, off-by-ones, etc?
33 |
34 | * Coverage-guided fuzzing
35 |
36 | Genetic algorithms to the rescue!
37 |
38 | Instrument program for code coverage
39 | Collect initial corpus of inputs
40 | for {
41 | Randomly mutate an input from the corpus
42 | Execute and collect coverage
43 | If the input gives new coverage, add it to corpus
44 | }
45 |
46 | * Example
47 |
48 | The following code wants "ABCD" input:
49 |
50 | if input[0] == 'A' {
51 | if input[1] == 'B' {
52 | if input[2] == 'C' {
53 | if input[3] == 'D' {
54 | panic("input must not be ABCD")
55 | }
56 | }
57 | }
58 | }
59 |
60 | Corpus progression:
61 |
62 | ""
63 | "", "A"
64 | "", "A", "AB"
65 | "", "A", "AB", "ABC"
66 | "", "A", "AB", "ABC", "ABCD"
67 |
68 | * High-level architecture
69 |
70 | - Coordinator: manages persistent artifacts
71 | - Hub: manages corpus in memory
72 | - Worker: manages a single testee subprocess
73 | - N workers are collocated with Hub in a single process.
74 | - Coordinator and Hub communicate via RPC
75 | - go-fuzz-build builds test binary
76 |
77 | * go-fuzz-build
78 |
79 | Creates a zip archive with:
80 |
81 | - coverage instrumented binary
82 | - sonar instrumented binary
83 | - coverage and sonar metadata
84 | - int and string literals
85 |
86 | Also generates a bit of code to glue test and driver.
87 |
88 | * Coordinator process
89 |
90 | - Persistent corpus (SHA-1 + input depth)
91 | - Persistent suppressions (SHA-1)
92 | - Persistent crashers (input, quoted input, output)
93 | - Final deduplication
94 | - Total stats
95 |
96 | * Hub/worker process
97 |
98 | - Does actual fuzzing
99 |
100 | * Driver/program interface
101 |
102 | - Input shared region (fd 3)
103 | - Coverage shared region (fixed size, 64K) (fd 4)
104 | - Output sonar data shared region (fd 5)
105 | - Input pipe: (input length)
106 | - Output pipe: (result, exec time, sonar data length)
107 |
108 | * Coverage
109 |
110 | - 64K of 8-bit counters
111 | - Counters are rounded up to: 0, 1, 2, 3, 4, 5, 8, 16, 32, 64, 255
112 | - Signal ID is hashed using SHA-1
113 | - Currently uses basic block ID (but adds missing else and default blocks, instruments || and &&)
114 |
115 | * Mutations
116 |
117 | Does N mutations with probability 1/2^N:
118 |
119 | - Insert/remove/duplicate/copy a random range of random bytes.
120 | - Bit flip.
121 | - Swap 2 bytes.
122 | - Set a byte to a random value.
123 | - Add/subtract from a byte/uint16/uint32/uint64 (le/be).
124 | - Replace a byte/uint16/uint32 with an interesting value (le/be).
125 | - Replace an ascii digit/number with another digit/number.
126 | - Splice another input.
127 | - Insert a part of another input.
128 | - Insert a string/int literal.
129 | - Replace with string/int literal.
130 |
131 | * Corpus management
132 |
133 | For input we know:
134 |
135 | - input data
136 | - coverage
137 | - result
138 | - mutation depth
139 | - execution time
140 |
141 | Inputs are minimized and prioritized.
142 |
143 | * Input prioritization
144 |
145 | Each input receives score 1..1000.
146 |
147 | - start with score 10
148 | - execution time multiplier 0.1-3x
149 | - coverage size multiplier 0.25-3x
150 | - input depth multiplier 1-5x
151 | - user boost multiplier 1-4x
152 |
153 | Then choose favored inputs: minimal set of highest-score inputs that give full counter coverage. The rest receive score 1 (can be discarded as well).
154 | During mutations inputs are selected according to the priority.
155 |
156 |
157 | * Game over
158 |
159 | CRC32 checksum verification in `image/png/reader.go`
160 |
161 | func (d *decoder) verifyChecksum() error {
162 | if binary.BigEndian.Uint32(d.tmp[:4]) != d.crc.Sum32() {
163 | return FormatError("invalid checksum")
164 | }
165 | return nil
166 | }
167 |
168 | Probability that random mutations will alter input in an interesting way and
169 | guess CRC32 at the same time is basically ZERO.
170 |
171 | * Sonar
172 |
173 | Don't need to guess, program knows it!
174 |
175 | + v1 := binary.BigEndian.Uint32(d.tmp[:4])
176 | + v2 := d.crc.Sum32()
177 | + __go_fuzz.Sonar(v1, v2, id, flags)
178 | if v1 != v2 {
179 | return FormatError("invalid checksum")
180 | }
181 |
182 | Then, find v1 in the input and replace it with v2. Done!
183 |
184 | * Sonar (2)
185 |
186 | ID allows to identify a particular code site (and match with metadata).
187 | Flags: SonarEQL, SonarNEQ, SonarLSS, SonarGTR, SonarLEQ, SonarGEQ, SonarLength, SonarSigned, SonarString, SonarConst1, SonarConst2.
188 |
189 | Understands lower/upper-case, big-endian, base-128, ascii, hex.
190 | Also tried to increment/decrement the value.
191 |
192 | Evalues the comparison and counts number of times it is taken one way or another.
193 | Marks sites where both operands are dynamic (e.g. CRC check).
194 | Skips sites that are taken both ways enough times.
195 |
196 | Tried to match causes and effects. Turned out to be non-trivial.
197 |
198 | * Game over 2
199 |
200 | Mutations and sonar do low-level changes ("bit-flipping"):
201 |
202 | Original:
203 |
204 | `100`
205 |
206 | Mutated:
207 |
208 | `100<`
209 |
210 | Also want high-level changes!
211 |
212 | * Versifier
213 |
214 | Versifier reverse-engineers [text] protocol and learns its _structure_.
215 |
216 | abc -> alphanum token
217 | 123, 1e-2 -> number
218 | "..." -> quoted
219 | [...] -> parenthesized
220 | ...,...,... -> list
221 | ...\n...\n -> lines
222 |
223 | Then, applies _structural_ mutations to inputs.
224 |
225 | * Versifier example
226 |
227 | Original:
228 |
229 | `100`
230 |
231 | Versified (all valid xml):
232 |
233 |
234 | =
235 | -026023767521520230564132665e0333302100
236 | 510-9e-07036514
237 | prop name="p"/}01e-6
238 | 1008
239 |
240 | * Algorithm
241 |
242 | .image algo.png
243 |
244 | * Cover profile demo
245 |
246 | [DEMO]
247 |
248 | * Achievements
249 |
250 | - 60 tests
251 | - 137 bugs in std lib (70 fixed)
252 | - 165 elsewhere (47 in gccgo, 30 in golang.org/x, 42 in freetype-go, protobuf, http2, bson)
253 |
254 | * Achievements
255 |
256 | fmt.Sprintf("%.[]")
257 | panic: runtime error: index out of range
258 |
259 | regexp.MustCompile("((0(0){0}))").ReplaceAllString("00000", "00$00")
260 | panic: runtime error: slice bounds out of range
261 |
262 | ioutil.ReadAll(flate.NewReader(strings.NewReader("4LJNIMK\a\x00\x00\xff..\xff.....\xff")))
263 | runs forever
264 |
265 | var x = 1/"."[0]
266 | crashes compiler
267 |
268 | archive/tar: hang
269 | archive/zip: cap out of range
270 | encoding/gob: stack overflow
271 | encoding/asn1: index out of range
272 | image/jpeg: Decode hangs
273 | image/png: nil deref
274 | math/big: incorrect string->Float conversion
275 | crypto/x509: division by zero
276 | ...
277 |
278 | * Usage
279 |
280 | - go get github.com/dvyukov/go-fuzz/...
281 | - write test:
282 |
283 | func Fuzz(data []byte) int {
284 | gob.NewDecoder(bytes.NewReader(data))
285 | return 0
286 | }
287 |
288 | - build
289 |
290 | $ go-fuzz-build github.com/dvyukov/go-fuzz/examples/gob
291 |
292 | - collect corpus
293 | - run
294 |
295 | $ go-fuzz -bin=gob-fuzz.zip -workdir=examples/gob
296 |
297 | * Areas for improvement
298 |
299 | - Better reverse engineering of text protocols (more constructs, recursive decomposition, several guesses)
300 | - Reverse engineering of binary protos (fields, message type, length, encoding of data, etc)
301 | - Better structural mutations
302 | - More compiler assists for sonar (x/3 > 100 --> x > 300, index expressions)
303 | - More encodings in sonar
304 | - Match causes and effects in sonar
305 | - Detect sonar sites that we don't affect
306 | - Mark fields and their meaning in inputs
307 | - Find non-meaningful bytes in inputs
308 | - Better corpus prioritization
309 | - What is a signal (bb, edge, path, stack+bb, type, index)
310 |
311 |
312 |
313 |
--------------------------------------------------------------------------------
/slides/gob.go:
--------------------------------------------------------------------------------
1 | // Prevent go install ./... from complaining about different packages in the same dir.
2 | // +build
3 |
4 | package main
5 |
6 | import (
7 | "bytes"
8 | "encoding/gob"
9 | "encoding/hex"
10 | )
11 |
12 | // START OMIT
13 | const data = "4dffb503010102303001ff30000109010130010800010130010800010130" +
14 | "01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" +
15 | "860001013001ff860001013001ffb80000001eff850401010e3030303030" +
16 | "30303030303030303001ff3000010c0104000016ffb70201010830303030" +
17 | "3030303001ff3000010c000030ffb6040405fcff00303030303030303030" +
18 | "303030303030303030303030303030303030303030303030303030303030" +
19 | "303030303030303030303030303030303030303030303030303030303030" +
20 | "30303030303030"
21 |
22 | type X struct {
23 | J *X
24 | K map[string]int
25 | }
26 |
27 | func main() {
28 | raw, _ := hex.DecodeString(data)
29 | gob.NewDecoder(bytes.NewReader(raw)).Decode(new(X))
30 | }
31 |
32 | // END OMIT
33 |
--------------------------------------------------------------------------------
/slides/regexp.go:
--------------------------------------------------------------------------------
1 | // Prevent go install ./... from complaining about different packages in the same dir.
2 | // +build
3 |
4 | package regexp
5 |
6 | import (
7 | "regexp"
8 | )
9 |
10 | // START OMIT
11 | func FuzzRegexp(data []byte) int {
12 | if len(data) < 3 {
13 | return 0
14 | }
15 | longestMode := data[0]%2 != 0 // first byte as "longest" flag
16 | reStr := data[1 : len(data)/2] // half as regular expression
17 | matchStr := data[len(data)/2:] // the rest is string to match
18 |
19 | re, err := regexp.Compile(string(reStr))
20 | if err != nil {
21 | return 0
22 | }
23 | if longestMode {
24 | re.Longest()
25 | }
26 | re.FindAll(matchStr, -1)
27 | return 0
28 | }
29 |
30 | // END OMIT
31 |
--------------------------------------------------------------------------------
/test/corpus/0:
--------------------------------------------------------------------------------
1 | -
2 |
--------------------------------------------------------------------------------
/test/corpus/1:
--------------------------------------------------------------------------------
1 | 0123456789abcd
2 |
--------------------------------------------------------------------------------
/test/corpus/2:
--------------------------------------------------------------------------------
1 | foo input [x] minimization ---- test
2 | ------------------------------------
3 | ------------------------------------
4 | ------------------------------------
5 | ------------------------------------
6 | ------------------------------------
7 | ------------------------------------
8 | ------------------------------------
9 | ------------------------------------
10 | ------------------------------------
11 | ------------------------------------
12 | ------------------------------------
13 | ------------------------------------
14 | ------------------------------------
15 | ------------------------------------
16 | ------------------------------------
17 |
18 |
--------------------------------------------------------------------------------
/test/internal/test/test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | // Test for fuzzing of internal packages.
5 |
6 | package test
7 |
8 | func Fuzz(data []byte) int {
9 | return 0
10 | }
11 |
--------------------------------------------------------------------------------
/test/test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 go-fuzz project authors. All rights reserved.
2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3 |
4 | package test
5 |
6 | import (
7 | "bytes"
8 | "runtime"
9 |
10 | // Test vendoring support.
11 | vendored_foo "non.existent.com/foo"
12 |
13 | "github.com/dvyukov/go-fuzz/test/testdep"
14 | )
15 |
16 | func init() {
17 | vendored_foo.Foo()
18 | // Test that background goroutines don't break sonar.
19 | // https://github.com/dvyukov/go-fuzz/issues/145
20 | // Sonar code is racy (see the issue), but the test don't crash
21 | // unless runtime.GOMAXPROCS is uncommented below.
22 | go func() {
23 | x := 0
24 | s := "foobarbazqux"
25 | for i := 0; ; i++ {
26 | runtime.Gosched()
27 | if i == x {
28 | s = "foobarbazquz"
29 | x -= 1
30 | }
31 | if s == "foo" {
32 | x -= 1
33 | }
34 | }
35 | }()
36 | }
37 |
38 | func Fuzz(data []byte) int {
39 | if len(data) != cap(data) {
40 | panic("should not happen")
41 | }
42 | if len(data) == 1 {
43 | if data[0] == '!' || data[0] == '#' {
44 | panic("bingo 0")
45 | }
46 | if data[0] == '0' || data[0] == '9' {
47 | for {
48 | c := make(chan bool)
49 | close(c)
50 | }
51 | }
52 | if data[0] == 'a' || data[0] == 'z' {
53 | data := make([]byte, 128<<30-1)
54 | _ = data
55 | }
56 | if data[0] == 'b' {
57 | // new coverage
58 | }
59 | if data[0] == 'c' {
60 | // new coverage
61 | }
62 | }
63 |
64 | // Test for crash minimization.
65 | if bytes.IndexByte(data, 'x') != -1 {
66 | if bytes.IndexByte(data, 'y') != -1 {
67 | panic("xy")
68 | }
69 | }
70 |
71 | // Test for input minimization.
72 | if bytes.Index(data, []byte("input ")) != -1 {
73 | if bytes.Index(data, []byte("minimization ")) != -1 {
74 | if bytes.Index(data, []byte("test")) != -1 {
75 | }
76 | }
77 | }
78 |
79 | if len(data) >= 14 && bytes.HasPrefix(data, []byte("0123456789")) {
80 | x := int(data[10]) + int(data[11])<<8 + int(data[12])<<16 + int(data[13])<<24
81 | if x == 0 || x == -1 {
82 | panic("bingo 1")
83 | }
84 | if x == 255 || x == 256 {
85 | for {
86 | c := make(chan bool)
87 | close(c)
88 | }
89 | }
90 | if x == 1<<16-1 || x == 1<<16 {
91 | data := make([]byte, 128<<30-1)
92 | _ = data
93 | }
94 | if x == '1' {
95 | // new coverage
96 | }
97 | if x == '2' {
98 | // new coverage
99 | }
100 | }
101 | if len(data) == 7 && data[7:8][0] == 'a' {
102 | // The above should cause OOB.
103 | }
104 | return 0
105 | }
106 |
107 | // Compilation tests, go-fuzz-build previously failed on these code patterns.
108 |
109 | // Test for issue #35.
110 | const X = 1 << 129
111 |
112 | func foo(x float64) bool {
113 | return x < X
114 | }
115 |
116 | func test1() bool {
117 | var x uint64
118 | var y uint
119 | return x == 1< ../../../../bar
82 |
83 | -- gopath/src/example.com/foo/fuzz.go --
84 | package foo
85 |
86 | import "example.com/bar"
87 |
88 | func FuzzMod(data []byte) int {
89 | bar.Bar()
90 | return 0
91 | }
92 |
93 | -- bar/go.mod --
94 | module example.com/bar
95 |
96 | -- bar/bar.go --
97 | package bar
98 |
99 | func Bar() string {
100 | return "hello from bar"
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/testscripts/mod_outside_gopath.txt:
--------------------------------------------------------------------------------
1 | # These steps validate we can fuzz outside of GOPATH
2 | # with GO111MODULE auto, on, unset, and off.
3 |
4 | # The foo module being fuzzed depends on a 'replace'
5 | # in its go.mod to find one of its dependencies,
6 | # which is a way to check that module-mode is required to compile successfully.
7 |
8 | # Enter a simple module with a fuzz function.
9 | # This is outside of GOPATH.
10 | cd foo
11 |
12 | # Copy a pristine go.mod file.
13 | cp go.mod_PRISTINE go.mod
14 |
15 | # First, we test with GO111MODULE=auto, which is the default
16 | # in Go 1.11-1.13. (In 1.14, the default will likely be GO111MODULE=on).
17 | env GO111MODULE=auto
18 |
19 | # Because cmd/go now defaults to -mod=readonly, we need to explicitly add go-fuzz-dep.
20 | go get github.com/dvyukov/go-fuzz/go-fuzz-dep
21 |
22 | # Sanity check the module seems well formed.
23 | exec go list -m all
24 | stdout '^example.com/foo$'
25 | exec go build
26 |
27 | # Because we are outside GOPATH, the presence of the 'go.mod' here will
28 | # enable module-module for cmd/go with GO111MODULE=auto.
29 | # This is true in Go 1.11-1.13, and likely will be true in 1.14 as well.
30 | # Ask go-fuzz-build to build, including specifying the fuzz function for mod.
31 | exec go-fuzz-build -func=FuzzMod
32 | exists foo-fuzz.zip
33 |
34 | # Validate we can start fuzzing.
35 | # Note that 'timeout(1)' will error here, so we preface the invocation with '!'.
36 | # For travis on Windows, we install 'timeout(1)' as part of our travis setup steps.
37 | # To test this locally on Windows, you might need to change 'timeout' to '\cygwin64\bin\timeout' or similar.
38 | ! exec timeout 5 go-fuzz -procs=1 -func=FuzzMod
39 | stderr 'workers: \d+, corpus: '
40 |
41 | # Clean up.
42 | cp go.mod_PRISTINE go.mod
43 | rm foo-fuzz.zip
44 | go get github.com/dvyukov/go-fuzz/go-fuzz-dep
45 |
46 | # Second, we test with GO111MODULE=on, which will likely be the default in Go 1.14.
47 | env GO111MODULE=on
48 |
49 | # Sanity check we can still interact with the module.
50 | exec go list -m all
51 | stdout '^example.com/foo$'
52 | exec go build
53 |
54 | # Ask go-fuzz-build to build, including specifying the fuzz function for mod.
55 | exec go-fuzz-build -func=FuzzMod
56 | exists foo-fuzz.zip
57 |
58 | # Validate we can start fuzzing.
59 | ! exec timeout 5 go-fuzz -procs=1 -func=FuzzMod
60 | stderr 'workers: \d+, corpus: '
61 |
62 | # Clean up.
63 | cp go.mod_PRISTINE go.mod
64 | rm foo-fuzz.zip
65 | go get github.com/dvyukov/go-fuzz/go-fuzz-dep
66 |
67 | # Third, we test with GO111MODULE unset.
68 | # The meaning of this will likely change in Go 1.14, but given we are
69 | # outside of GOPATH, in theory these steps will still work even in Go 1.14.
70 | env GO111MODULE=
71 |
72 | # Sanity check we can still interact with the module.
73 | exec go list -m all
74 | stdout '^example.com/foo$'
75 | exec go build
76 |
77 | # Ask go-fuzz-build to build, including specifying the fuzz function for mod.
78 | exec go-fuzz-build -func=FuzzMod
79 | exists foo-fuzz.zip
80 |
81 | # Validate we can start fuzzing.
82 | ! exec timeout 5 go-fuzz -procs=1 -func=FuzzMod
83 | stderr 'workers: \d+, corpus: '
84 |
85 | # Clean up.
86 | cp go.mod_PRISTINE go.mod
87 | rm foo-fuzz.zip
88 | go get github.com/dvyukov/go-fuzz/go-fuzz-dep
89 |
90 | # Fourth, we test with GO111MODULE=off.
91 | # The meaning of this is unlikely to change in Go 1.14,
92 | # altough in some (distant?) future, GO111MODULE=off might
93 | # no longer be supported.
94 | env GO111MODULE=off
95 |
96 | # Confirm 'go list -m' and 'go build' fail.
97 | ! exec go list -m all
98 | ! exec go build
99 |
100 | # Confirm go-fuzz-build fails.
101 | ! exec go-fuzz-build -func=FuzzMod
102 | ! exists foo-fuzz.zip
103 |
104 | # Clean up (mainly in case we later add another test below).
105 | cp go.mod_PRISTINE go.mod
106 | rm foo-fuzz.zip
107 | go get github.com/dvyukov/go-fuzz/go-fuzz-dep
108 |
109 | # Define two modules.
110 | # example.com/foo has a fuzz function, and depends on example.com/bar.
111 |
112 | -- foo/go.mod_PRISTINE --
113 | module example.com/foo
114 |
115 | require example.com/bar v0.0.0
116 |
117 | replace example.com/bar => ../bar
118 |
119 | -- foo/fuzz.go --
120 | package foo
121 |
122 | import "example.com/bar"
123 |
124 | func FuzzMod(data []byte) int {
125 | bar.Bar()
126 | return 0
127 | }
128 |
129 | -- bar/go.mod --
130 | module example.com/bar
131 |
132 | -- bar/bar.go --
133 | package bar
134 |
135 | func Bar() string {
136 | return "hello from bar"
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/testscripts/mod_v2.txt:
--------------------------------------------------------------------------------
1 | # These steps validate we can fuzz a module that depends on a v2 module.
2 | # v2 modules are particularly problematic if not handled properly.
3 | # We validate that 'go-fuzz' and 'go-fuzz-build' work with and without
4 | # the package path and fuzz function being specified.
5 |
6 | # Enter a module that depends on a v2+ module.
7 | # Note that we are outside of GOPATH, so the presence of the 'go.mod' here will
8 | # enable module-module for cmd/go (which is true in Go 1.11-1.13, and likely will be true in 1.14 as well).
9 | cd foo
10 |
11 | # Sanity check the module seems well formed.
12 | exec go list -m all
13 | stdout '^example.com/foo$'
14 | stdout '^example.com/bar/v2 v2.0.0 => ../bar$'
15 | exec go build
16 |
17 | # Because cmd/go now defaults to -mod=readonly, we need to explicitly add go-fuzz-dep.
18 | go get github.com/dvyukov/go-fuzz/go-fuzz-dep
19 |
20 | # Ask go-fuzz-build to build, first specifying the package path and fuzz function for foo.
21 | # foo is a module itself, and foo also depends on a v2 module bar.
22 | exec go-fuzz-build -func=FuzzDependOnV2Mod example.com/foo
23 | exists foo-fuzz.zip
24 |
25 | # Validate we can start fuzzing, first with the fuzz function specified.
26 | # Note that 'timeout(1)' will error here, so we preface the invocation with '!'.
27 | # For travis on Windows, we install 'timeout(1)' as part of our travis setup steps.
28 | # To test this locally on Windows, you might need to change 'timeout' to '\cygwin64\bin\timeout' or similar.
29 | ! exec timeout 5 go-fuzz -procs=1 -func=FuzzDependOnV2Mod
30 | stderr 'workers: \d+, corpus: '
31 |
32 | # Validate we can start fuzzing, now without a fuzz function specified.
33 | ! exec timeout 5 go-fuzz -procs=1
34 | stderr 'workers: \d+, corpus: '
35 |
36 | # Ask go-fuzz-build to build again, but now do not specify the fuzz function.
37 | rm foo-fuzz.zip
38 | exec go-fuzz-build
39 | exists foo-fuzz.zip
40 |
41 | # Validate we can start fuzzing with the new zip, first with a fuzz function specified.
42 | ! exec timeout 5 go-fuzz -procs=1 -func=FuzzDependOnV2Mod
43 | stderr 'workers: \d+, corpus: '
44 |
45 | # Validate we can start fuzzing with the new zip, now without a fuzz function specified.
46 | ! exec timeout 5 go-fuzz -procs=1
47 | stderr 'workers: \d+, corpus: '
48 |
49 | # Define two modules.
50 | # example.com/foo has a fuzz function, and depends on example.com/bar/v2.
51 | # The v2 module is following the 'major branch' approach for v2+ modules,
52 | # not 'major subdirectory' approach. (Details: https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher)
53 |
54 | -- foo/go.mod --
55 | module example.com/foo
56 |
57 | require example.com/bar/v2 v2.0.0
58 |
59 | replace example.com/bar/v2 => ../bar
60 |
61 | -- foo/fuzz.go --
62 | package foo
63 |
64 | import "example.com/bar/v2"
65 |
66 | func FuzzDependOnV2Mod(data []byte) int {
67 | bar.Bar()
68 | return 0
69 | }
70 |
71 | -- bar/go.mod --
72 | module example.com/bar/v2
73 |
74 | -- bar/bar.go --
75 | package bar
76 |
77 | func Bar() string {
78 | return "hello from bar"
79 | }
80 |
--------------------------------------------------------------------------------
/testscripts/mod_vendor.txt:
--------------------------------------------------------------------------------
1 | # These steps validate we get a proper error
2 | # if we try to fuzz a module using GOFLAGS=-mod=vendor.
3 | # It then stops.
4 |
5 | # For the future, we have some unexecuted steps after the stop.
6 | # These don't run now, but attempt to validate fuzzing a module using -mod=vendor,
7 | # including validating that the actual code in the 'vendor' directory is used.
8 |
9 | # TODO: -mod=vendor is not yet supported with go-fuzz, so the majority of this test is disabled with a 'stop' command.
10 | # If you change go-fuzz-build to remove the check for -mod=vendor, it currently fails with error:
11 | # > exec go-fuzz-build -func=FuzzDependOnVendor
12 | # [stderr]
13 | # could not load packages: go [list -e -json -compiled=true -test=false -export=false -deps=true -find=false -tags gofuzz -- .
14 | # github.com/dvyukov/go-fuzz/go-fuzz-dep]: exit status 1: build github.com/dvyukov/go-fuzz/go-fuzz-dep:
15 | # cannot load github.com/dvyukov/go-fuzz/go-fuzz-dep:
16 | # open $WORK\foo\vendor\github.com\dvyukov\go-fuzz\go-fuzz-dep: The system cannot find the path specified.
17 |
18 | # TODO: this test currently relies on latest go-fuzz/go-fuzz-dep and go-fuzz-defs
19 | # versions on master being downloaded from github as part of the tests.
20 | # If this test is run in a go-fuzz branch and there have been signficant
21 | # changes to go-fuzz/go-fuzz-dep and go-fuzz-defs in the branch, this test
22 | # will use the versions from master and might incorrectly fail.
23 | # If that happens, the foo/go.mod below can temporarily be modified to point
24 | # to the branch, or this test can temporarily be ignored or removed.
25 |
26 | # Enter a module that has a vendor directory, where the code in the vendor
27 | # directory has been edited compared (so that we can validate the vendored
28 | # code is in fact used).
29 | # Note that we are outside of GOPATH, so the presence of the 'go.mod' here will
30 | # enable module-module for cmd/go (which is true in Go 1.11-1.13, and likely will be true in 1.14 as well).
31 | cd foo
32 |
33 | # Because cmd/go now defaults to -mod=readonly and requires a valid go.sum entry, we need to explicitly add go-fuzz-dep.
34 | go get github.com/dvyukov/go-fuzz/go-fuzz-dep
35 |
36 | # Sanity check the module seems well formed.
37 | exec go list -m all
38 | stdout '^example.com/foo$'
39 | stdout '^example.com/bar v0.0.0 => ../bar$'
40 | exec go build
41 |
42 | # Do an intial 'go mod vendor'
43 | exec go mod vendor
44 |
45 | # Overwrite the original bar with bad code that will not compile,
46 | # while leaving the vendored version of bar alone
47 | # so that vendored code still compiles.
48 | cp ../bar/bar.go_BAD ../bar/bar.go
49 |
50 | # Sanity check the good code compiles, and bad code fails.
51 | # Successful compilation here relies on -mod=vendor being enabled.
52 | env GOFLAGS=-mod=vendor
53 | exec go build
54 | env GOFLAGS=
55 | ! exec go build
56 |
57 | # Validate we get the expected error if we try to run go-fuzz-build
58 | # with -mod=vendor in GOFLAGS.
59 | env GOFLAGS=-mod=vendor
60 | ! exec go-fuzz-build -func=FuzzDependOnVendor
61 | stderr 'GOFLAGS with -mod=vendor is not supported'
62 | env GOFLAGS='-v -mod=vendor'
63 | ! exec go-fuzz-build -func=FuzzDependOnVendor
64 | stderr 'GOFLAGS with -mod=vendor is not supported'
65 |
66 | # This is as far as we can currently go here, given go-fuzz does not support -mod=vendor.
67 | # The remainder of the test has never passed, but we will leave here in case useful
68 | # in the future. The rest of the tests are set to pass as if GOFLAGS=-mod=vendor worked,
69 | # but they have never run, so might have a typo or other mistake.
70 | stop 'do not run actual vendoring test. -mod=vendor not yet supported.'
71 |
72 | # Ask go-fuzz-build to build, specifying the fuzz function for foo.
73 | # First, we rely on -mod=vendor being set.
74 | env GOFLAGS=-mod=vendor
75 | exec go-fuzz-build -func=FuzzDependOnVendor
76 | exists foo-fuzz.zip
77 |
78 | # Validate we can start fuzzing.
79 | # Note that 'timeout(1)' will error here, so we preface the invocation with '!'.
80 | # For travis on Windows, we install 'timeout(1)' as part of our travis setup steps.
81 | # To test this locally on Windows, you might need to change 'timeout' to '\cygwin64\bin\timeout' or similar.
82 | ! exec timeout 5 go-fuzz -procs=1 -func=FuzzUsingVendor
83 | stderr 'workers: \d+, corpus: '
84 |
85 | # Ask go-fuzz-build to build again, but now we do not specify -mod=vendor,
86 | # which results in a compilation failure because the non-vendored code
87 | # deliberately does not compile (as a way to validate we are using the vendored code above).
88 | env GOFLAGS=
89 | rm foo-fuzz.zip
90 | ! exec go-fuzz-build
91 | ! exists foo-fuzz.zip
92 |
93 | # Define two modules.
94 | # example.com/foo has a fuzz function, and depends on example.com/bar.
95 | # foo has vendored bar and (in theory) made a code change inside foo's vendor
96 | # directory. The bar code inside foo's vendor compiles, but the original
97 | # bar code does not compile.
98 | # The go.mod files set 'go 1.13' to avoid triggering the Go 1.14 auto
99 | # detection of a vendor directory.
100 |
101 | -- foo/fuzz.go --
102 | package foo
103 |
104 | import "example.com/bar"
105 |
106 | func FuzzUsingVendor(data []byte) int {
107 | bar.Bar()
108 | return 0
109 | }
110 |
111 | -- foo/go.mod --
112 | module example.com/foo
113 |
114 | go 1.13
115 |
116 | require (
117 | example.com/bar v0.0.0
118 | )
119 |
120 | replace example.com/bar => ../bar
121 |
122 | -- bar/bar.go_BAD --
123 | package bar
124 |
125 | // This will not compile.
126 | func BarWillNotCompile() string {
127 |
128 | }
129 |
130 | -- bar/bar.go --
131 | package bar
132 |
133 | // This compiles.
134 | func Bar() string {
135 | return "hello from bar"
136 | }
137 |
138 | -- bar/go.mod --
139 | module example.com/bar
140 |
141 | go 1.13
142 |
--------------------------------------------------------------------------------