diff options
19 files changed, 2483 insertions, 7 deletions
@@ -401,8 +401,7 @@ install_prerequisites: sudo apt-get install -y -q g++-s390x-linux-gnu || true sudo apt-get install -y -q g++-riscv64-linux-gnu || true sudo apt-get install -y -q ragel clang-format - go get -u golang.org/x/tools/cmd/goyacc \ - github.com/dvyukov/go-fuzz/go-fuzz-build + go get -u golang.org/x/tools/cmd/goyacc check_copyright: ./tools/check-copyright.sh @@ -6,6 +6,7 @@ require ( cloud.google.com/go v0.60.0 // indirect cloud.google.com/go/storage v1.10.0 github.com/BurntSushi/toml v0.3.1 // indirect + github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.4.2 // indirect github.com/golangci/golangci-lint v1.27.0 @@ -71,6 +71,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go index 009963b3f..630a66907 100644 --- a/pkg/tools/tools.go +++ b/pkg/tools/tools.go @@ -9,5 +9,7 @@ package tools import ( + _ "github.com/dvyukov/go-fuzz/go-fuzz-build" + _ "github.com/dvyukov/go-fuzz/go-fuzz-dep" _ "github.com/golangci/golangci-lint/cmd/golangci-lint" ) diff --git a/tools/docker/env/Dockerfile b/tools/docker/env/Dockerfile index 87184f84a..c1152a9a6 100644 --- a/tools/docker/env/Dockerfile +++ b/tools/docker/env/Dockerfile @@ -2,7 +2,7 @@ # Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. # The image provides dev environment suitable for syzkaller development/testing. -# It includes Go toolchain, C/C++ cross-compilers and go-fuzz. +# It includes Go toolchain and C/C++ cross-compilers. # The image is available as gcr.io/syzkaller/env. @@ -36,9 +36,6 @@ RUN curl https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | tar -C /usr/loca ENV PATH /usr/local/go/bin:/gopath/bin:$PATH ENV GOPATH /gopath -# For go-fuzz we also need sources (go-fuzz-dep). -RUN go get github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build - # Pre-create dirs for syz-dock. # This is necessary to make docker work with the current user, # otherwise --volume will create these dirs under root and then diff --git a/tools/fuzzit.sh b/tools/fuzzit.sh index a056b7c3a..780ce4db5 100755 --- a/tools/fuzzit.sh +++ b/tools/fuzzit.sh @@ -7,13 +7,16 @@ set -eux export TYPE="${1}" +export GOBIN=$(realpath .)/bin function target { - go-fuzz-build -libfuzzer -func $3 -o fuzzer.a $2 + bin/go-fuzz-build -libfuzzer -func $3 -o fuzzer.a $2 clang -fsanitize=fuzzer fuzzer.a -o fuzzer ./fuzzit create job --type "${TYPE}" --branch ${GITHUB_REF#refs/heads/} --revision ${GITHUB_SHA} syzkaller/$1 ./fuzzer } +go install github.com/dvyukov/go-fuzz/go-fuzz-build + curl -L --output fuzzit https://github.com/fuzzitdev/fuzzit/releases/download/v2.4.77/fuzzit_Linux_x86_64 chmod a+x fuzzit diff --git a/vendor/github.com/dvyukov/go-fuzz/LICENSE b/vendor/github.com/dvyukov/go-fuzz/LICENSE new file mode 100644 index 000000000..dd5b3a58a --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/LICENSE @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/cover.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/cover.go new file mode 100644 index 000000000..5c5d83efb --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/cover.go @@ -0,0 +1,906 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "crypto/sha1" + "fmt" + "go/ast" + "go/constant" + "go/printer" + "go/token" + "go/types" + "io" + "strconv" + "strings" + + . "github.com/dvyukov/go-fuzz/go-fuzz-defs" + . "github.com/dvyukov/go-fuzz/internal/go-fuzz-types" +) + +const fuzzdepPkg = "_go_fuzz_dep_" + +func instrument(pkg, fullName string, fset *token.FileSet, parsedFile *ast.File, info *types.Info, out io.Writer, blocks *[]CoverBlock, sonar *[]CoverBlock) { + file := &File{ + fset: fset, + pkg: pkg, + fullName: fullName, + astFile: parsedFile, + blocks: blocks, + info: info, + } + if sonar == nil { + file.addImport("go-fuzz-dep", fuzzdepPkg, "CoverTab") + ast.Walk(file, file.astFile) + } else { + s := &Sonar{ + fset: fset, + fullName: fullName, + pkg: pkg, + blocks: sonar, + info: info, + } + ast.Walk(s, file.astFile) + } + + file.print(out) +} + +type Sonar struct { + fset *token.FileSet + fullName string + pkg string + blocks *[]CoverBlock + info *types.Info +} + +var sonarSeq = 0 + +func (s *Sonar) Visit(n ast.Node) ast.Visitor { + // TODO: detect "x&mask==0", emit sonar(x, x&^mask) + switch nn := n.(type) { + case *ast.BinaryExpr: + break + + case *ast.GenDecl: + if nn.Tok != token.VAR { + return nil // constants and types are not interesting + } + return s + + case *ast.SelectorExpr: + return nil + + case *ast.SwitchStmt: + if nn.Tag == nil || nn.Body == nil { + return s // recurse + } + // Replace: + // switch a := foo(); bar(a) { + // case x: ... + // case y: ... + // } + // with: + // switch { + // default: + // a := foo() + // __tmp := bar(a) + // switch { + // case __tmp == x: ... + // case __tmp == y: ... + // } + // } + // The == comparisons will be instrumented later when we recurse. + sw := new(ast.SwitchStmt) + *sw = *nn + var stmts []ast.Stmt + if sw.Init != nil { + stmts = append(stmts, sw.Init) + sw.Init = nil + } + const tmpvar = "__go_fuzz_tmp" + tmp := ast.NewIdent(tmpvar) + typ := s.info.Types[sw.Tag] + s.info.Types[tmp] = typ + stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{tmp}, Tok: token.DEFINE, Rhs: []ast.Expr{sw.Tag}}) + stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent("_")}, Tok: token.ASSIGN, Rhs: []ast.Expr{tmp}}) + sw.Tag = nil + stmts = append(stmts, sw) + for _, cas1 := range sw.Body.List { + cas := cas1.(*ast.CaseClause) + for i, expr := range cas.List { + tmp := &ast.Ident{Name: tmpvar, NamePos: expr.Pos()} + s.info.Types[tmp] = typ + cas.List[i] = &ast.BinaryExpr{X: tmp, Op: token.EQL, Y: expr} + } + } + nn.Tag = nil + nn.Init = nil + nn.Body = &ast.BlockStmt{List: []ast.Stmt{&ast.CaseClause{Body: stmts}}} + return s // recurse + + case *ast.ForStmt: + // For condition is usually uninteresting, but produces lots of samples. + // So we skip it if it looks boring. + if nn.Init != nil { + ast.Walk(s, nn.Init) + } + if nn.Post != nil { + ast.Walk(s, nn.Post) + } + ast.Walk(s, nn.Body) + if nn.Cond != nil { + // Look for the following pattern: + // for foo := ...; foo ? ...; ... { ... } + boring := false + if nn.Init != nil { + if init, ok1 := nn.Init.(*ast.AssignStmt); ok1 && init.Tok == token.DEFINE && len(init.Lhs) == 1 { + if id, ok2 := init.Lhs[0].(*ast.Ident); ok2 { + if bex, ok3 := nn.Cond.(*ast.BinaryExpr); ok3 { + if x, ok4 := bex.X.(*ast.Ident); ok4 && x.Name == id.Name { + boring = true + } + if x, ok4 := bex.Y.(*ast.Ident); ok4 && x.Name == id.Name { + boring = true + } + } + } + } + } + if !boring { + ast.Walk(s, nn.Cond) + } + } + return nil + + default: + return s // recurse + } + + // TODO: handle map index expressions (especially useful for strings). + // E.g. when code matches a read in identifier against a set of known identifiers. + // For the record, it looks as follows. However, it is tricky to distinguish + // from slice/array index and map assignments... + //. . . . . . . *ast.IndexExpr { + //. . . . . . . . X: *ast.Ident { + //. . . . . . . . . Name: "m" + //. . . . . . . . } + //. . . . . . . . Index: *ast.Ident { + //. . . . . . . . . Name: "s" + //. . . . . . . . } + //. . . . . . . } + + // TODO: transform expressions so that lhs expression contains a variable + // and rhs contains all constant operands. For example, for (real code from vp8 codec): + // cf := (b[0]>>4)&7 == 5 + // we would like to transform it to: + // b[0] & (7<<4) == 5<<4 + // and then to: + // b[0] == 5<<4 | b & ^(7<<4) + // and emit: + // Sonar(b[0], 5<<4 | b & ^(7<<4), SonarEQL) + // This will allow the fuzzer to figure out what bytes it needs to replace + // with what bytes in order to crack this condition. + // Similarly, for: + // x/3 == 100 + // we would like to emit: + // Sonar(x, 100*3, SonarEQL) + + // TODO: intercept strings.Index/HasPrefix and similar functions. + + nn := n.(*ast.BinaryExpr) + var flags uint8 + switch nn.Op { + case token.EQL: + flags = SonarEQL + break + case token.NEQ: + flags = SonarNEQ + break + case token.LSS: + flags = SonarLSS + break + case token.GTR: + flags = SonarGTR + break + case token.LEQ: + flags = SonarLEQ + break + case token.GEQ: + flags = SonarGEQ + break + default: + return s // recurse + } + // Replace: + // x != y + // with: + // func() _go_fuzz_dep_.Bool { v1 := x; v2 := y; go-fuzz-dep.Sonar(v1, v2, flags); return v1 != v2 }() == true + // Using "== true" lets us modify the AST Node in-place. + v1 := nn.X + v2 := nn.Y + ast.Walk(s, v1) + ast.Walk(s, v2) + if isCap(v1) || isCap(v2) { + // Haven't seen useful cases yet. + return s + } + if isLen(v1) || isLen(v2) { + // TODO: we could pass both length value and the len argument. + // For example, if the code is: + // name := ... // obtained from input + // if len(name) > 5 { ... } + // If we would have the name value at runtime, we will know + // what part of the input to alter to affect len result. + flags |= SonarLength + } + + checkType := func(tv types.TypeAndValue) bool { + // Comparisons of pointers, maps, chans and bool are not interesting. + if _, ok := tv.Type.(*types.Pointer); ok { + return false + } + if _, ok := tv.Type.(*types.Map); ok { + return false + } + if _, ok := tv.Type.(*types.Chan); ok { + return false + } + s := tv.Type.Underlying().String() + if s == "bool" || s == "ideal bool" || s == "error" || + s == "untyped nil" || s == "unsafe.Pointer" { + return false + } + return true + } + if !checkType(s.info.Types[v1]) || !checkType(s.info.Types[v2]) { + return nil + } + var tv types.TypeAndValue + if isConstExpr(s.info, v1) { + flags |= SonarConst1 + } else { + tv = s.info.Types[v1] + } + if isConstExpr(s.info, v2) { + flags |= SonarConst2 + } else { + tv = s.info.Types[v2] + } + if flags&SonarConst1 != 0 && flags&SonarConst2 != 0 { + return nil + } + id := int(flags) | sonarSeq<<8 + startPos := s.fset.Position(nn.Pos()) + endPos := s.fset.Position(nn.End()) + *s.blocks = append(*s.blocks, CoverBlock{sonarSeq, s.fullName, startPos.Line, startPos.Column, endPos.Line, endPos.Column, int(flags)}) + sonarSeq++ + block := &ast.BlockStmt{} + + typstr := tv.Type.String() + if strings.HasPrefix(typstr, s.pkg+".") { + typstr = typstr[len(s.pkg)+1:] + } + if idx := strings.LastIndexByte(typstr, '/'); idx != -1 { + typstr = typstr[idx+1:] + } + conv := func(name string, v ast.Expr) ast.Expr { + // Convert const to the type of the other expr. + isConst := isConstExpr(s.info, v) + badConst := false + if isConst { + c := s.info.Types[v].Value + switch c.Kind() { + case constant.Int: + if v, ok := constant.Int64Val(c); !ok || int64(int(v)) != v { + // Such const can't be used outside of its current context, + // because it will be converted to int and that will fail. + badConst = true + } + case constant.Float: + badConst = true + } + } + if badConst || isWeirdShift(s.info, v) { + v = &ast.CallExpr{ + Fun: ast.NewIdent(typstr), + Args: []ast.Expr{v}, + } + s.info.Types[v] = tv + } + if !isConst { + // Assign to a temp to avoid double side-effects. + tmp := ast.NewIdent(name) + block.List = append(block.List, &ast.AssignStmt{Tok: token.DEFINE, Lhs: []ast.Expr{tmp}, Rhs: []ast.Expr{v}}) + v = tmp + s.info.Types[v] = tv + } + return v + } + v1 = conv("__gofuzz_v1", v1) + v2 = conv("__gofuzz_v2", v2) + + block.List = append(block.List, + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: ast.NewIdent(fuzzdepPkg), Sel: ast.NewIdent("Sonar")}, + Args: []ast.Expr{v1, v2, &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(id)}}, + }, + }, + &ast.ReturnStmt{Results: []ast.Expr{&ast.BinaryExpr{Op: nn.Op, X: v1, Y: v2}}}, + ) + nn.X = &ast.CallExpr{ + Fun: &ast.FuncLit{ + Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("_go_fuzz_dep_.Bool")}}}}, + Body: block, + }, + } + nn.Y = &ast.BasicLit{Kind: token.INT, Value: "true"} + nn.Op = token.EQL + return nil +} + +func isWeirdShift(info *types.Info, n ast.Expr) bool { + w := &WeirdShiftWalker{info: info} + ast.Walk(w, n) + return w.found +} + +type WeirdShiftWalker struct { + info *types.Info + found bool +} + +func (w *WeirdShiftWalker) Visit(n ast.Node) ast.Visitor { + if bin, ok := n.(*ast.BinaryExpr); ok && (bin.Op == token.SHL || bin.Op == token.SHR) && isConstExpr(w.info, bin.X) { + w.found = true + } + return w +} + +func isConstExpr(info *types.Info, n ast.Expr) bool { + tv := info.Types[n] + if tv.Type == nil && tv.Value == nil { + panic(fmt.Sprintf("untyped expression: %#v", n)) + } + return tv.Value != nil +} + +func isCap(n ast.Expr) bool { + if call, ok := n.(*ast.CallExpr); ok { + if id, ok2 := call.Fun.(*ast.Ident); ok2 { + return id.Name == "cap" + } + } + return false +} + +func isLen(n ast.Expr) bool { + if call, ok := n.(*ast.CallExpr); ok { + if id, ok2 := call.Fun.(*ast.Ident); ok2 { + return id.Name == "len" + } + } + return false +} + +type LiteralCollector struct { + ctxt *Context + lits map[Literal]struct{} +} + +func (lc *LiteralCollector) Visit(n ast.Node) (w ast.Visitor) { + switch nn := n.(type) { + default: + return lc // recurse + case *ast.ImportSpec: + return nil + case *ast.Field: + return nil // ignore field tags + case *ast.CallExpr: + switch fn := nn.Fun.(type) { + case *ast.Ident: + if fn.Name == "panic" { + return nil + } + case *ast.SelectorExpr: + if id, ok := fn.X.(*ast.Ident); ok && (id.Name == "fmt" || id.Name == "errors") { + return nil + } + } + return lc + case *ast.BasicLit: + lit := nn.Value + switch nn.Kind { + case token.STRING: + lc.lits[Literal{lc.unquote(lit), true}] = struct{}{} + case token.CHAR: + lc.lits[Literal{lc.unquote(lit), false}] = struct{}{} + case token.INT: + if lit[0] < '0' || lit[0] > '9' { + lc.ctxt.failf("unsupported literal '%v'", lit) + } + v, err := strconv.ParseInt(lit, 0, 64) + if err != nil { + u, err := strconv.ParseUint(lit, 0, 64) + if err != nil { + lc.ctxt.failf("failed to parse int literal '%v': %v", lit, err) + } + v = int64(u) + } + var val []byte + if v >= -(1<<7) && v < 1<<8 { + val = append(val, byte(v)) + } else if v >= -(1<<15) && v < 1<<16 { + val = append(val, byte(v), byte(v>>8)) + } else if v >= -(1<<31) && v < 1<<32 { + val = append(val, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)) + } else { + val = append(val, byte(v), byte(v>>8), byte(v>>16), byte(v>>24), byte(v>>32), byte(v>>40), byte(v>>48), byte(v>>56)) + } + lc.lits[Literal{string(val), false}] = struct{}{} + } + return nil + } +} + +func trimComments(file *ast.File, fset *token.FileSet) []*ast.CommentGroup { + var comments []*ast.CommentGroup + for _, group := range file.Comments { + var list []*ast.Comment + for _, comment := range group.List { + if strings.HasPrefix(comment.Text, "//go:") && fset.Position(comment.Slash).Column == 1 { + list = append(list, comment) + } + } + if list != nil { + comments = append(comments, &ast.CommentGroup{List: list}) + } + } + return comments +} + +func initialComments(content []byte) []byte { + // Derived from go/build.Context.shouldBuild. + end := 0 + p := content + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if len(line) == 0 { // Blank line. + end = len(content) - len(p) + continue + } + if !bytes.HasPrefix(line, slashslash) { // Not comment line. + break + } + } + return content[:end] +} + +type File struct { + fset *token.FileSet + pkg string + fullName string + astFile *ast.File + blocks *[]CoverBlock + info *types.Info +} + +var slashslash = []byte("//") + +func (f *File) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + if n.Name.String() == "init" { + // Don't instrument init functions. + // They run regardless of what we do, so it is just noise. + return nil + } + case *ast.GenDecl: + if n.Tok != token.VAR { + return nil // constants and types are not interesting + } + + case *ast.BlockStmt: + // If it's a switch or select, the body is a list of case clauses; don't tag the block itself. + if len(n.List) > 0 { + switch n.List[0].(type) { + case *ast.CaseClause: // switch + for _, n := range n.List { + clause := n.(*ast.CaseClause) + clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false) + } + return f + case *ast.CommClause: // select + for _, n := range n.List { + clause := n.(*ast.CommClause) + clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false) + } + return f + } + } + n.List = f.addCounters(n.Lbrace, n.Rbrace+1, n.List, true) // +1 to step past closing brace. + case *ast.IfStmt: + if n.Init != nil { + ast.Walk(f, n.Init) + } + if n.Cond != nil { + ast.Walk(f, n.Cond) + } + ast.Walk(f, n.Body) + if n.Else == nil { + // Add else because we want coverage for "not taken". + n.Else = &ast.BlockStmt{ + Lbrace: n.Body.End(), + Rbrace: n.Body.End(), + } + } + // The elses are special, because if we have + // if x { + // } else if y { + // } + // we want to cover the "if y". To do this, we need a place to drop the counter, + // so we add a hidden block: + // if x { + // } else { + // if y { + // } + // } + switch stmt := n.Else.(type) { + case *ast.IfStmt: + block := &ast.BlockStmt{ + Lbrace: n.Body.End(), // Start at end of the "if" block so the covered part looks like it starts at the "else". + List: []ast.Stmt{stmt}, + Rbrace: stmt.End(), + } + n.Else = block + case *ast.BlockStmt: + stmt.Lbrace = n.Body.End() // Start at end of the "if" block so the covered part looks like it starts at the "else". + default: + panic("unexpected node type in if") + } + ast.Walk(f, n.Else) + return nil + case *ast.ForStmt: + // TODO: handle increment statement + case *ast.SelectStmt: + // Don't annotate an empty select - creates a syntax error. + if n.Body == nil || len(n.Body.List) == 0 { + return nil + } + case *ast.SwitchStmt: + hasDefault := false + if n.Body == nil { + n.Body = new(ast.BlockStmt) + } + for _, s := range n.Body.List { + if cas, ok := s.(*ast.CaseClause); ok && cas.List == nil { + hasDefault = true + break + } + } + if !hasDefault { + // Add default case to get additional coverage. + n.Body.List = append(n.Body.List, &ast.CaseClause{}) + } + + // Don't annotate an empty switch - creates a syntax error. + if n.Body == nil || len(n.Body.List) == 0 { + return nil + } + case *ast.TypeSwitchStmt: + // Don't annotate an empty type switch - creates a syntax error. + // TODO: add default case + if n.Body == nil || len(n.Body.List) == 0 { + return nil + } + case *ast.BinaryExpr: + if n.Op == token.LAND || n.Op == token.LOR { + // Replace: + // x && y + // with: + // x && func() T { return y } + // where T is a bool of the same type as n (and x and y). + + // Spelling T correctly is a little tricky. + // go/types gives us a canonical name for T, + // but we can't always use that canonical name in the code directly; + // in the general case, it is of the form a/b/c/d.U. + // When U is the built-in bool, or defined in the current package, + // or defined in a dot-imported package, we want just U. + // When U is in another package, we want d.U. + // When U is in another package, imported under the name e, we want e.U. + // (And when the built-in bool type is shadowed, we're just screwed.) + // Handling all of these cases correctly is hard (it requires parsing the imports), + // so we handle just the common cases. + + // types.Default maps untyped bools to typed bools. + typ := types.Default(f.info.Types[n].Type).String() + // If we're in the current package, strip the package path. + if strings.HasPrefix(typ, f.pkg+".") { + typ = typ[len(f.pkg)+1:] + } + // If we're still in a package, assume it was imported with a reasonable name. + if i := strings.LastIndexByte(typ, '/'); i >= 0 { + typ = typ[i+1:] + } + + n.Y = &ast.CallExpr{ + Fun: &ast.FuncLit{ + Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent(typ)}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{n.Y}}}}, + }, + } + } + } + return f +} + +func (f *File) addImport(path, name, anyIdent string) { + newImport := &ast.ImportSpec{ + Name: ast.NewIdent(name), + Path: &ast.BasicLit{ + Kind: token.STRING, + Value: fmt.Sprintf("%q", path), + }, + } + impDecl := &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + newImport, + }, + } + // Make the new import the first Decl in the file. + astFile := f.astFile + astFile.Decls = append(astFile.Decls, nil) + copy(astFile.Decls[1:], astFile.Decls[0:]) + astFile.Decls[0] = impDecl + astFile.Imports = append(astFile.Imports, newImport) + + // Now refer to the package, just in case it ends up unused. + // That is, append to the end of the file the declaration + // var _ = _cover_atomic_.AddUint32 + reference := &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{ + ast.NewIdent("_"), + }, + Values: []ast.Expr{ + &ast.SelectorExpr{ + X: ast.NewIdent(name), + Sel: ast.NewIdent(anyIdent), + }, + }, + }, + }, + } + astFile.Decls = append(astFile.Decls, reference) +} + +func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt { + // Special case: make sure we add a counter to an empty block. Can't do this below + // or we will add a counter to an empty statement list after, say, a return statement. + if len(list) == 0 { + return []ast.Stmt{f.newCounter(pos, blockEnd, 0)} + } + // We have a block (statement list), but it may have several basic blocks due to the + // appearance of statements that affect the flow of control. + var newList []ast.Stmt + for { + // Find first statement that affects flow of control (break, continue, if, etc.). + // It will be the last statement of this basic block. + var last int + end := blockEnd + for last = 0; last < len(list); last++ { + end = f.statementBoundary(list[last]) + if f.endsBasicSourceBlock(list[last]) { + extendToClosingBrace = false // Block is broken up now. + last++ + break + } + } + if extendToClosingBrace { + end = blockEnd + } + if pos != end { // Can have no source to cover if e.g. blocks abut. + newList = append(newList, f.newCounter(pos, end, last)) + } + newList = append(newList, list[0:last]...) + list = list[last:] + if len(list) == 0 { + break + } + pos = list[0].Pos() + } + return newList +} + +func (f *File) endsBasicSourceBlock(s ast.Stmt) bool { + switch s := s.(type) { + case *ast.BlockStmt: + // Treat blocks like basic blocks to avoid overlapping counters. + return true + case *ast.BranchStmt: + return true + case *ast.ForStmt: + return true + case *ast.IfStmt: + return true + case *ast.LabeledStmt: + return f.endsBasicSourceBlock(s.Stmt) + case *ast.RangeStmt: + return true + case *ast.SwitchStmt: + return true + case *ast.SelectStmt: + return true + case *ast.TypeSwitchStmt: + return true + case *ast.ExprStmt: + // Calls to panic change the flow. + // We really should verify that "panic" is the predefined function, + // but without type checking we can't and the likelihood of it being + // an actual problem is vanishingly small. + if call, ok := s.X.(*ast.CallExpr); ok { + if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 { + return true + } + } + } + found, _ := hasFuncLiteral(s) + return found +} + +func (f *File) statementBoundary(s ast.Stmt) token.Pos { + // Control flow statements are easy. + switch s := s.(type) { + case *ast.BlockStmt: + // Treat blocks like basic blocks to avoid overlapping counters. + return s.Lbrace + case *ast.IfStmt: + found, pos := hasFuncLiteral(s.Init) + if found { + return pos + } + found, pos = hasFuncLiteral(s.Cond) + if found { + return pos + } + return s.Body.Lbrace + case *ast.ForStmt: + found, pos := hasFuncLiteral(s.Init) + if found { + return pos + } + found, pos = hasFuncLiteral(s.Cond) + if found { + return pos + } + found, pos = hasFuncLiteral(s.Post) + if found { + return pos + } + return s.Body.Lbrace + case *ast.LabeledStmt: + return f.statementBoundary(s.Stmt) + case *ast.RangeStmt: + found, pos := hasFuncLiteral(s.X) + if found { + return pos + } + return s.Body.Lbrace + case *ast.SwitchStmt: + found, pos := hasFuncLiteral(s.Init) + if found { + return pos + } + found, pos = hasFuncLiteral(s.Tag) + if found { + return pos + } + return s.Body.Lbrace + case *ast.SelectStmt: + return s.Body.Lbrace + case *ast.TypeSwitchStmt: + found, pos := hasFuncLiteral(s.Init) + if found { + return pos + } + return s.Body.Lbrace + } + found, pos := hasFuncLiteral(s) + if found { + return pos + } + return s.End() +} + +var counterGen uint32 + +func genCounter() int { + counterGen++ + id := counterGen + buf := []byte{byte(id), byte(id >> 8), byte(id >> 16), byte(id >> 24)} + hash := sha1.Sum(buf) + return int(uint16(hash[0]) | uint16(hash[1])<<8) +} + +func (f *File) newCounter(start, end token.Pos, numStmt int) ast.Stmt { + cnt := genCounter() + + if f.blocks != nil { + s := f.fset.Position(start) + e := f.fset.Position(end) + *f.blocks = append(*f.blocks, CoverBlock{cnt, f.fullName, s.Line, s.Column, e.Line, e.Column, numStmt}) + } + + idx := &ast.BasicLit{ + Kind: token.INT, + Value: strconv.Itoa(cnt), + } + counter := &ast.IndexExpr{ + X: &ast.SelectorExpr{ + X: ast.NewIdent(fuzzdepPkg), + Sel: ast.NewIdent("CoverTab"), + }, + Index: idx, + } + return &ast.IncDecStmt{ + X: counter, + Tok: token.INC, + } +} + +func (f *File) print(w io.Writer) { + cfg := printer.Config{ + Mode: printer.SourcePos, + Tabwidth: 8, + Indent: 0, + } + cfg.Fprint(w, f.fset, f.astFile) +} + +type funcLitFinder token.Pos + +func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) { + if f.found() { + return nil // Prune search. + } + switch n := node.(type) { + case *ast.FuncLit: + *f = funcLitFinder(n.Body.Lbrace) + return nil // Prune search. + } + return f +} + +func (f *funcLitFinder) found() bool { + return token.Pos(*f) != token.NoPos +} + +func hasFuncLiteral(n ast.Node) (bool, token.Pos) { + if n == nil { + return false, 0 + } + var literal funcLitFinder + ast.Walk(&literal, n) + return literal.found(), token.Pos(literal) +} + +func (lc *LiteralCollector) unquote(s string) string { + t, err := strconv.Unquote(s) + if err != nil { + lc.ctxt.failf("cover: improperly quoted string %q\n", s) + } + return t +} diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/main.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/main.go new file mode 100644 index 000000000..dc43ac2b4 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/main.go @@ -0,0 +1,882 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package main + +import ( + "archive/zip" + "bytes" + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "runtime/pprof" + "strings" + "text/template" + "unicode" + "unicode/utf8" + + "golang.org/x/tools/go/packages" + + . "github.com/dvyukov/go-fuzz/internal/go-fuzz-types" +) + +var ( + flagTag = flag.String("tags", "", "a space-separated list of build tags to consider satisfied during the build") + flagOut = flag.String("o", "", "output file") + flagFunc = flag.String("func", "", "preferred entry function") + flagWork = flag.Bool("work", false, "don't remove working directory") + flagRace = flag.Bool("race", false, "enable race detector") + flagCPU = flag.Bool("cpuprofile", false, "generate cpu profile in cpu.pprof") + flagLibFuzzer = flag.Bool("libfuzzer", false, "output static archive for use with libFuzzer") + flagBuildX = flag.Bool("x", false, "print the commands if build fails") + flagPreserve = flag.String("preserve", "", "a comma-separated list of import paths not to instrument") +) + +func makeTags() string { + tags := "gofuzz" + if *flagLibFuzzer { + tags += " gofuzz_libfuzzer" + } + if *flagRace { + tags += " race" + } + if len(*flagTag) > 0 { + tags += " " + *flagTag + } + return tags +} + +// basePackagesConfig returns a base golang.org/x/tools/go/packages.Config +// that clients can then modify and use for calls to go/packages. +func basePackagesConfig() *packages.Config { + cfg := new(packages.Config) + + // Note that we do not set GO111MODULE here in order to respect any GO111MODULE + // setting by the user as we are finding dependencies. Note, however, that + // we are still setting up a GOPATH to build, so we later will force + // GO111MODULE to be off when building so that we are in GOPATH mode. + // If the user has not set GO111MODULE, the meaning here is + // left up to cmd/go (defaulting to 'auto' in Go 1.11-1.13, + // but likely defaulting to 'on' at some point during Go 1.14 + // development cycle). + // Also note that we are leaving the overall cfg structure + // in place to support future experimentation, etc. + cfg.Env = os.Environ() + return cfg +} + +// checkModVendor reports if the GOFLAGS env variable +// contains -mod=vendor, which enables vendoring for modules. +func checkModVendor() bool { + val := os.Getenv("GOFLAGS") + for _, s := range strings.Split(val, " ") { + if s == "-mod=vendor" { + return true + } + } + return false +} + +// main copies the package with all dependent packages into a temp dir, +// instruments Go source files there, and builds setting GOROOT to the temp dir. +func main() { + flag.Parse() + c := new(Context) + + if flag.NArg() > 1 { + c.failf("usage: go-fuzz-build [pkg]") + } + + pkg := "." + if flag.NArg() == 1 { + pkg = flag.Arg(0) + } + if *flagFunc != "" && !isFuzzFuncName(*flagFunc) { + c.failf("provided -func=%v, but %v is not a fuzz function name", *flagFunc, *flagFunc) + } + if *flagLibFuzzer && *flagRace { + c.failf("-race and -libfuzzer are incompatible") + } + if checkModVendor() { + // We don't support -mod=vendor with modules. + // Part of the issue is go-fuzz-dep and go-fuzz-defs + // won't be in the user's vendor directory. + c.failf("GOFLAGS with -mod=vendor is not supported") + } + + c.startProfiling() // start pprof as requested + c.loadPkg(pkg) // load and typecheck pkg + c.getEnv() // discover GOROOT, GOPATH + c.loadStd() // load standard library + c.calcIgnore() // calculate set of packages to ignore + c.makeWorkdir() // create workdir + defer c.cleanup() // delete workdir as needed, etc. + c.populateWorkdir() // copy tools and packages to workdir as needed + + if *flagOut == "" { + ext := ".zip" + if *flagLibFuzzer { + ext = ".a" + } + *flagOut = c.pkgs[0].Name + "-fuzz" + ext + } + + // Gather literals, instrument, and compile. + // Order matters here! + // buildInstrumentedBinary (and instrumentPackages) modify the AST. + // (We don't want to re-parse and re-typecheck every time, for performance.) + // So we gather literals first, while the AST is pristine. + // Then we add coverage and build. + // Then we add sonar and build. + // TODO: migrate to use cmd/internal/edit instead of AST modification. + // This has several benefits: (1) It is easier to work with. + // (2) 'go cover' has switched to it; we would get the benefit of + // upstream bug fixes, of which there has been at least one (around gotos and labels). + // (3) It leaves the AST intact, so we are less order-sensitive. + // The primary blocker is that we want good line numbers for when we find crashers. + // go/printer handles this automatically using Mode printer.SourcePos. + // We'd need to implement that support ourselves. (It's do-able but non-trivial.) + // See also https://golang.org/issue/29824. + lits := c.gatherLiterals() + var blocks, sonar []CoverBlock + + if *flagLibFuzzer { + archive := c.buildInstrumentedBinary(&blocks, nil) + c.moveFile(archive, *flagOut) + return + } + + coverBin := c.buildInstrumentedBinary(&blocks, nil) + sonarBin := c.buildInstrumentedBinary(nil, &sonar) + metaData := c.createMeta(lits, blocks, sonar) + defer func() { + os.Remove(coverBin) + os.Remove(sonarBin) + os.Remove(metaData) + }() + + outf, err := os.Create(*flagOut) + if err != nil { + c.failf("failed to create output file: %v", err) + } + zipw := zip.NewWriter(outf) + zipFile := func(name, datafile string) { + w, err := zipw.Create(name) + if err != nil { + c.failf("failed to create zip file: %v", err) + } + f, err := os.Open(datafile) + if err != nil { + c.failf("failed to open data file %v", datafile) + } + if _, err := io.Copy(w, f); err != nil { + c.failf("failed to write %v to zip file: %v", datafile, err) + } + // best effort: close and remove our temp file + f.Close() + os.Remove(datafile) + } + zipFile("cover.exe", coverBin) + zipFile("sonar.exe", sonarBin) + zipFile("metadata", metaData) + if err := zipw.Close(); err != nil { + c.failf("failed to close zip file: %v", err) + } + if err := outf.Close(); err != nil { + c.failf("failed to close out file: %v", err) + } +} + +// Context holds state for a go-fuzz-build run. +type Context struct { + fuzzpkg *packages.Package // package containing Fuzz function + pkgs []*packages.Package // typechecked root packages + + std map[string]bool // set of packages in the standard library + ignore map[string]bool // set of packages to ignore during instrumentation + + allFuncs []string // all fuzz functions found in package + + workdir string + GOROOT string + GOPATH string + + cpuprofile *os.File + + cmdGoHasTrimPath bool // does the active version of cmd/go have the -trimpath flag? +} + +// getEnv determines GOROOT and GOPATH and updates c accordingly. +func (c *Context) getEnv() { + env := map[string]string{ + "GOROOT": "", + "GOPATH": "", + } + for k := range env { + v := os.Getenv(k) + if v != "" { + env[k] = v + continue + } + // TODO: make a single call ("go env GOROOT GOPATH") instead + out, err := exec.Command("go", "env", k).CombinedOutput() + if err != nil || len(out) == 0 { + c.failf("%s is not set and failed to locate it: 'go env %s' returned '%s' (%v)", k, k, out, err) + } + env[k] = strings.TrimSpace(string(out)) + } + c.GOROOT = env["GOROOT"] + c.GOPATH = env["GOPATH"] + + out, err := exec.Command("go", "list", "-f", "'{{context.ReleaseTags}}'", "runtime").CombinedOutput() + if err != nil || len(out) == 0 { + c.failf("go list -f '{{context.ReleaseTags}}' runtime returned '%s' (%v)", out, err) + } + c.cmdGoHasTrimPath = bytes.Contains(out, []byte("go1.13")) +} + +// startProfiling starts pprof profiling, if requested. +func (c *Context) startProfiling() { + if !*flagCPU { + return + } + var err error + c.cpuprofile, err = os.Create("cpu.pprof") + if err != nil { + c.failf("could not create cpu profile: %v", err) + } + pprof.StartCPUProfile(c.cpuprofile) +} + +// loadPkg loads, parses, and typechecks pkg (the package containing the Fuzz function), +// go-fuzz-dep, and their dependencies. +func (c *Context) loadPkg(pkg string) { + // Resolve pkg. + // See https://golang.org/issue/30826 and https://golang.org/issue/30828. + rescfg := basePackagesConfig() + rescfg.Mode = packages.NeedName + rescfg.BuildFlags = []string{"-tags", makeTags()} + respkgs, err := packages.Load(rescfg, pkg) + if err != nil { + c.failf("could not resolve package %q: %v", pkg, err) + } + if len(respkgs) != 1 { + paths := make([]string, len(respkgs)) + for i, p := range respkgs { + paths[i] = p.PkgPath + } + c.failf("cannot build multiple packages, but %q resolved to: %v", pkg, strings.Join(paths, ", ")) + } + if respkgs[0].Name == "main" { + c.failf("cannot fuzz package main") + } + pkgpath := respkgs[0].PkgPath + + // Load, parse, and type-check all packages. + // We'll use the type information later. + // This also provides better error messages in the case + // of invalid code than trying to compile instrumented code. + cfg := basePackagesConfig() + cfg.Mode = packages.LoadAllSyntax + cfg.BuildFlags = []string{"-tags", makeTags()} + // use custom ParseFile in order to get comments + cfg.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { + return parser.ParseFile(fset, filename, src, parser.ParseComments) + } + // We need to load: + // * the target package, obviously + // * go-fuzz-dep, since we use it for instrumentation + // * reflect, if we are using libfuzzer, since its generated main function requires it + loadpkgs := []string{pkg, "github.com/dvyukov/go-fuzz/go-fuzz-dep"} + if *flagLibFuzzer { + loadpkgs = append(loadpkgs, "reflect") + } + initial, err := packages.Load(cfg, loadpkgs...) + if err != nil { + c.failf("could not load packages: %v", err) + } + + // Stop if any package had errors. + if packages.PrintErrors(initial) > 0 { + c.failf("typechecking of %v failed", pkg) + } + + c.pkgs = initial + + // Find the fuzz package among c.pkgs. + for _, p := range initial { + if p.PkgPath == pkgpath { + c.fuzzpkg = p + break + } + } + if c.fuzzpkg == nil { + c.failf("internal error: failed to find fuzz package; please file an issue") + } + + // Find all fuzz functions in fuzzpkg. + foundFlagFunc := false + s := c.fuzzpkg.Types.Scope() + for _, n := range s.Names() { + if !isFuzzFuncName(n) { + continue + } + // Check that n is a function with an appropriate signature. + typ := s.Lookup(n).Type() + sig, ok := typ.(*types.Signature) + if !ok || sig.Variadic() || !isFuzzSig(sig) { + if n == *flagFunc { + c.failf("provided -func=%v, but %v is not a fuzz function", *flagFunc, *flagFunc) + } + continue + } + // n is a fuzz function. + c.allFuncs = append(c.allFuncs, n) + foundFlagFunc = foundFlagFunc || n == *flagFunc + } + + if len(c.allFuncs) == 0 { + c.failf("could not find any fuzz functions in %v", c.fuzzpkg.PkgPath) + } + if len(c.allFuncs) > 255 { + c.failf("go-fuzz-build supports a maximum of 255 fuzz functions, found %v; please file an issue", len(c.allFuncs)) + } + + if *flagFunc != "" { + // Specific fuzz function requested. + // If the requested function doesn't exist, fail. + if !foundFlagFunc { + c.failf("could not find fuzz function %v in %v", *flagFunc, c.fuzzpkg.PkgPath) + } + } else { + // No specific fuzz function requested. + // If there's only one fuzz function, mark it as preferred. + // If there's more than one... + // ...for go-fuzz, that's fine; one can be specified later on the command line. + // ...for libfuzzer, that's not fine, as there is no way to specify one later. + if len(c.allFuncs) == 1 { + *flagFunc = c.allFuncs[0] + } else if *flagLibFuzzer { + c.failf("must specify a fuzz function with -libfuzzer, found: %v", strings.Join(c.allFuncs, ", ")) + } + } +} + +// isFuzzSig reports whether sig is of the form +// func FuzzFunc(data []byte) int +func isFuzzSig(sig *types.Signature) bool { + return tupleHasTypes(sig.Params(), "[]byte") && tupleHasTypes(sig.Results(), "int") +} + +// tupleHasTypes reports whether tuple is composed of +// elements with exactly the types in types. +func tupleHasTypes(tuple *types.Tuple, types ...string) bool { + if tuple.Len() != len(types) { + return false + } + for i, t := range types { + if tuple.At(i).Type().String() != t { + return false + } + } + return true +} + +func isFuzzFuncName(name string) bool { + return isTest(name, "Fuzz") +} + +// isTest is copied verbatim, along with its name, +// from GOROOT/src/cmd/go/internal/load/test.go. +// isTest tells whether name looks like a test (or benchmark, according to prefix). +// It is a Test (say) if there is a character after Test that is not a lower-case letter. +// We don't want TesticularCancer. +func isTest(name, prefix string) bool { + if !strings.HasPrefix(name, prefix) { + return false + } + if len(name) == len(prefix) { // "Test" is ok + return true + } + rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(rune) +} + +// loadStd finds the set of standard library package paths. +func (c *Context) loadStd() { + // Find out what packages are in the standard library. + cfg := basePackagesConfig() + cfg.Mode = packages.NeedName + stdpkgs, err := packages.Load(cfg, "std") + if err != nil { + c.failf("could not load standard library: %v", err) + } + c.std = make(map[string]bool, len(stdpkgs)) + for _, p := range stdpkgs { + c.std[p.PkgPath] = true + } +} + +// makeWorkdir creates the workdir, logging as requested. +func (c *Context) makeWorkdir() { + // TODO: make workdir stable, so that we can use cmd/go's build cache? + // See https://github.com/golang/go/issues/29430. + var err error + c.workdir, err = ioutil.TempDir("", "go-fuzz-build") + if err != nil { + c.failf("failed to create temp dir: %v", err) + } + if *flagWork { + fmt.Printf("workdir: %v\n", c.workdir) + } +} + +// cleanup ensures a clean exit. It should be called on all (controllable) exit paths. +func (c *Context) cleanup() { + if !*flagWork && c.workdir != "" { + os.RemoveAll(c.workdir) + } + if c.cpuprofile != nil { + pprof.StopCPUProfile() + c.cpuprofile.Close() + } +} + +// populateWorkdir prepares workdir for builds. +func (c *Context) populateWorkdir() { + // TODO: instead of reconstructing the world, + // can we use a bunch of replace directives in a go.mod? + + // TODO: make all this I/O concurrent (up to a limit). + // It's a non-trivial part of build time. + // Question: Do it here or in copyDir? + + // TODO: See if we can avoid making toolchain copies, + // using some combination of env vars and toolexec. + if *flagLibFuzzer || *flagRace { + c.copyDir(filepath.Join(c.GOROOT, "src", "runtime", "cgo"), filepath.Join(c.workdir, "goroot", "src", "runtime", "cgo")) + } + if *flagRace { + c.copyDir(filepath.Join(c.GOROOT, "src", "runtime", "race"), filepath.Join(c.workdir, "goroot", "src", "runtime", "race")) + c.copyDir(filepath.Join(c.GOROOT, "src", "sync", "atomic"), filepath.Join(c.workdir, "goroot", "src", "sync", "atomic")) + } + c.copyDir(filepath.Join(c.GOROOT, "pkg", "tool"), filepath.Join(c.workdir, "goroot", "pkg", "tool")) + if _, err := os.Stat(filepath.Join(c.GOROOT, "pkg", "include")); err == nil { + c.copyDir(filepath.Join(c.GOROOT, "pkg", "include"), filepath.Join(c.workdir, "goroot", "pkg", "include")) + } else { + // Cross-compilation is not implemented. + c.copyDir(filepath.Join(c.GOROOT, "pkg", runtime.GOOS+"_"+runtime.GOARCH), filepath.Join(c.workdir, "goroot", "pkg", runtime.GOOS+"_"+runtime.GOARCH)) + } + + // Clone our package, go-fuzz-deps, and all dependencies. + // TODO: we might not need to do this for all packages. + // We know that we'll be writing out instrumented Go code later; + // we could instead just os.MkdirAll and copy non-Go files here. + // We'd still need to do a full package clone for packages that + // we aren't instrumenting (c.ignore). + packages.Visit(c.pkgs, nil, func(p *packages.Package) { + c.clonePackage(p) + }) + c.copyFuzzDep() +} + +func (c *Context) createMeta(lits map[Literal]struct{}, blocks []CoverBlock, sonar []CoverBlock) string { + meta := MetaData{Blocks: blocks, Sonar: sonar, Funcs: c.allFuncs, DefaultFunc: *flagFunc} + for k := range lits { + meta.Literals = append(meta.Literals, k) + } + data, err := json.Marshal(meta) + if err != nil { + c.failf("failed to serialize meta information: %v", err) + } + f := c.tempFile() + c.writeFile(f, data) + return f +} + +func (c *Context) buildInstrumentedBinary(blocks *[]CoverBlock, sonar *[]CoverBlock) string { + c.instrumentPackages(blocks, sonar) + mainPkg := c.createFuzzMain() + outf := c.tempFile() + args := []string{"build", "-tags", makeTags()} + if *flagBuildX { + args = append(args, "-x") + + if *flagWork { + args = append(args, "-work") + } + } + if *flagRace { + args = append(args, "-race") + } + if *flagLibFuzzer { + args = append(args, "-buildmode=c-archive") + } + if c.cmdGoHasTrimPath { + args = append(args, "-trimpath") + } + args = append(args, "-o", outf, mainPkg) + cmd := exec.Command("go", args...) + + // We are constructing a GOPATH environment, so while building + // we force GOPATH mode here via GO111MODULE=off. + cmd.Env = append(os.Environ(), + "GOROOT="+filepath.Join(c.workdir, "goroot"), + "GOPATH="+filepath.Join(c.workdir, "gopath"), + "GO111MODULE=off", + ) + if out, err := cmd.CombinedOutput(); err != nil { + c.failf("failed to execute go build: %v\n%v", err, string(out)) + } + return outf +} + +func (c *Context) calcIgnore() { + // No reason to instrument these. + c.ignore = map[string]bool{ + "runtime/cgo": true, + "runtime/pprof": true, + "runtime/race": true, + } + + // Roots: must not instrument these, nor any of their dependencies, to avoid import cycles. + // Fortunately, these are mostly packages that are non-deterministic, + // noisy (because they are low level), and/or not interesting. + // We could manually maintain this list, but that makes go-fuzz-build + // fragile in the face of internal standard library package changes. + roots := c.packagesNamed("runtime", "github.com/dvyukov/go-fuzz/go-fuzz-dep") + packages.Visit(roots, func(p *packages.Package) bool { + c.ignore[p.PkgPath] = true + return true + }, nil) + + // Ignore any packages requested explicitly by the user. + paths := strings.Split(*flagPreserve, ",") + for _, path := range paths { + c.ignore[path] = true + } +} + +func (c *Context) gatherLiterals() map[Literal]struct{} { + nolits := map[string]bool{ + "math": true, + "os": true, + "unicode": true, + } + + lits := make(map[Literal]struct{}) + visit := func(pkg *packages.Package) { + if c.ignore[pkg.PkgPath] || nolits[pkg.PkgPath] { + return + } + for _, f := range pkg.Syntax { + ast.Walk(&LiteralCollector{lits: lits, ctxt: c}, f) + } + } + + packages.Visit(c.pkgs, nil, visit) + return lits +} + +func (c *Context) copyFuzzDep() { + // Standard library packages can't depend on non-standard ones. + // So we pretend that go-fuzz-dep is a standard one. + // go-fuzz-dep depends on go-fuzz-defs, which creates a problem. + // Fortunately (and intentionally), go-fuzz-defs contains only constants, + // which can be duplicated safely. + // So we eliminate the import statement and copy go-fuzz-defs/defs.go + // directly into the go-fuzz-dep package. + newDir := filepath.Join(c.workdir, "goroot", "src", "go-fuzz-dep") + c.mkdirAll(newDir) + dep := c.packageNamed("github.com/dvyukov/go-fuzz/go-fuzz-dep") + for _, f := range dep.GoFiles { + data := c.readFile(f) + // Eliminate the dot import. + data = bytes.Replace(data, []byte(`. "github.com/dvyukov/go-fuzz/go-fuzz-defs"`), nil, -1) + c.writeFile(filepath.Join(newDir, filepath.Base(f)), data) + } + + defs := c.packageNamed("github.com/dvyukov/go-fuzz/go-fuzz-defs") + for _, f := range defs.GoFiles { + data := c.readFile(f) + // Adjust package name to match go-fuzz-deps. + data = bytes.Replace(data, []byte("\npackage base"), []byte("\npackage gofuzzdep"), -1) + c.writeFile(filepath.Join(newDir, "defs.go"), data) + } +} + +func (c *Context) funcMain() []byte { + t := mainSrc + if *flagLibFuzzer { + t = mainSrcLibFuzzer + } + dot := map[string]interface{}{"Pkg": c.fuzzpkg.PkgPath, "AllFuncs": c.allFuncs, "DefaultFunc": *flagFunc} + buf := new(bytes.Buffer) + if err := t.Execute(buf, dot); err != nil { + c.failf("could not execute template: %v", err) + } + return buf.Bytes() +} + +func (c *Context) createFuzzMain() string { + mainPkg := filepath.Join(c.fuzzpkg.PkgPath, "go.fuzz.main") + path := filepath.Join(c.workdir, "gopath", "src", mainPkg) + c.mkdirAll(path) + c.writeFile(filepath.Join(path, "main.go"), c.funcMain()) + return mainPkg +} + +func (c *Context) clonePackage(p *packages.Package) { + root := "goroot" + if !c.std[p.PkgPath] { + root = "gopath" + } + newDir := filepath.Join(c.workdir, root, "src", p.PkgPath) + c.mkdirAll(newDir) + + if p.PkgPath == "unsafe" { + // Write a dummy file. go/packages explicitly returns an empty GoFiles for it, + // for reasons that are unclear, but cmd/go wants there to be a Go file in the package. + c.writeFile(filepath.Join(newDir, "unsafe.go"), []byte(`package unsafe`)) + return + } + + // Copy all the source code. + + // Use GoFiles instead of CompiledGoFiles here. + // If we use CompiledGoFiles, we end up with code that cmd/go won't compile. + // See https://golang.org/issue/30479 and Context.instrumentPackages. + for _, f := range p.GoFiles { + dst := filepath.Join(newDir, filepath.Base(f)) + c.copyFile(f, dst) + } + for _, f := range p.OtherFiles { + dst := filepath.Join(newDir, filepath.Base(f)) + c.copyFile(f, dst) + } + + // TODO: do we need to look for and copy go.mod? +} + +// packageNamed extracts the package listed in path. +func (c *Context) packageNamed(path string) (pkgs *packages.Package) { + all := c.packagesNamed(path) + if len(all) != 1 { + c.failf("got multiple packages, requested only %v", path) + } + return all[0] +} + +// packagesNamed extracts the packages listed in paths. +func (c *Context) packagesNamed(paths ...string) (pkgs []*packages.Package) { + pre := func(p *packages.Package) bool { + for _, path := range paths { + if p.PkgPath == path { + pkgs = append(pkgs, p) + break + } + } + return len(pkgs) < len(paths) // continue only if we have not succeeded yet + } + packages.Visit(c.pkgs, pre, nil) + return pkgs +} + +func (c *Context) instrumentPackages(blocks *[]CoverBlock, sonar *[]CoverBlock) { + visit := func(pkg *packages.Package) { + if c.ignore[pkg.PkgPath] { + return + } + + root := "goroot" + if !c.std[pkg.PkgPath] { + root = "gopath" + } + path := filepath.Join(c.workdir, root, "src", pkg.PkgPath) // TODO: need filepath.FromSlash for pkg.PkgPath? + + for i, fullName := range pkg.CompiledGoFiles { + fname := filepath.Base(fullName) + if !strings.HasSuffix(fname, ".go") { + // This is a cgo-generated file. + // Instrumenting it currently does not work. + // We copied the original Go file as part of copyPackageRewrite, + // so we can just skip this one. + // See https://golang.org/issue/30479. + continue + } + f := pkg.Syntax[i] + + // TODO: rename trimComments? + f.Comments = trimComments(f, pkg.Fset) + + buf := new(bytes.Buffer) + content := c.readFile(fullName) + buf.Write(initialComments(content)) // Retain '// +build' directives. + instrument(pkg.PkgPath, fullName, pkg.Fset, f, pkg.TypesInfo, buf, blocks, sonar) + tmp := c.tempFile() + c.writeFile(tmp, buf.Bytes()) + outpath := filepath.Join(path, fname) + if runtime.GOOS == "windows" { + os.Remove(outpath) + } + c.moveFile(tmp, outpath) + } + } + + packages.Visit(c.pkgs, nil, visit) +} + +func (c *Context) copyDir(dir, newDir string) { + c.mkdirAll(newDir) + files, err := ioutil.ReadDir(dir) + if err != nil { + c.failf("failed to scan dir '%v': %v", dir, err) + } + for _, f := range files { + if f.IsDir() { + c.copyDir(filepath.Join(dir, f.Name()), filepath.Join(newDir, f.Name())) + continue + } + src := filepath.Join(dir, f.Name()) + dst := filepath.Join(newDir, f.Name()) + c.copyFile(src, dst) + } +} + +func (c *Context) copyFile(src, dst string) { + r, err := os.Open(src) + if err != nil { + c.failf("copyFile: could not read %v", src, err) + } + w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700) + if err != nil { + c.failf("copyFile: could not write %v: %v", dst, err) + } + if _, err := io.Copy(w, r); err != nil { + c.failf("copyFile: copying failed: %v", err) + } + if err := r.Close(); err != nil { + c.failf("copyFile: closing %v failed: %v", src, err) + } + if err := w.Close(); err != nil { + c.failf("copyFile: closing %v failed: %v", dst, err) + } +} + +func (c *Context) moveFile(src, dst string) { + c.copyFile(src, dst) + err := os.Remove(src) + if err != nil { + c.failf("moveFile: removing %q failed: %v", src, err) + } +} + +func (c *Context) failf(str string, args ...interface{}) { + c.cleanup() + fmt.Fprintf(os.Stderr, str+"\n", args...) + os.Exit(1) +} + +// tempFile creates and deletes a temp file, and returns its path. +// This is helpful when you need a temp path for an output file +// that will be created by an external tool (go build) or by a call to writeFile. +func (c *Context) tempFile() string { + outf, err := ioutil.TempFile("", "go-fuzz") + if err != nil { + c.failf("failed to create temp file: %v", err) + } + outf.Close() + os.Remove(outf.Name()) // necessary on Windows + return outf.Name() +} + +func (c *Context) readFile(name string) []byte { + data, err := ioutil.ReadFile(name) + if err != nil { + c.failf("failed to read temp file: %v", err) + } + return data +} + +func (c *Context) writeFile(name string, data []byte) { + if err := ioutil.WriteFile(name, data, 0700); err != nil { + c.failf("failed to write temp file: %v", err) + } +} + +func (c *Context) mkdirAll(dir string) { + if err := os.MkdirAll(dir, 0700); err != nil { + c.failf("failed to create temp dir: %v", err) + } +} + +var mainSrc = template.Must(template.New("main").Parse(` +package main + +import ( + target "{{.Pkg}}" + dep "go-fuzz-dep" +) + +func main() { + fns := []func([]byte)int { + {{range .AllFuncs}} + target.{{.}}, + {{end}} + } + dep.Main(fns) +} +`)) + +var mainSrcLibFuzzer = template.Must(template.New("main").Parse(` +package main + +import ( + "unsafe" + "reflect" + target "{{.Pkg}}" + dep "go-fuzz-dep" +) + +// #cgo CFLAGS: -Wall -Werror +// #ifdef __linux__ +// __attribute__((weak, section("__libfuzzer_extra_counters"))) +// #else +// #error Currently only Linux is supported +// #endif +// unsigned char GoFuzzCoverageCounters[65536]; +import "C" + +//export LLVMFuzzerInitialize +func LLVMFuzzerInitialize(argc uintptr, argv uintptr) int { + dep.Initialize(unsafe.Pointer(&C.GoFuzzCoverageCounters[0]), 65536) + return 0 +} + +//export LLVMFuzzerTestOneInput +func LLVMFuzzerTestOneInput(data uintptr, size uint64) int { + sh := &reflect.SliceHeader{ + Data: data, + Len: int(size), + Cap: int(size), + } + + input := *(*[]byte)(unsafe.Pointer(sh)) + target.{{.DefaultFunc}}(input) + + return 0 +} + +func main() { +} +`)) diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-defs/defs.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-defs/defs.go new file mode 100644 index 000000000..b66d5abdf --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-defs/defs.go @@ -0,0 +1,36 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// Package defs provides constants required by go-fuzz-build, go-fuzz, and instrumented code. +package base + +// This package has a special interaction with go-fuzz-dep: +// It is copied into a package with it by go-fuzz-build. +// Only things that can be safely duplicated without confusion, +// like constants, should be added to this package. +// And any additions should be tested carefully. :) + +const ( + CoverSize = 64 << 10 + MaxInputSize = 1 << 20 + SonarRegionSize = 1 << 20 +) + +const ( + SonarEQL = iota + SonarNEQ + SonarLSS + SonarGTR + SonarLEQ + SonarGEQ + + SonarOpMask = 7 + SonarLength = 1 << 3 + SonarSigned = 1 << 4 + SonarString = 1 << 5 + SonarConst1 = 1 << 6 + SonarConst2 = 1 << 7 + + SonarHdrLen = 6 + SonarMaxLen = 20 +) diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/cover.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/cover.go new file mode 100644 index 000000000..46604ad5a --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/cover.go @@ -0,0 +1,22 @@ +// Copyright 2019 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// +build gofuzz + +package gofuzzdep + +import ( + . "github.com/dvyukov/go-fuzz/go-fuzz-defs" +) + +// Bool is just a bool. +// It is used by code autogenerated by go-fuzz-build +// to avoid compilation errors when a user's code shadows the built-in bool. +type Bool = bool + +// CoverTab holds code coverage. +// It is initialized to a new array so that instrumentation +// executed during process initialization has somewhere to write to. +// It is replaced by a newly initialized array when it is +// time for actual instrumentation to commence. +var CoverTab = new([CoverSize]byte) diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/doc.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/doc.go new file mode 100644 index 000000000..c8adedb38 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/doc.go @@ -0,0 +1,10 @@ +// Package gofuzzdep contains the business logic used to monitor the fuzzing. +// +// It is handled specially by go-fuzz-build; see the comments in package go-fuzz-defs. +// +// Be particularly careful about adding imports to go-fuzz-dep: +// Any package imported by go-fuzz-dep cannot be instrumented (on pain of import cycles), +// which reduces the effectiveness of go-fuzz on any other package that imports it. +// That is why (e.g.) there are hand-rolled serialization functions instead of using encoding/binary, +// and hand-rolled syscall-based communication instead of using package net or os. +package gofuzzdep diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main.go new file mode 100644 index 000000000..09bb3c581 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main.go @@ -0,0 +1,99 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// +build gofuzz +// +build !gofuzz_libfuzzer + +package gofuzzdep + +import ( + "runtime" + "sync/atomic" + "syscall" + "time" + "unsafe" + + . "github.com/dvyukov/go-fuzz/go-fuzz-defs" +) + +func Main(fns []func([]byte) int) { + mem, inFD, outFD := setupCommFile() + CoverTab = (*[CoverSize]byte)(unsafe.Pointer(&mem[0])) + input := mem[CoverSize : CoverSize+MaxInputSize] + sonarRegion = mem[CoverSize+MaxInputSize:] + runtime.GOMAXPROCS(1) // makes coverage more deterministic, we parallelize on higher level + for { + fnidx, n := read(inFD) + if n > uint64(len(input)) { + println("invalid input length") + syscall.Exit(1) + } + for i := range CoverTab { + CoverTab[i] = 0 + } + atomic.StoreUint32(&sonarPos, 0) + t0 := time.Now() + res := fns[fnidx](input[:n:n]) + ns := time.Since(t0) + write(outFD, uint64(res), uint64(ns), uint64(atomic.LoadUint32(&sonarPos))) + } +} + +// read reads little-endian-encoded uint8+uint64 from fd. +func read(fd FD) (uint8, uint64) { + rd := 0 + var buf [9]byte + for rd != len(buf) { + n, err := fd.read(buf[rd:]) + if err == syscall.EINTR { + continue + } + if n == 0 { + syscall.Exit(1) + } + if err != nil { + println("failed to read fd =", fd, "errno =", err.(syscall.Errno)) + syscall.Exit(1) + } + rd += n + } + return buf[0], deserialize64(buf[1:]) +} + +// write writes little-endian-encoded vals... to fd. +func write(fd FD, vals ...uint64) { + var tmp [3 * 8]byte + buf := tmp[:len(vals)*8] + for i, v := range vals { + serialize64(buf[i*8:], v) + } + wr := 0 + for wr != len(buf) { + n, err := fd.write(buf[wr:]) + if err == syscall.EINTR { + continue + } + if err != nil { + println("failed to read fd =", fd, "errno =", err.(syscall.Errno)) + syscall.Exit(1) + } + wr += n + } +} + +// writeStr writes strings s to fd. +func writeStr(fd FD, s string) { + buf := []byte(s) + wr := 0 + for wr != len(buf) { + n, err := fd.write(buf[wr:]) + if err == syscall.EINTR { + continue + } + if err != nil { + println("failed to read fd =", fd, "errno =", err.(syscall.Errno)) + syscall.Exit(1) + } + wr += n + } +} diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main_libFuzzer.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main_libFuzzer.go new file mode 100644 index 000000000..7a4911788 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main_libFuzzer.go @@ -0,0 +1,20 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// +build gofuzz +// +build gofuzz_libfuzzer + +package gofuzzdep + +import ( + "unsafe" + + . "github.com/dvyukov/go-fuzz/go-fuzz-defs" +) + +func Initialize(coverTabPtr unsafe.Pointer, coverTabSize uint64) { + if coverTabSize != CoverSize { + panic("Incorrect cover tab size") + } + CoverTab = (*[CoverSize]byte)(coverTabPtr) +} diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sonar.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sonar.go new file mode 100644 index 000000000..e713c6d05 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sonar.go @@ -0,0 +1,201 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// +build gofuzz + +package gofuzzdep + +import ( + "sync/atomic" + "unsafe" + + . "github.com/dvyukov/go-fuzz/go-fuzz-defs" +) + +var ( + sonarRegion []byte + sonarPos uint32 +) + +const failure = ^uint8(0) + +type iface struct { + typ unsafe.Pointer + val unsafe.Pointer +} + +// Sonar is called by instrumentation code to notify go-fuzz about comparisons. +// Low 8 bits of id are flags, the rest is unique id of a comparison. +func Sonar(v1, v2 interface{}, id uint32) { + var buf [SonarHdrLen + 2*SonarMaxLen]byte + n1, f1 := serialize(v1, v2, buf[SonarHdrLen:]) + if n1 == failure { + return + } + n2, f2 := serialize(v2, v1, buf[SonarHdrLen+n1:]) + if n2 == failure { + return + } + // Ideal const operands are converted to signed int, + // but it does not mean that the comparison is signed + // unless the other operand is signed. + if id&SonarConst1 != 0 { + f1 &^= SonarSigned + } + if id&SonarConst2 != 0 { + f2 &^= SonarSigned + } + id |= uint32(f1 | f2) + serialize32(buf[:], id) + buf[4] = n1 + buf[5] = n2 + n := uint32(SonarHdrLen + n1 + n2) + pos := atomic.LoadUint32(&sonarPos) + for { + if pos+n > uint32(len(sonarRegion)) { + return + } + if atomic.CompareAndSwapUint32(&sonarPos, pos, pos+n) { + break + } + pos = atomic.LoadUint32(&sonarPos) + } + copy(sonarRegion[pos:pos+n], buf[:]) +} + +func serialize(v, v2 interface{}, buf []byte) (n, flags uint8) { + switch vv := v.(type) { + case int8: + buf[0] = byte(vv) + return 1, SonarSigned + case uint8: + buf[0] = byte(vv) + return 1, 0 + case int16: + return serialize16(buf, uint16(vv)), SonarSigned + case uint16: + return serialize16(buf, vv), 0 + case int32: + return serialize32(buf, uint32(vv)), SonarSigned + case uint32: + return serialize32(buf, vv), 0 + case int64: + return serialize64(buf, uint64(vv)), SonarSigned + case uint64: + return serialize64(buf, vv), 0 + case int: + if unsafe.Sizeof(vv) == 4 { + return serialize32(buf, uint32(vv)), SonarSigned + } else { + return serialize64(buf, uint64(vv)), SonarSigned + } + case uint: + if unsafe.Sizeof(vv) == 4 { + return serialize32(buf, uint32(vv)), 0 + } else { + return serialize64(buf, uint64(vv)), 0 + } + case string: + if len(vv) > SonarMaxLen { + return failure, 0 + } + return uint8(copy(buf, vv)), SonarString + case [1]byte: + return uint8(copy(buf, vv[:])), SonarString + case [2]byte: + return uint8(copy(buf, vv[:])), SonarString + case [3]byte: + return uint8(copy(buf, vv[:])), SonarString + case [4]byte: + return uint8(copy(buf, vv[:])), SonarString + case [5]byte: + return uint8(copy(buf, vv[:])), SonarString + case [6]byte: + return uint8(copy(buf, vv[:])), SonarString + case [7]byte: + return uint8(copy(buf, vv[:])), SonarString + case [8]byte: + return uint8(copy(buf, vv[:])), SonarString + case [9]byte: + return uint8(copy(buf, vv[:])), SonarString + case [10]byte: + return uint8(copy(buf, vv[:])), SonarString + case [11]byte: + return uint8(copy(buf, vv[:])), SonarString + case [12]byte: + return uint8(copy(buf, vv[:])), SonarString + case [13]byte: + return uint8(copy(buf, vv[:])), SonarString + case [14]byte: + return uint8(copy(buf, vv[:])), SonarString + case [15]byte: + return uint8(copy(buf, vv[:])), SonarString + case [16]byte: + return uint8(copy(buf, vv[:])), SonarString + case [17]byte: + return uint8(copy(buf, vv[:])), SonarString + case [18]byte: + return uint8(copy(buf, vv[:])), SonarString + case [19]byte: + return uint8(copy(buf, vv[:])), SonarString + case [20]byte: + return uint8(copy(buf, vv[:])), SonarString + default: + // Special case: string literal is compared with a variable of + // user type with string underlying type: + // type Name string + // var name Name + // if name == "foo" { ... } + if _, ok := v2.(string); ok { + s := *(*string)((*iface)(unsafe.Pointer(&v)).val) + if len(s) <= SonarMaxLen { + return uint8(copy(buf[:], s)), SonarString + } + } + return failure, 0 + } +} + +// The serialization routines here match those of encoding/binary.LittleEndian. +// They are copied here because importing encoding/binary creates import cycles. + +func serialize16(buf []byte, v uint16) uint8 { + _ = buf[1] + buf[0] = byte(v >> 0) + buf[1] = byte(v >> 8) + return 2 +} + +func serialize32(buf []byte, v uint32) uint8 { + _ = buf[3] + buf[0] = byte(v >> 0) + buf[1] = byte(v >> 8) + buf[2] = byte(v >> 16) + buf[3] = byte(v >> 24) + return 4 +} + +func serialize64(buf []byte, v uint64) uint8 { + _ = buf[7] + buf[0] = byte(v >> 0) + buf[1] = byte(v >> 8) + buf[2] = byte(v >> 16) + buf[3] = byte(v >> 24) + buf[4] = byte(v >> 32) + buf[5] = byte(v >> 40) + buf[6] = byte(v >> 48) + buf[7] = byte(v >> 56) + return 8 +} + +func deserialize64(buf []byte) uint64 { + _ = buf[7] + return uint64(buf[0])<<0 | + uint64(buf[1])<<8 | + uint64(buf[2])<<16 | + uint64(buf[3])<<24 | + uint64(buf[4])<<32 | + uint64(buf[5])<<40 | + uint64(buf[6])<<48 | + uint64(buf[7])<<56 +} diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_posix.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_posix.go new file mode 100644 index 000000000..938cf9324 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_posix.go @@ -0,0 +1,32 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// +build darwin linux freebsd dragonfly openbsd netbsd +// +build gofuzz + +package gofuzzdep + +import ( + "syscall" + + . "github.com/dvyukov/go-fuzz/go-fuzz-defs" +) + +type FD int + +func setupCommFile() ([]byte, FD, FD) { + mem, err := syscall.Mmap(3, 0, CoverSize+MaxInputSize+SonarRegionSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + println("failed to mmap fd = 3 errno =", err.(syscall.Errno)) + syscall.Exit(1) + } + return mem, 4, 5 +} + +func (fd FD) read(buf []byte) (int, error) { + return syscall.Read(int(fd), buf) +} + +func (fd FD) write(buf []byte) (int, error) { + return syscall.Write(int(fd), buf) +} diff --git a/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_windows.go b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_windows.go new file mode 100644 index 000000000..160301549 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_windows.go @@ -0,0 +1,56 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// +build gofuzz + +package gofuzzdep + +import ( + "syscall" + "unsafe" + + . "github.com/dvyukov/go-fuzz/go-fuzz-defs" +) + +// Can't import reflect because of import cycles. +type sliceHeader struct { + addr uintptr + l, c int +} + +type FD syscall.Handle + +func setupCommFile() ([]byte, FD, FD) { + const ( + size = CoverSize + MaxInputSize + SonarRegionSize + FILE_MAP_ALL_ACCESS = 0xF001F + ) + mapping := readEnvParam("GO_FUZZ_COMM_FD") + addr, err := syscall.MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, size) + if err != nil { + println("failed to mmap comm file:", err.Error()) + syscall.Exit(1) + } + hdr := sliceHeader{addr, size, size} + mem := *(*[]byte)(unsafe.Pointer(&hdr)) + in := FD(readEnvParam("GO_FUZZ_IN_FD")) + out := FD(readEnvParam("GO_FUZZ_OUT_FD")) + return mem, in, out +} + +func readEnvParam(name string) syscall.Handle { + v, _ := syscall.Getenv(name) + var x uintptr + for i := 0; i < len(v); i++ { + x = x*10 + uintptr(v[i]-'0') + } + return syscall.Handle(x) +} + +func (fd FD) read(buf []byte) (int, error) { + return syscall.Read(syscall.Handle(fd), buf) +} + +func (fd FD) write(buf []byte) (int, error) { + return syscall.Write(syscall.Handle(fd), buf) +} diff --git a/vendor/github.com/dvyukov/go-fuzz/internal/go-fuzz-types/types.go b/vendor/github.com/dvyukov/go-fuzz/internal/go-fuzz-types/types.go new file mode 100644 index 000000000..360481ff2 --- /dev/null +++ b/vendor/github.com/dvyukov/go-fuzz/internal/go-fuzz-types/types.go @@ -0,0 +1,28 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +// Package types provides types shared between go-fuzz-build and go-fuzz. +package types + +type CoverBlock struct { + ID int + File string + StartLine int + StartCol int + EndLine int + EndCol int + NumStmt int +} + +type Literal struct { + Val string + IsStr bool +} + +type MetaData struct { + Literals []Literal + Blocks []CoverBlock + Sonar []CoverBlock + Funcs []string // fuzz function names; must have length > 0 + DefaultFunc string // default function to fuzz +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 43aeb59e5..c715bdc20 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -21,6 +21,12 @@ github.com/OpenPeeDeeP/depguard github.com/bombsimon/wsl/v3 # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew +# github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 +## explicit +github.com/dvyukov/go-fuzz/go-fuzz-build +github.com/dvyukov/go-fuzz/go-fuzz-defs +github.com/dvyukov/go-fuzz/go-fuzz-dep +github.com/dvyukov/go-fuzz/internal/go-fuzz-types # github.com/fatih/color v1.7.0 github.com/fatih/color # github.com/fsnotify/fsnotify v1.4.7 |
