diff --git a/hash.go b/hash.go index 3c66d04..be72bc1 100644 --- a/hash.go +++ b/hash.go @@ -60,10 +60,7 @@ func alterToolVersion(tool string, args []string) error { toolID = []byte(line) } - contentID, err := ownContentID(toolID) - if err != nil { - return fmt.Errorf("cannot obtain garble's own version: %v", err) - } + contentID := addGarbleToBuildIDComponent(toolID) // 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 // 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 } -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 // sum. This includes the original tool's content ID, and garble's own // content ID. h := sha256.New() - h.Write(toolID) + h.Write(inputHash) if len(cache.BinaryContentID) == 0 { panic("missing binary content ID") } @@ -102,7 +105,7 @@ func ownContentID(toolID []byte) ([]byte, error) { 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, diff --git a/shared.go b/shared.go index 9067499..11661c6 100644 --- a/shared.go +++ b/shared.go @@ -3,7 +3,6 @@ package main import ( "bytes" "crypto/rand" - "crypto/sha256" "encoding/base64" "encoding/gob" "encoding/json" @@ -217,11 +216,7 @@ func setListedPackages(patterns []string) error { } if pkg.Export != "" { actionID := decodeHash(splitActionID(pkg.BuildID)) - h := sha256.New() - h.Write(actionID) - h.Write(cache.BinaryContentID) - - pkg.GarbleActionID = h.Sum(nil)[:buildIDComponentLength] + pkg.GarbleActionID = addGarbleToBuildIDComponent(actionID) } cache.ListedPackages[pkg.ImportPath] = &pkg } diff --git a/testdata/scripts/seed.txt b/testdata/scripts/seed.txt index 01bbe08..0f04b55 100644 --- a/testdata/scripts/seed.txt +++ b/testdata/scripts/seed.txt @@ -1,42 +1,80 @@ env GOPRIVATE=test/main +env SEED1=OQg9kACEECQ +env SEED2=NruiDmVz6/s + # Check the binary with a given base64 encoded seed -garble -literals -seed=OQg9kACEECQ build +garble -seed=${SEED1} build exec ./main$exe -cmp stderr main.stdout -! binsubstr main$exe 'teststring' 'teststringVar' 'imported var value' 'ImportedVar' +cmp stderr main.stderr +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. # No packages should be rebuilt either, thanks to the build cache. cp main$exe main_old$exe rm main$exe -garble -literals -seed=OQg9kACEECQ= build -v +garble -seed=${SEED1}= build -v ! stderr . 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. # We can't know if caching happens here, because of previous test runs. cp main$exe main_old$exe rm main$exe -garble -literals -seed=NruiDmVz6/s build +garble -seed=${SEED2} build ! 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. -garble -literals -seed=random build -v -stderr . +garble -seed=random build -v +stderr -count=1 '^runtime$' +stderr -count=1 '^test/main$' exec ./main$exe -cmp stderr main.stdout -! binsubstr main$exe 'teststring' 'teststringVar' 'imported var value' 'ImportedVar' +cmp stderr main.stderr +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. cp main$exe main_old$exe rm main$exe -garble -literals -seed=random build -v +garble -seed=random build -v stderr . ! 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 -- module test/main @@ -44,19 +82,34 @@ go 1.16 -- main.go -- package main -import "test/main/imported" +import ( + "os" + "runtime" + + "test/main/imported" +) var teststringVar = "teststring" func main() { - println(teststringVar) - println(imported.ImportedVar) + if len(os.Args) > 1 && os.Args[1] == "funcName" { + 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 -- package imported var ImportedVar = "imported var value" --- main.stdout -- +-- main.stderr -- teststring imported var value