|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/gob"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// shared this data is shared between the different garble processes
|
|
|
|
type shared struct {
|
fix garbling names belonging to indirect imports (#203)
main.go includes a lengthy comment that documents this edge case, why it
happened, and how we are fixing it. To summarize, we should no longer
error with a build error in those cases. Read the comment for details.
A few other minor changes were done to allow writing this patch.
First, the actionID and contentID funcs were renamed, since they started
to collide with variable names.
Second, the logging has been improved a bit, which allowed me to debug
the issue.
Third, the "cache" global shared by all garble sub-processes now
includes the necessary parameters to run "go list -toolexec", including
the path to garble and the build flags being used.
Thanks to lu4p for writing a test case, which also applied gofmt to that
testdata Go file.
Fixes #180.
Closes #181, since it includes its test case.
4 years ago
|
|
|
ExecPath string // absolute path to the garble binary being used
|
|
|
|
BuildFlags []string // build flags fed to the original "garble ..." command
|
|
|
|
|
|
|
|
Options options // garble options being used, i.e. our own flags
|
|
|
|
ListedPackages listedPackages // non-garbled view of all packages to build
|
|
|
|
}
|
|
|
|
|
|
|
|
var cache *shared
|
|
|
|
|
|
|
|
// loadShared the shared data passed from the entry garble process
|
|
|
|
func loadShared() error {
|
|
|
|
if cache == nil {
|
|
|
|
f, err := os.Open(filepath.Join(sharedTempDir, "main-cache.gob"))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(`cannot open shared file, this is most likely due to not running "garble [command]"`)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
if err := gob.NewDecoder(f).Decode(&cache); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// saveShared creates a temporary directory to share between garble processes.
|
|
|
|
// This directory also includes the gob-encoded cache global.
|
|
|
|
func saveShared() (string, error) {
|
|
|
|
dir, err := ioutil.TempDir("", "garble-shared")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
sharedCache := filepath.Join(dir, "main-cache.gob")
|
|
|
|
f, err := os.Create(sharedCache)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
if err := gob.NewEncoder(f).Encode(&cache); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return dir, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// options are derived from the flags
|
|
|
|
type options struct {
|
|
|
|
GarbleLiterals bool
|
|
|
|
Tiny bool
|
|
|
|
GarbleDir string
|
|
|
|
DebugDir string
|
|
|
|
Seed []byte
|
|
|
|
Random bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// setOptions sets all options from the user supplied flags.
|
|
|
|
func setOptions() error {
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
opts = &options{
|
|
|
|
GarbleDir: wd,
|
|
|
|
GarbleLiterals: flagGarbleLiterals,
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.Random = true
|
|
|
|
|
|
|
|
} else {
|
|
|
|
flagSeed = strings.TrimRight(flagSeed, "=")
|
|
|
|
seed, err := base64.RawStdEncoding.DecodeString(flagSeed)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error decoding seed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(seed) != 0 && len(seed) < 8 {
|
|
|
|
return fmt.Errorf("the seed needs to be at least 8 bytes, but is only %v bytes", len(seed))
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.Seed = seed
|
|
|
|
}
|
|
|
|
|
|
|
|
if flagDebugDir != "" {
|
|
|
|
if !filepath.IsAbs(flagDebugDir) {
|
|
|
|
flagDebugDir = filepath.Join(wd, flagDebugDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.RemoveAll(flagDebugDir); err == nil || os.IsNotExist(err) {
|
|
|
|
err := os.MkdirAll(flagDebugDir, 0o755)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("debugdir error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.DebugDir = flagDebugDir
|
|
|
|
}
|
|
|
|
|
|
|
|
cache = &shared{Options: *opts}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// listedPackages contains data obtained via 'go list -json -export -deps'. This
|
|
|
|
// allows us to obtain the non-garbled export data of all dependencies, useful
|
|
|
|
// for type checking of the packages as we obfuscate them.
|
|
|
|
//
|
|
|
|
// Note that we obtain this data once in saveListedPackages, store it into a
|
|
|
|
// temporary file via gob encoding, and then reuse that file in each of the
|
|
|
|
// garble processes that wrap a package compilation.
|
|
|
|
type listedPackages map[string]*listedPackage
|
|
|
|
|
|
|
|
// listedPackage contains information useful for obfuscating a package
|
|
|
|
type listedPackage struct {
|
|
|
|
Name string
|
|
|
|
ImportPath string
|
|
|
|
Export string
|
|
|
|
Deps []string
|
|
|
|
ImportMap map[string]string
|
|
|
|
|
reverse: support unexported names and package paths (#233)
Unexported names are a bit tricky, since they are not listed in the
export data file. Perhaps unsurprisingly, it's only meant to expose
exported objects.
One option would be to go back to adding an extra header to the export
data file, containing the unexported methods in a map[string]T or
[]string. However, we have an easier route: just parse the Go files and
look up the names directly.
This does mean that we parse the Go files every time "reverse" runs,
even if the build cache is warm, but that should not be an issue.
Parsing Go files without any typechecking is very cheap compared to
everything else we do. Plus, we save having to load go/types information
from the build cache, or having to load extra headers from export files.
It should be noted that the obfuscation process does need type
information, mainly to be careful about which names can be obfuscated
and how they should be obfuscated. Neither is a worry here; all names
belong to a single package, and it doesn't matter if some aren't
actually obfuscated, since the string replacements would simply never
trigger in practice.
The test includes an unexported func, to test the new feature. We also
start reversing the obfuscation of import paths. Now, the test's reverse
output is as follows:
goroutine 1 [running]:
runtime/debug.Stack(0x??, 0x??, 0x??)
runtime/debug/stack.go:24 +0x??
test/main/lib.ExportedLibFunc(0x??, 0x??, 0x??, 0x??)
p.go:6 +0x??
main.unexportedMainFunc(...)
C.go:2
main.main()
z.go:3 +0x??
The only major missing feature is positions and filenames. A follow-up
PR will take care of those.
Updates #5.
4 years ago
|
|
|
Dir string
|
|
|
|
GoFiles []string
|
|
|
|
|
|
|
|
// TODO(mvdan): reuse this field once TOOLEXEC_IMPORTPATH is used
|
|
|
|
private bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// setListedPackages gets information about the current package
|
|
|
|
// and all of its dependencies
|
fix garbling names belonging to indirect imports (#203)
main.go includes a lengthy comment that documents this edge case, why it
happened, and how we are fixing it. To summarize, we should no longer
error with a build error in those cases. Read the comment for details.
A few other minor changes were done to allow writing this patch.
First, the actionID and contentID funcs were renamed, since they started
to collide with variable names.
Second, the logging has been improved a bit, which allowed me to debug
the issue.
Third, the "cache" global shared by all garble sub-processes now
includes the necessary parameters to run "go list -toolexec", including
the path to garble and the build flags being used.
Thanks to lu4p for writing a test case, which also applied gofmt to that
testdata Go file.
Fixes #180.
Closes #181, since it includes its test case.
4 years ago
|
|
|
func setListedPackages(patterns []string) error {
|
|
|
|
args := []string{"list", "-json", "-deps", "-export"}
|
fix garbling names belonging to indirect imports (#203)
main.go includes a lengthy comment that documents this edge case, why it
happened, and how we are fixing it. To summarize, we should no longer
error with a build error in those cases. Read the comment for details.
A few other minor changes were done to allow writing this patch.
First, the actionID and contentID funcs were renamed, since they started
to collide with variable names.
Second, the logging has been improved a bit, which allowed me to debug
the issue.
Third, the "cache" global shared by all garble sub-processes now
includes the necessary parameters to run "go list -toolexec", including
the path to garble and the build flags being used.
Thanks to lu4p for writing a test case, which also applied gofmt to that
testdata Go file.
Fixes #180.
Closes #181, since it includes its test case.
4 years ago
|
|
|
args = append(args, cache.BuildFlags...)
|
|
|
|
args = append(args, patterns...)
|
|
|
|
cmd := exec.Command("go", args...)
|
|
|
|
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
cmd.Stderr = &stderr
|
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return fmt.Errorf("go list error: %v", err)
|
|
|
|
}
|
|
|
|
dec := json.NewDecoder(stdout)
|
|
|
|
cache.ListedPackages = make(listedPackages)
|
|
|
|
for dec.More() {
|
|
|
|
var pkg listedPackage
|
|
|
|
if err := dec.Decode(&pkg); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cache.ListedPackages[pkg.ImportPath] = &pkg
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cmd.Wait(); err != nil {
|
|
|
|
return fmt.Errorf("go list error: %v: %s", err, stderr.Bytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
anyPrivate := false
|
|
|
|
for path, pkg := range cache.ListedPackages {
|
|
|
|
if isPrivate(path) {
|
|
|
|
pkg.private = true
|
|
|
|
anyPrivate = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !anyPrivate {
|
|
|
|
return fmt.Errorf("GOPRIVATE=%q does not match any packages to be built", os.Getenv("GOPRIVATE"))
|
|
|
|
}
|
|
|
|
for path, pkg := range cache.ListedPackages {
|
|
|
|
if pkg.private {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, depPath := range pkg.Deps {
|
|
|
|
if cache.ListedPackages[depPath].private {
|
|
|
|
return fmt.Errorf("public package %q can't depend on obfuscated package %q (matched via GOPRIVATE=%q)",
|
|
|
|
path, depPath, os.Getenv("GOPRIVATE"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// listPackage gets the listedPackage information for a certain package
|
|
|
|
func listPackage(path string) (*listedPackage, error) {
|
|
|
|
// If the path is listed in the top-level ImportMap, use its mapping instead.
|
|
|
|
// This is a common scenario when dealing with vendored packages in GOROOT.
|
|
|
|
// The map is flat, so we don't need to recurse.
|
|
|
|
if fromPkg, ok := cache.ListedPackages[curPkgPath]; ok {
|
|
|
|
if path2 := fromPkg.ImportMap[path]; path2 != "" {
|
|
|
|
path = path2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pkg, ok := cache.ListedPackages[path]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("path not found in listed packages: %s", path)
|
|
|
|
}
|
|
|
|
return pkg, nil
|
|
|
|
}
|