From 9c6ff18c45aeb79ba03837dd83f50692904f5a3c Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Mon, 30 Oct 2017 15:28:48 -0400 Subject: [PATCH] Strip stdlib paths from binaries Set GOROOT_FINAL to a constant string ('GOROOT') when linking. Normally, the actual path to GOROOT is set in linked binaries, which results in non-reproducible builds. GOROOT_FINAL provides a constant replacement. Also, added a test for common reproducibility problems. Fixes #969 --- go/tools/builders/env.go | 1 + tests/reproducible_binary/BUILD.bazel | 25 ++++++ tests/reproducible_binary/hello.go | 7 ++ .../reproducible_binary_test.go | 83 +++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 tests/reproducible_binary/BUILD.bazel create mode 100644 tests/reproducible_binary/hello.go create mode 100644 tests/reproducible_binary/reproducible_binary_test.go diff --git a/go/tools/builders/env.go b/go/tools/builders/env.go index d9eb779d4..65c08cfe3 100644 --- a/go/tools/builders/env.go +++ b/go/tools/builders/env.go @@ -71,6 +71,7 @@ func (env *GoEnv) Env() []string { } return []string{ fmt.Sprintf("GOROOT=%s", env.absRoot()), + "GOROOT_FINAL=GOROOT", fmt.Sprintf("TMP=%s", "/tmp"), // TODO: may need to be different on windows fmt.Sprintf("GOOS=%s", env.goos), fmt.Sprintf("GOARCH=%s", env.goarch), diff --git a/tests/reproducible_binary/BUILD.bazel b/tests/reproducible_binary/BUILD.bazel new file mode 100644 index 000000000..194efc7e9 --- /dev/null +++ b/tests/reproducible_binary/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["hello.go"], + importpath = "github.com/bazelbuild/rules_go/tests/reproducible_binary", + visibility = ["//visibility:private"], +) + +go_binary( + name = "reproducible_binary", + importpath = "github.com/bazelbuild/rules_go/tests/reproducible_binary", + library = ":go_default_library", + visibility = ["//visibility:public"], +) + +# keep +go_test( + name = "go_default_test", + srcs = ["reproducible_binary_test.go"], + args = ["$(location :reproducible_binary)"], + data = [":reproducible_binary"], + importpath = "github.com/bazelbuild/rules_go/tests/reproducible_binary", + rundir = ".", # run in repo root instead of test dir +) diff --git a/tests/reproducible_binary/hello.go b/tests/reproducible_binary/hello.go new file mode 100644 index 000000000..f7b60bdeb --- /dev/null +++ b/tests/reproducible_binary/hello.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world!") +} diff --git a/tests/reproducible_binary/reproducible_binary_test.go b/tests/reproducible_binary/reproducible_binary_test.go new file mode 100644 index 000000000..38548b48a --- /dev/null +++ b/tests/reproducible_binary/reproducible_binary_test.go @@ -0,0 +1,83 @@ +/* Copyright 2017 The Bazel Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// reproducible_binary_test checks that a given binary DOES NOT contain +// strings that match the GOROOT, the current user's name, or the +// current user's home directory. +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "os/user" + "regexp" + "testing" +) + +var allStrings [][]byte +var currentUser *user.User + +func TestMain(m *testing.M) { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "usage: %s \n", os.Args[0]) + os.Exit(1) + } + + binaryData, err := ioutil.ReadFile(os.Args[1]) + if err != nil { + log.Fatal(err) + } + stringRex := regexp.MustCompile(`[[:graph:]]{3,}`) + allStrings = stringRex.FindAll(binaryData, -1) + + currentUser, err = user.Current() + if err != nil { + currentUser = nil + } + + os.Exit(m.Run()) +} + +// TestStandardPath checks that source paths from the standard library +// are trimmed. We just check one known source file. +func TestStandardPath(t *testing.T) { + want := []byte("GOROOT/src/fmt/format.go") + for _, s := range allStrings { + if bytes.HasSuffix(s, []byte("fmt/format.go")) && !bytes.Equal(s, want) { + t.Fatalf("got %q; want %q", s, want) + } + } +} + +// TestUserNameAndHome checks the user name and home directory do not +// appear in strings from the binary. +func TestUserNameAndHome(t *testing.T) { + if currentUser == nil { + t.Skip() + } + fmt.Fprintf(os.Stderr, "username: %s\n", currentUser.Username) + for _, s := range allStrings { + if currentUser.Username != "" && bytes.Contains(s, []byte(currentUser.Username)) { + t.Errorf("binary contains username %q in string %q", currentUser.Username, s) + continue + } + if currentUser.HomeDir != "" && bytes.Contains(s, []byte(currentUser.HomeDir)) { + t.Errorf("binary contains home dir %q in string %q", currentUser.HomeDir, s) + } + } +}