From c9deff810b6091955c5bd7f43cdd9dc7e4488426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Sat, 14 Nov 2020 14:51:35 +0000 Subject: [PATCH] obfuscate fewer std packages (#196) Previously, we were never obfuscating runtime and its direct dependencies. Unfortunately, due to linkname, the runtime package is actually closely related to dozens of other std packages as well. Until we can obfuscate the runtime and properly support go:linkname directives, obfuscating fewer std packages is a better outcome than breaking and not producing any obfuscated code at all. The added test case is building runtime/pprof, which used to cause failures: # runtime/pprof /go/src/runtime/pprof/label.go:27:21: undefined: context.Context /go/src/runtime/pprof/label.go:59:21: undefined: context.Context /go/src/runtime/pprof/label.go:93:16: undefined: context.Context /go/src/runtime/pprof/label.go:101:20: undefined: context.Context The net package was also very close to obfuscating properly thanks to this change, so its test is now run as well. The only other remaining fix was to not obfuscate fields on cgo types, since those aren't obfuscated at the moment. The map is pretty long, but it's only a temporary solution and the command to obtain the list again is included. Never obfuscating the entire std library is also an option, but it's a bit unnecessary. Fixes #134. --- main.go | 74 +++++++++++++++++++++++++++++++--- scripts/runtime-related.sh | 19 +++++++++ shared.go | 1 - testdata/scripts/goprivate.txt | 23 +++++++---- 4 files changed, 104 insertions(+), 13 deletions(-) create mode 100755 scripts/runtime-related.sh diff --git a/main.go b/main.go index 26a69cb..13a3dc1 100644 --- a/main.go +++ b/main.go @@ -572,17 +572,75 @@ func transformCompile(args []string) ([]string, error) { return append(flags, newPaths...), nil } -// neverPrivate is a core set of std packages which we cannot obfuscate. -// At the moment, this is the runtime package and its (few) dependencies. -// This might change in the future. -const neverPrivate = "runtime,internal/cpu,internal/bytealg,unsafe" +// runtimeRelated is a snapshot of all the packages runtime depends on, or +// packages which the runtime points to via go:linkname. +// +// Once we support go:linkname well and once we can obfuscate the runtime +// package, this entire map can likely go away. +// +// The list was obtained via scripts/runtime-related.sh on Go 1.15.5. +var runtimeRelated = map[string]bool{ + "bufio": true, + "bytes": true, + "compress/flate": true, + "compress/gzip": true, + "context": true, + "encoding/binary": true, + "errors": true, + "fmt": true, + "hash": true, + "hash/crc32": true, + "internal/bytealg": true, + "internal/cpu": true, + "internal/fmtsort": true, + "internal/oserror": true, + "internal/poll": true, + "internal/race": true, + "internal/reflectlite": true, + "internal/syscall/execenv": true, + "internal/syscall/unix": true, + "internal/syscall/windows": true, + "internal/syscall/windows/registry": true, + "internal/syscall/windows/sysdll": true, + "internal/testlog": true, + "internal/unsafeheader": true, + "io": true, + "io/ioutil": true, + "math": true, + "math/bits": true, + "os": true, + "os/signal": true, + "path/filepath": true, + "plugin": true, + "reflect": true, + "runtime": true, + "runtime/cgo": true, + "runtime/debug": true, + "runtime/internal/atomic": true, + "runtime/internal/math": true, + "runtime/internal/sys": true, + "runtime/pprof": true, + "runtime/trace": true, + "sort": true, + "strconv": true, + "strings": true, + "sync": true, + "sync/atomic": true, + "syscall": true, + "text/tabwriter": true, + "time": true, + "unicode": true, + "unicode/utf16": true, + "unicode/utf8": true, + "unsafe": true, +} // isPrivate checks if GOPRIVATE matches path. // // To allow using garble without GOPRIVATE for standalone main packages, it will // default to not matching standard library packages. func isPrivate(path string) bool { - if module.MatchPrefixPatterns(neverPrivate, path) { + if runtimeRelated[path] { return false } if path == "main" || path == "command-line-arguments" || strings.HasPrefix(path, "plugin/unnamed") { @@ -837,6 +895,7 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File { // if the struct of this field was not garbled, do not garble // any of that struct's fields if (parentScope != tf.pkg.Scope()) && (x.IsField() && !x.Embedded()) { + // fmt.Println(node.Name) parent, ok := cursor.Parent().(*ast.SelectorExpr) if !ok { break @@ -849,6 +908,11 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File { if named == nil { break } + if name := named.Obj().Name(); strings.HasPrefix(name, "_Ctype") { + // A field accessor on a cgo type, such as a C struct. + // We're not obfuscating cgo names. + return true + } if garbledPkg, _ := garbledImport(path); garbledPkg != nil { if garbledPkg.Scope().Lookup(named.Obj().Name()) != nil { blacklistStruct(named, tf.blacklist) diff --git a/scripts/runtime-related.sh b/scripts/runtime-related.sh new file mode 100755 index 0000000..e34feaa --- /dev/null +++ b/scripts/runtime-related.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# This script is hacky, but lets us list all packages depended on by runtime, or +# related to runtime via go:linkname. +# +# Once we can obfuscate the runtime package, this script can probably be +# deleted. + +go version +echo + +for GOOS in linux darwin windows; do + skip="macos" + if [[ $GOOS == "darwin" ]]; then + skip="" + fi + + GOOS=$GOOS go list -deps $(sed -rn 's@//go:linkname .* ([^.]*)\.[^.]*@\1@p' $(go env GOROOT)/src/runtime/*.go | grep -v '^main\|\.\|js\|'$skip) runtime || exit 1 +done | sort -u diff --git a/shared.go b/shared.go index a33f3f2..6e50550 100644 --- a/shared.go +++ b/shared.go @@ -201,7 +201,6 @@ func setListedPackages(flags, patterns []string) error { } return nil - } // listPackage gets the listedPackage information for a certain package diff --git a/testdata/scripts/goprivate.txt b/testdata/scripts/goprivate.txt index 6e92a78..fba670e 100644 --- a/testdata/scripts/goprivate.txt +++ b/testdata/scripts/goprivate.txt @@ -8,11 +8,23 @@ stderr '^public package "test/main/importer" can''t depend on obfuscated package [short] stop -# Try garbling all of std. -# This used to fail since the "net" import causes 'go list -json' to output -# ImportMap, since "net" imports packages vendored in std. +# Try garbling all of std, given some std packages. +# No need for a main package here; building the std packages directly works the +# same, and is faster as we don't need to link a binary. env GOPRIVATE='*' -garble build -o=out ./standalone + +# This used to cause errors since the "net" import causes 'go list -json' +# to output ImportMap, since "net" imports packages vendored in std. +# Another quirk of "net" is that it makes rather heavy use of cgo, which was +# hitting some edge cases we did not handle. +garble build net + +# This used to cause incorrect errors since we would not obfuscate +# runtime/pprof, but we would try to obfuscate its dependencies. For now, this +# simply errors because we obfuscate nothing, since we can't obfuscate the +# runtime package just yet. +! garble build runtime/pprof +stderr 'does not match any packages to be built' -- go.mod -- module test/main @@ -21,9 +33,6 @@ go 1.15 -- standalone/main.go -- package main -// TODO: This tests that #146 is fixed, but is blocked by errors garbling 'net' -//import _ "net" - func main() {} -- importer/importer.go -- package importer