make flags like -literals and GOPRIVATE affect hashing (#288)

In 6898d61637, we switched from using action IDs from "go list
-toolexec=garble" to those from the original "go list". We still wanted
the obfuscation and hashing to change if the version of garble changes,
so we hashed that "original action ID" with garble's own content ID, and
called the new hash "garble action ID".

While working on a different patch, I noticed something weird: with the
new mechanism, adding or removing flags like -literals did not alter
those hashes, unlike the old method. This is because the old method used
ownContentID, which includes such bits of information, but the new
method does not.

Change that, and add a test that locks in the behavior we want. In
seed.txt, we check that a single function name gets hashed in particular
ways in different scenarios.

Note that we use a mix of "cmp" and "! bincmp", since the former has no
negated form.

While at it, the seed.txt test is revamped a bit. Now, we only run with
-literals once, as this test is mainly about -seed. We also declare seed
strings once, as environment variables, which makes it easier to track
what each step is doing.
pull/289/head
Daniel Martí 4 years ago committed by GitHub
parent a8c5d534d1
commit 10ec00b37a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -60,10 +60,7 @@ func alterToolVersion(tool string, args []string) error {
toolID = []byte(line) toolID = []byte(line)
} }
contentID, err := ownContentID(toolID) contentID := addGarbleToBuildIDComponent(toolID)
if err != nil {
return fmt.Errorf("cannot obtain garble's own version: %v", err)
}
// The part of the build ID that matters is the last, since it's the // The part of the build ID that matters is the last, since it's the
// "content ID" which is used to work out whether there is a need to redo // "content ID" which is used to work out whether there is a need to redo
// the action (build) or not. Since cmd/go parses the last word in the // the action (build) or not. Since cmd/go parses the last word in the
@ -75,12 +72,18 @@ func alterToolVersion(tool string, args []string) error {
return nil return nil
} }
func ownContentID(toolID []byte) ([]byte, error) { // addGarbleToBuildIDComponent takes a build ID component hash, such as an
// action ID or a content ID, and returns a new hash which also contains
// garble's own deterministic inputs.
//
// This includes garble's own version, obtained via its own binary's content ID,
// as well as any other options which affect a build, such as GOPRIVATE and -tiny.
func addGarbleToBuildIDComponent(inputHash []byte) []byte {
// Join the two content IDs together into a single base64-encoded sha256 // Join the two content IDs together into a single base64-encoded sha256
// sum. This includes the original tool's content ID, and garble's own // sum. This includes the original tool's content ID, and garble's own
// content ID. // content ID.
h := sha256.New() h := sha256.New()
h.Write(toolID) h.Write(inputHash)
if len(cache.BinaryContentID) == 0 { if len(cache.BinaryContentID) == 0 {
panic("missing binary content ID") panic("missing binary content ID")
} }
@ -102,7 +105,7 @@ func ownContentID(toolID []byte) ([]byte, error) {
fmt.Fprintf(h, " -seed=%x", opts.Seed) fmt.Fprintf(h, " -seed=%x", opts.Seed)
} }
return h.Sum(nil)[:buildIDComponentLength], nil return h.Sum(nil)[:buildIDComponentLength]
} }
// buildIDComponentLength is the number of bytes each build ID component takes, // buildIDComponentLength is the number of bytes each build ID component takes,

@ -3,7 +3,6 @@ package main
import ( import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/gob" "encoding/gob"
"encoding/json" "encoding/json"
@ -217,11 +216,7 @@ func setListedPackages(patterns []string) error {
} }
if pkg.Export != "" { if pkg.Export != "" {
actionID := decodeHash(splitActionID(pkg.BuildID)) actionID := decodeHash(splitActionID(pkg.BuildID))
h := sha256.New() pkg.GarbleActionID = addGarbleToBuildIDComponent(actionID)
h.Write(actionID)
h.Write(cache.BinaryContentID)
pkg.GarbleActionID = h.Sum(nil)[:buildIDComponentLength]
} }
cache.ListedPackages[pkg.ImportPath] = &pkg cache.ListedPackages[pkg.ImportPath] = &pkg
} }

@ -1,42 +1,80 @@
env GOPRIVATE=test/main env GOPRIVATE=test/main
env SEED1=OQg9kACEECQ
env SEED2=NruiDmVz6/s
# Check the binary with a given base64 encoded seed # Check the binary with a given base64 encoded seed
garble -literals -seed=OQg9kACEECQ build garble -seed=${SEED1} build
exec ./main$exe exec ./main$exe
cmp stderr main.stdout cmp stderr main.stderr
! binsubstr main$exe 'teststring' 'teststringVar' 'imported var value' 'ImportedVar' binsubstr main$exe 'teststring' 'imported var value'
! binsubstr main$exe 'ImportedVar'
[short] stop # the extra checks are relatively expensive
[short] stop # checking that the build is reproducible and random is slow exec ./main$exe funcName
cp stderr funcName-seed-static-1
# Also check that the binary is reproducible. # Also check that the binary is reproducible.
# No packages should be rebuilt either, thanks to the build cache. # No packages should be rebuilt either, thanks to the build cache.
cp main$exe main_old$exe cp main$exe main_old$exe
rm main$exe rm main$exe
garble -literals -seed=OQg9kACEECQ= build -v garble -seed=${SEED1}= build -v
! stderr . ! stderr .
bincmp main$exe main_old$exe bincmp main$exe main_old$exe
exec ./main$exe funcName
cmp stderr funcName-seed-static-1
# Also check that a different seed leads to a different binary. # Also check that a different seed leads to a different binary.
# We can't know if caching happens here, because of previous test runs. # We can't know if caching happens here, because of previous test runs.
cp main$exe main_old$exe cp main$exe main_old$exe
rm main$exe rm main$exe
garble -literals -seed=NruiDmVz6/s build garble -seed=${SEED2} build
! bincmp main$exe main_old$exe ! bincmp main$exe main_old$exe
exec ./main$exe funcName
cp stderr funcName-seed-static-2
! bincmp funcName-seed-static-2 funcName-seed-static-1
# Use a random seed, which should always trigger a full build. # Use a random seed, which should always trigger a full build.
garble -literals -seed=random build -v garble -seed=random build -v
stderr . stderr -count=1 '^runtime$'
stderr -count=1 '^test/main$'
exec ./main$exe exec ./main$exe
cmp stderr main.stdout cmp stderr main.stderr
! binsubstr main$exe 'teststring' 'teststringVar' 'imported var value' 'ImportedVar' binsubstr main$exe 'teststring' 'imported var value'
! binsubstr main$exe 'ImportedVar'
exec ./main$exe funcName
cp stderr funcName-seed-random-1
! bincmp funcName-seed-random-1 funcName-seed-static-1
# Also check that the random binary is not reproducible. # Also check that the random binary is not reproducible.
cp main$exe main_old$exe cp main$exe main_old$exe
rm main$exe rm main$exe
garble -literals -seed=random build -v garble -seed=random build -v
stderr . stderr .
! bincmp main$exe main_old$exe ! bincmp main$exe main_old$exe
exec ./main$exe funcName
cp stderr funcName-seed-random-2
! bincmp funcName-seed-random-2 funcName-seed-random-1
# Using different flags which affect the build, such as -literals or -tiny,
# should result in different obfuscation of names etc.
# There's strictly no reason to have this rule,
# but the flags result in different builds and binaries anyway,
# so we might as well make them as different as possible.
garble -seed=${SEED1} -literals build
exec ./main$exe funcName
! bincmp stderr funcName-seed-static-1
garble -seed=${SEED1} -tiny build
exec ./main$exe funcName
! bincmp stderr funcName-seed-static-1
-- go.mod -- -- go.mod --
module test/main module test/main
@ -44,19 +82,34 @@ go 1.16
-- main.go -- -- main.go --
package main package main
import "test/main/imported" import (
"os"
"runtime"
"test/main/imported"
)
var teststringVar = "teststring" var teststringVar = "teststring"
func main() { func main() {
println(teststringVar) if len(os.Args) > 1 && os.Args[1] == "funcName" {
println(imported.ImportedVar) println(originalFuncName())
} else {
println(teststringVar)
println(imported.ImportedVar)
}
}
func originalFuncName() string {
pc, _, _, _ := runtime.Caller(0)
fn := runtime.FuncForPC(pc)
return fn.Name()
} }
-- imported/imported.go -- -- imported/imported.go --
package imported package imported
var ImportedVar = "imported var value" var ImportedVar = "imported var value"
-- main.stdout -- -- main.stderr --
teststring teststring
imported var value imported var value

Loading…
Cancel
Save