From e104af2242e1cb5507df1c4516081da4ed80440b Mon Sep 17 00:00:00 2001 From: Stan Rosenberg Date: Tue, 21 May 2024 20:00:14 -0400 Subject: [PATCH] Currently, there isn't a reliable way to build Go test binaries with libfuzzer instrumentation [1]. An attempt such as `bazel run --@io_bazel_rules_go//go/config:gc_goopts=-d=libfuzzer ...` fails to compile owing to unresolved symbols, e.g., runtime.libfuzzerTraceConstCmp8 [2]. This patch adds `libfuzzer_shim.go` (identical to `trace.go` [2]) to the `bzltestutil` package, which ensures the shim is linked with a Go test binary. Furthermore, we exclude `-d=libfuzzer` when compiling any of the _external_ dependencies. [1] https://github.com/bazelbuild/rules_go/issues/3088#issuecomment-1829192869 [2] https://github.com/golang/go/commit/74f49f3366826f95a464cc15838a0668c92e3357 --- go/tools/builders/compilepkg.go | 12 ++++++++ go/tools/builders/stdlib.go | 7 +++++ go/tools/bzltestutil/libfuzzer_shim.go | 41 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 go/tools/bzltestutil/libfuzzer_shim.go diff --git a/go/tools/builders/compilepkg.go b/go/tools/builders/compilepkg.go index 6d3afdd868..7f8991db45 100644 --- a/go/tools/builders/compilepkg.go +++ b/go/tools/builders/compilepkg.go @@ -27,6 +27,7 @@ import ( "os/exec" "path" "path/filepath" + "slices" "sort" "strings" ) @@ -343,6 +344,17 @@ func compileArchive( cgoSrcs[i-len(goSrcs)] = coverSrc } } + if strings.Contains(outLinkObj, "external/") && slices.Contains(gcFlags, "-d=libfuzzer") { + // Remove -d=libfuzzer from gcFlags when compiling external packages. We don't really want to instrument them, + // and they may not link without libfuzzer_shim.go. + gcFlags = slices.DeleteFunc(gcFlags, func(s string) bool { + return s == "-d=libfuzzer" + }) + } + // Log instrumented objs for ease of tracking/debugging. + if slices.Contains(gcFlags, "-d=libfuzzer") { + fmt.Printf("%s -- gcFlags=%s\n", outLinkObj, gcFlags) + } // If we have cgo, generate separate C and go files, and compile the // C files. diff --git a/go/tools/builders/stdlib.go b/go/tools/builders/stdlib.go index 8398b19ca0..72ff08258f 100644 --- a/go/tools/builders/stdlib.go +++ b/go/tools/builders/stdlib.go @@ -22,6 +22,7 @@ import ( "path/filepath" "regexp" "strings" + "slices" ) // stdlib builds the standard library in the appropriate mode into a new goroot. @@ -154,6 +155,12 @@ You may need to use the flags --cpu=x64_windows --compiler=mingw-gcc.`) break } } + // Remove -d=libfuzzer from gcflags when compiling stdlib. We only want our code to be instrumented. + // N.B. allowing this flag to pass may cause issues when linking external tools, e.g., protoc-bin, owing + // to unresolved symbols, i.e., libfuzzer_shim.go isn't included. + gcflags = slices.DeleteFunc(gcflags, func(s string) bool { + return s == "-d=libfuzzer" + }) installArgs = append(installArgs, "-gcflags="+allSlug+strings.Join(gcflags, " ")) installArgs = append(installArgs, "-ldflags="+allSlug+strings.Join(ldflags, " ")) installArgs = append(installArgs, "-asmflags="+allSlug+strings.Join(asmflags, " ")) diff --git a/go/tools/bzltestutil/libfuzzer_shim.go b/go/tools/bzltestutil/libfuzzer_shim.go new file mode 100644 index 0000000000..99c859a22c --- /dev/null +++ b/go/tools/bzltestutil/libfuzzer_shim.go @@ -0,0 +1,41 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// N.B. This source is lifted verbatim from trace.go, added in +// https://github.com/golang/go/commit/74f49f3366826f95a464cc15838a0668c92e3357 +// +// It's essentially a shim to allow linking a Go test binary without libfuzzer. +// We chose the 'bzltestutil' package because it's a bazel dependency for all Go test binaries. + +//go:build !libfuzzer + +package bzltestutil + +import _ "unsafe" // for go:linkname + +//go:linkname libfuzzerTraceCmp1 runtime.libfuzzerTraceCmp1 +//go:linkname libfuzzerTraceCmp2 runtime.libfuzzerTraceCmp2 +//go:linkname libfuzzerTraceCmp4 runtime.libfuzzerTraceCmp4 +//go:linkname libfuzzerTraceCmp8 runtime.libfuzzerTraceCmp8 + +//go:linkname libfuzzerTraceConstCmp1 runtime.libfuzzerTraceConstCmp1 +//go:linkname libfuzzerTraceConstCmp2 runtime.libfuzzerTraceConstCmp2 +//go:linkname libfuzzerTraceConstCmp4 runtime.libfuzzerTraceConstCmp4 +//go:linkname libfuzzerTraceConstCmp8 runtime.libfuzzerTraceConstCmp8 + +//go:linkname libfuzzerHookStrCmp runtime.libfuzzerHookStrCmp +//go:linkname libfuzzerHookEqualFold runtime.libfuzzerHookEqualFold + +func libfuzzerTraceCmp1(arg0, arg1 uint8, fakePC int) {} +func libfuzzerTraceCmp2(arg0, arg1 uint16, fakePC int) {} +func libfuzzerTraceCmp4(arg0, arg1 uint32, fakePC int) {} +func libfuzzerTraceCmp8(arg0, arg1 uint64, fakePC int) {} + +func libfuzzerTraceConstCmp1(arg0, arg1 uint8, fakePC int) {} +func libfuzzerTraceConstCmp2(arg0, arg1 uint16, fakePC int) {} +func libfuzzerTraceConstCmp4(arg0, arg1 uint32, fakePC int) {} +func libfuzzerTraceConstCmp8(arg0, arg1 uint64, fakePC int) {} + +func libfuzzerHookStrCmp(arg0, arg1 string, fakePC int) {} +func libfuzzerHookEqualFold(arg0, arg1 string, fakePC int) {}