unify the definition and storage of flag values

The parent garble process parses the original flags,
as provided by the user via the command line.
Previously, those got stored in the shared cache file,
so that child processes spawned by toolexec could see them.

Unfortunately, this made the code relatively easy to misuse.
A child process would always see flagLiterals as zero value,
given that it should never see such a flag argument directly.
Similarly, one would have to be careful with cached options,
as they could only be consumed after the cache file is loaded.

Simplify the situation by deduplicating the storage of flags.
Now, the parent passes all flags onto children via toolexec.

One exception is GarbleDir, which now becomes an env var.
This seems in line with other top-level dirs like GARBLE_SHARED.

Finally, we turn -seed into a flag.Value,
which lets us implement its "set" behavior as part of flag.Parse.

Overall, we barely reduce the amount of code involved,
but we certainly remove a couple of footguns.
As part of the cleanup, we also introduce appendFlags.
pull/431/head
Daniel Martí 3 years ago committed by Andrew LeFevre
parent a144789910
commit 5f74a1c9f0

@ -98,17 +98,27 @@ func addGarbleToHash(inputHash []byte) []byte {
if cache.GOGARBLE != "" {
fmt.Fprintf(h, " GOGARBLE=%s", cache.GOGARBLE)
}
if opts.ObfuscateLiterals {
fmt.Fprintf(h, " -literals")
appendFlags(h)
return h.Sum(nil)[:buildIDComponentLength]
}
// appendFlags writes garble's own flags to w in string form.
// Errors are ignored, as w is always a buffer or hasher.
func appendFlags(w io.Writer) {
if flagLiterals {
io.WriteString(w, " -literals")
}
if opts.Tiny {
fmt.Fprintf(h, " -tiny")
if flagTiny {
io.WriteString(w, " -tiny")
}
if len(opts.Seed) > 0 {
fmt.Fprintf(h, " -seed=%x", opts.Seed)
if flagDebugDir != "" {
io.WriteString(w, " -debugdir=")
io.WriteString(w, flagDebugDir)
}
if len(flagSeed.bytes) > 0 {
io.WriteString(w, " -seed=")
io.WriteString(w, flagSeed.String())
}
return h.Sum(nil)[:buildIDComponentLength]
}
// buildIDComponentLength is the number of bytes each build ID component takes,
@ -196,7 +206,7 @@ func hashWith(salt []byte, name string) string {
d := sha256.New()
d.Write(salt)
d.Write(opts.Seed)
d.Write(flagSeed.bytes)
io.WriteString(d, name)
sum := make([]byte, nameBase64.EncodedLen(d.Size()))
nameBase64.Encode(sum, d.Sum(nil))

@ -5,6 +5,7 @@ package main
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/gob"
@ -19,7 +20,6 @@ import (
"go/types"
"io"
"io/fs"
"io/ioutil"
"log"
mathrand "math/rand"
"os"
@ -48,18 +48,50 @@ var (
)
var (
flagObfuscateLiterals bool
flagGarbleTiny bool
flagDebugDir string
flagSeed string
flagLiterals bool
flagTiny bool
flagDebugDir string
flagSeed seedFlag
)
func init() {
flagSet.Usage = usage
flagSet.BoolVar(&flagObfuscateLiterals, "literals", false, "Obfuscate literals such as strings")
flagSet.BoolVar(&flagGarbleTiny, "tiny", false, "Optimize for binary size, losing some ability to reverse the process")
flagSet.BoolVar(&flagLiterals, "literals", false, "Obfuscate literals such as strings")
flagSet.BoolVar(&flagTiny, "tiny", false, "Optimize for binary size, losing some ability to reverse the process")
flagSet.StringVar(&flagDebugDir, "debugdir", "", "Write the obfuscated source to a directory, e.g. -debugdir=out")
flagSet.StringVar(&flagSeed, "seed", "", "Provide a base64-encoded seed, e.g. -seed=o9WDTZ4CN4w\nFor a random seed, provide -seed=random")
flagSet.Var(&flagSeed, "seed", "Provide a base64-encoded seed, e.g. -seed=o9WDTZ4CN4w\nFor a random seed, provide -seed=random")
}
type seedFlag struct {
random bool
bytes []byte
}
func (f seedFlag) String() string {
return base64.RawStdEncoding.EncodeToString(f.bytes)
}
func (f *seedFlag) Set(s string) error {
if s == "random" {
f.bytes = make([]byte, 16) // random 128 bit seed
if _, err := rand.Read(f.bytes); err != nil {
return fmt.Errorf("error generating random seed: %v", err)
}
} else {
// We expect unpadded base64, but to be nice, accept padded
// strings too.
s = strings.TrimRight(s, "=")
seed, err := base64.RawStdEncoding.DecodeString(s)
if err != nil {
return fmt.Errorf("error decoding seed: %v", err)
}
if len(seed) < 8 {
return fmt.Errorf("-seed needs at least 8 bytes, have %d", len(seed))
}
f.bytes = seed
}
return nil
}
func usage() {
@ -100,6 +132,7 @@ func main() { os.Exit(main1()) }
var (
fset = token.NewFileSet()
sharedTempDir = os.Getenv("GARBLE_SHARED")
parentWorkDir = os.Getenv("GARBLE_PARENT_WORK")
// origImporter is a go/types importer which uses the original versions
// of packages, without any obfuscation. This is helpful to make
@ -122,8 +155,6 @@ var (
}
return os.Open(pkgfile)
}).(types.ImporterFrom)
opts *flagOptions
)
type importerWithMap func(path, dir string, mode types.ImportMode) (*types.Package, error)
@ -146,7 +177,7 @@ func obfuscatedTypesPackage(path string) *types.Package {
if pkg := cachedTypesPackages[path]; pkg != nil {
return pkg
}
pkg, err := garbledImporter.ImportFrom(path, opts.GarbleDir, 0)
pkg, err := garbledImporter.ImportFrom(path, parentWorkDir, 0)
if err != nil {
panic(err)
}
@ -175,8 +206,8 @@ func main1() int {
// If the build failed and a random seed was used,
// the failure might not reproduce with a different seed.
// Print it before we exit.
if flagSeed == "random" {
fmt.Fprintf(os.Stderr, "random seed: %s\n", base64.RawStdEncoding.EncodeToString(opts.Seed))
if flagSeed.random {
fmt.Fprintf(os.Stderr, "random seed: %s\n", base64.RawStdEncoding.EncodeToString(flagSeed.bytes))
}
return 1
}
@ -264,7 +295,6 @@ func mainErr(args []string) error {
if err := loadSharedCache(); err != nil {
return err
}
opts = &cache.Options
_, tool := filepath.Split(args[0])
if runtime.GOOS == "windows" {
@ -330,13 +360,9 @@ This command wraps "go %s". Below is its help:
return nil, errJustExit(2)
}
if err := setFlagOptions(); err != nil {
return nil, err
}
// Here is the only place we initialize the cache.
// The sub-processes will parse it from a shared gob file.
cache = &sharedCache{Options: *opts}
cache = &sharedCache{}
// Note that we also need to pass build flags to 'go list', such
// as -tags.
@ -369,11 +395,38 @@ This command wraps "go %s". Below is its help:
}
os.Setenv("GARBLE_SHARED", sharedTempDir)
defer os.Remove(sharedTempDir)
wd, err := os.Getwd()
if err != nil {
return nil, err
}
os.Setenv("GARBLE_PARENT_WORK", wd)
if flagDebugDir != "" {
if !filepath.IsAbs(flagDebugDir) {
flagDebugDir = filepath.Join(wd, flagDebugDir)
}
if err := os.RemoveAll(flagDebugDir); err == nil || errors.Is(err, fs.ErrExist) {
err := os.MkdirAll(flagDebugDir, 0o755)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("debugdir error: %v", err)
}
}
// Pass the garble flags down to each toolexec invocation.
// This way, all garble processes see the same flag values.
var toolexecFlag strings.Builder
toolexecFlag.WriteString("-toolexec=")
toolexecFlag.WriteString(cache.ExecPath)
appendFlags(&toolexecFlag)
goArgs := []string{
command,
"-trimpath",
"-toolexec=" + cache.ExecPath,
toolexecFlag.String(),
}
if flagDebugDir != "" {
// In case the user deletes the debug directory,
@ -595,11 +648,11 @@ func transformCompile(args []string) ([]string, error) {
// because obfuscated literals sometimes escape to heap,
// and that's not allowed in the runtime itself.
if runtimeAndDeps[curPkg.ImportPath] {
opts.ObfuscateLiterals = false
flagLiterals = false
}
// Literal obfuscation uses math/rand, so seed it deterministically.
randSeed := opts.Seed
randSeed := flagSeed.bytes
if len(randSeed) == 0 {
randSeed = curPkg.GarbleActionID
}
@ -621,7 +674,7 @@ func transformCompile(args []string) ([]string, error) {
for i, file := range files {
name := filepath.Base(paths[i])
if curPkg.ImportPath == "runtime" && opts.Tiny {
if curPkg.ImportPath == "runtime" && flagTiny {
// strip unneeded runtime code
stripRuntime(name, file)
}
@ -650,9 +703,9 @@ func transformCompile(args []string) ([]string, error) {
} else {
newPaths = append(newPaths, path)
}
if opts.DebugDir != "" {
if flagDebugDir != "" {
osPkgPath := filepath.FromSlash(curPkg.ImportPath)
pkgDebugDir := filepath.Join(opts.DebugDir, osPkgPath)
pkgDebugDir := filepath.Join(flagDebugDir, osPkgPath)
if err := os.MkdirAll(pkgDebugDir, 0o755); err != nil {
return nil, err
}
@ -1229,7 +1282,7 @@ func (tf *transformer) recordType(t types.Type) {
func (tf *transformer) transformGo(file *ast.File) *ast.File {
// Only obfuscate the literals here if the flag is on
// and if the package in question is to be obfuscated.
if opts.ObfuscateLiterals && curPkg.ToObfuscate {
if flagLiterals && curPkg.ToObfuscate {
file = literals.Obfuscate(file, tf.info, fset, tf.ignoreObjects)
}
@ -1473,7 +1526,7 @@ func locateForeignAlias(dependentImportPath, aliasName string) *types.TypeName {
panic(err) // shouldn't happen
}
for _, importedPath := range lpkg.Imports {
pkg2, err := origImporter.ImportFrom(importedPath, opts.GarbleDir, 0)
pkg2, err := origImporter.ImportFrom(importedPath, parentWorkDir, 0)
if err != nil {
panic(err)
}
@ -1842,7 +1895,7 @@ How to install Go: https://golang.org/doc/install
// path as a GOPRIVATE default. Include a _test variant too.
// TODO(mvdan): we shouldn't need the _test variant here,
// as the import path should not include it; only the package name.
if mod, err := ioutil.ReadFile(cache.GoEnv.GOMOD); err == nil {
if mod, err := os.ReadFile(cache.GoEnv.GOMOD); err == nil {
modpath := modfile.ModulePath(mod)
if modpath != "" {
cache.GOGARBLE = modpath + "," + modpath + "_test"

@ -103,7 +103,7 @@ func printFile(file1 *ast.File) ([]byte, error) {
origNode := origCallExprs[i]
i++
newName := ""
if !opts.Tiny {
if !flagTiny {
origPos := fmt.Sprintf("%s:%d", filename, fset.Position(origNode.Pos()).Offset)
newName = hashWith(curPkg.GarbleActionID, origPos) + ".go"
// log.Printf("%q hashed with %x to %q", origPos, curPkg.GarbleActionID, newName)

@ -2,13 +2,9 @@ package main
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
@ -25,8 +21,6 @@ type sharedCache struct {
ExecPath string // absolute path to the garble binary being used
ForwardBuildFlags []string // build flags fed to the original "garble ..." command
Options flagOptions // garble options being used, i.e. our own flags
// ListedPackages contains data obtained via 'go list -json -export -deps'.
// This allows us to obtain the non-obfuscated export data of all dependencies,
// useful for type checking of the packages as we obfuscate them.
@ -120,73 +114,6 @@ func writeGobExclusive(name string, val interface{}) error {
return err
}
// flagOptions are derived from the flags
type flagOptions struct {
ObfuscateLiterals bool
Tiny bool
GarbleDir string
DebugDir string
Seed []byte
}
// setFlagOptions sets flagOptions from the user supplied flags.
func setFlagOptions() error {
wd, err := os.Getwd()
if err != nil {
return err
}
if cache != nil {
panic("opts set twice?")
}
opts = &flagOptions{
GarbleDir: wd,
ObfuscateLiterals: flagObfuscateLiterals,
Tiny: flagGarbleTiny,
}
if flagSeed == "random" {
opts.Seed = make([]byte, 16) // random 128 bit seed
if _, err := rand.Read(opts.Seed); err != nil {
return fmt.Errorf("error generating random seed: %v", err)
}
} else if len(flagSeed) > 0 {
// We expect unpadded base64, but to be nice, accept padded
// strings too.
flagSeed = strings.TrimRight(flagSeed, "=")
seed, err := base64.RawStdEncoding.DecodeString(flagSeed)
if err != nil {
return fmt.Errorf("error decoding seed: %v", err)
}
if len(seed) < 8 {
return fmt.Errorf("-seed needs at least 8 bytes, have %d", len(seed))
}
opts.Seed = seed
}
if flagDebugDir != "" {
if !filepath.IsAbs(flagDebugDir) {
flagDebugDir = filepath.Join(wd, flagDebugDir)
}
if err := os.RemoveAll(flagDebugDir); err == nil || errors.Is(err, fs.ErrExist) {
err := os.MkdirAll(flagDebugDir, 0o755)
if err != nil {
return err
}
} else {
return fmt.Errorf("debugdir error: %v", err)
}
opts.DebugDir = flagDebugDir
}
return nil
}
// listedPackage contains the 'go list -json -export' fields obtained by the
// root process, shared with all garble sub-processes via a file.
type listedPackage struct {

Loading…
Cancel
Save