diff options
| author | Khaled Yakdan <yakdan@code-intelligence.com> | 2022-05-20 22:09:58 +0000 |
|---|---|---|
| committer | Keith Randall <khr@golang.org> | 2022-05-20 22:30:37 +0000 |
| commit | 2b0e457b42a64455ca2d3eebb5c6d4e6acfc5db2 (patch) | |
| tree | 5d524a6994b395136cf1e6e5954266856091bd08 /src/cmd | |
| parent | b58067013eaa2f2bf0dc24f4d848e10bb758b6bd (diff) | |
| download | go-2b0e457b42a64455ca2d3eebb5c6d4e6acfc5db2.tar.xz | |
cmd/compile: intercept string compares in libFuzzer mode
IR string compares as well as calls to string comparison functions such
as `strings.EqualFold` are intercepted and the corresponding libFuzzer
callbacks are invoked with the corresponding arguments. As a result, the
compared strings will be added to libFuzzer’s table of recent compares,
which feeds future mutations performed by the fuzzer and thus allow it
to reach into branches guarded by string comparisons.
The list of methods to intercept is maintained in
`cmd/compile/internal/walk/expr.go` and can easily be extended to cover
more standard library functions in the future.
Change-Id: I5c8b89499c4e19459406795dea923bf777779c51
GitHub-Last-Rev: 6b8529b55561faf57ea59cb7cff1caf8c9c94ecd
GitHub-Pull-Request: golang/go#51319
Reviewed-on: https://go-review.googlesource.com/c/go/+/387335
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Run-TryBot: Keith Randall <khr@golang.org>
Diffstat (limited to 'src/cmd')
| -rw-r--r-- | src/cmd/compile/internal/typecheck/builtin.go | 5 | ||||
| -rw-r--r-- | src/cmd/compile/internal/typecheck/builtin/runtime.go | 2 | ||||
| -rw-r--r-- | src/cmd/compile/internal/walk/compare.go | 29 | ||||
| -rw-r--r-- | src/cmd/compile/internal/walk/expr.go | 24 | ||||
| -rw-r--r-- | src/cmd/internal/goobj/builtinlist.go | 2 |
5 files changed, 61 insertions, 1 deletions
diff --git a/src/cmd/compile/internal/typecheck/builtin.go b/src/cmd/compile/internal/typecheck/builtin.go index 875af37215..e452f23ff0 100644 --- a/src/cmd/compile/internal/typecheck/builtin.go +++ b/src/cmd/compile/internal/typecheck/builtin.go @@ -205,6 +205,8 @@ var runtimeDecls = [...]struct { {"libfuzzerTraceConstCmp2", funcTag, 146}, {"libfuzzerTraceConstCmp4", funcTag, 147}, {"libfuzzerTraceConstCmp8", funcTag, 148}, + {"libfuzzerHookStrCmp", funcTag, 149}, + {"libfuzzerHookEqualFold", funcTag, 149}, {"x86HasPOPCNT", varTag, 6}, {"x86HasSSE41", varTag, 6}, {"x86HasFMA", varTag, 6}, @@ -228,7 +230,7 @@ func params(tlist ...*types.Type) []*types.Field { } func runtimeTypes() []*types.Type { - var typs [149]*types.Type + var typs [150]*types.Type typs[0] = types.ByteType typs[1] = types.NewPtr(typs[0]) typs[2] = types.Types[types.TANY] @@ -378,5 +380,6 @@ func runtimeTypes() []*types.Type { typs[146] = newSig(params(typs[60], typs[60]), nil) typs[147] = newSig(params(typs[62], typs[62]), nil) typs[148] = newSig(params(typs[24], typs[24]), nil) + typs[149] = newSig(params(typs[28], typs[28], typs[15]), nil) return typs[:] } diff --git a/src/cmd/compile/internal/typecheck/builtin/runtime.go b/src/cmd/compile/internal/typecheck/builtin/runtime.go index dd19eefa29..97b8318f7f 100644 --- a/src/cmd/compile/internal/typecheck/builtin/runtime.go +++ b/src/cmd/compile/internal/typecheck/builtin/runtime.go @@ -267,6 +267,8 @@ func libfuzzerTraceConstCmp1(uint8, uint8) func libfuzzerTraceConstCmp2(uint16, uint16) func libfuzzerTraceConstCmp4(uint32, uint32) func libfuzzerTraceConstCmp8(uint64, uint64) +func libfuzzerHookStrCmp(string, string, int) +func libfuzzerHookEqualFold(string, string, int) // architecture variants var x86HasPOPCNT bool diff --git a/src/cmd/compile/internal/walk/compare.go b/src/cmd/compile/internal/walk/compare.go index d271698c51..b02cf22acf 100644 --- a/src/cmd/compile/internal/walk/compare.go +++ b/src/cmd/compile/internal/walk/compare.go @@ -5,7 +5,11 @@ package walk import ( + "encoding/binary" + "fmt" "go/constant" + "hash/fnv" + "io" "cmd/compile/internal/base" "cmd/compile/internal/compare" @@ -16,6 +20,22 @@ import ( "cmd/compile/internal/types" ) +func fakePC(n ir.Node) ir.Node { + // In order to get deterministic IDs, we include the package path, absolute filename, line number, column number + // in the calculation of the fakePC for the IR node. + hash := fnv.New32() + // We ignore the errors here because the `io.Writer` in the `hash.Hash` interface never returns an error. + io.WriteString(hash, base.Ctxt.Pkgpath) + io.WriteString(hash, base.Ctxt.PosTable.Pos(n.Pos()).AbsFilename()) + binary.Write(hash, binary.LittleEndian, int64(n.Pos().Line())) + binary.Write(hash, binary.LittleEndian, int64(n.Pos().Col())) + // We also include the string representation of the node to distinguish autogenerated expression since + // those get the same `src.XPos` + io.WriteString(hash, fmt.Sprintf("%v", n)) + + return ir.NewInt(int64(hash.Sum32())) +} + // The result of walkCompare MUST be assigned back to n, e.g. // // n.Left = walkCompare(n.Left, init) @@ -290,6 +310,15 @@ func walkCompareInterface(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { } func walkCompareString(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { + if base.Debug.Libfuzzer != 0 { + if !ir.IsConst(n.X, constant.String) || !ir.IsConst(n.Y, constant.String) { + fn := "libfuzzerHookStrCmp" + n.X = cheapExpr(n.X, init) + n.Y = cheapExpr(n.Y, init) + paramType := types.Types[types.TSTRING] + init.Append(mkcall(fn, nil, init, tracecmpArg(n.X, paramType, init), tracecmpArg(n.Y, paramType, init), fakePC(n))) + } + } // Rewrite comparisons to short constant strings as length+byte-wise comparisons. var cs, ncs ir.Node // const string, non-const string switch { diff --git a/src/cmd/compile/internal/walk/expr.go b/src/cmd/compile/internal/walk/expr.go index 9aabf91679..803a07ae73 100644 --- a/src/cmd/compile/internal/walk/expr.go +++ b/src/cmd/compile/internal/walk/expr.go @@ -496,6 +496,16 @@ func walkAddString(n *ir.AddStringExpr, init *ir.Nodes) ir.Node { return r1 } +type hookInfo struct { + paramType types.Kind + argsNum int + runtimeFunc string +} + +var hooks = map[string]hookInfo{ + "strings.EqualFold": {paramType: types.TSTRING, argsNum: 2, runtimeFunc: "libfuzzerHookEqualFold"}, +} + // walkCall walks an OCALLFUNC or OCALLINTER node. func walkCall(n *ir.CallExpr, init *ir.Nodes) ir.Node { if n.Op() == ir.OCALLMETH { @@ -591,6 +601,20 @@ func walkCall1(n *ir.CallExpr, init *ir.Nodes) { } n.Args = args + funSym := n.X.Sym() + if base.Debug.Libfuzzer != 0 && funSym != nil { + if hook, found := hooks[funSym.Pkg.Path+"."+funSym.Name]; found { + if len(args) != hook.argsNum { + panic(fmt.Sprintf("%s.%s expects %d arguments, but received %d", funSym.Pkg.Path, funSym.Name, hook.argsNum, len(args))) + } + var hookArgs []ir.Node + for _, arg := range args { + hookArgs = append(hookArgs, tracecmpArg(arg, types.Types[hook.paramType], init)) + } + hookArgs = append(hookArgs, fakePC(n)) + init.Append(mkcall(hook.runtimeFunc, nil, init, hookArgs...)) + } + } } // walkDivMod walks an ODIV or OMOD node. diff --git a/src/cmd/internal/goobj/builtinlist.go b/src/cmd/internal/goobj/builtinlist.go index 608c0d7222..2d13222984 100644 --- a/src/cmd/internal/goobj/builtinlist.go +++ b/src/cmd/internal/goobj/builtinlist.go @@ -195,6 +195,8 @@ var builtins = [...]struct { {"runtime.libfuzzerTraceConstCmp2", 1}, {"runtime.libfuzzerTraceConstCmp4", 1}, {"runtime.libfuzzerTraceConstCmp8", 1}, + {"runtime.libfuzzerHookStrCmp", 1}, + {"runtime.libfuzzerHookEqualFold", 1}, {"runtime.x86HasPOPCNT", 0}, {"runtime.x86HasSSE41", 0}, {"runtime.x86HasFMA", 0}, |
