Commit Graph

264 Commits (745d089a9dde5d8b5a76a048c960b0545b4da5d5)

Author SHA1 Message Date
Daniel Martí 7c2866356f support obfuscating the syscall package
One more package that further unblocks obfuscating the runtime.
The issue was the TODO we already had about go:linkname directives with
just one argument, which are used in the syscall package.

While here, factor out the obfuscation of linkname directives into
transformLinkname, as it was starting to get a bit complex.
We now support debug logging as well, while still being able to use
"early returns" for some cases where we bail out.

We also need listPackage to treat all runtime sub-packages like it does
runtime itself, as `runtime/internal/syscall` linknames into `syscall`
without it being a dependency as well.

Finally, add a regression test that, without the fix,
properly spots that the syscall package was not obfuscated:

	FAIL: testdata/script/gogarble.txtar:41: unexpected match for ["syscall.RawSyscall6"] in out

Updates #193.
2 years ago
Daniel Martí 58b2d64784 drop support for Go 1.18.x
With Go 1.19 having been out for two months,
and Go 1.20's first beta coming out in two months,
it is now time to move forward again.
2 years ago
Daniel Martí ac0945eaa5 work around cmd/go issue relating to CompiledGoFiles
See https://golang.org/issue/28749. The improved asm test would fail:

	go parse: $WORK/imported/imported_amd64.s:1:1: expected 'package', found TEXT (and 2 more errors)

because we would incorrectly parse a non-Go file as a Go file.

Add a workaround. The original reporter's reproducer with go-ethereum
works now, as this was the last hiccup.

Fixes #555.
2 years ago
Daniel Martí e8e06f6ad6 support reverse on packages using cgo
The reverse feature relied on `GoFiles` from `go list`,
but that list may not be enough to typecheck a package:

	typecheck error: $WORK/main.go:3:15: undeclared name: longMain

`go help list` shows:

	GoFiles         []string   // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
	CgoFiles        []string   // .go source files that import "C"
	CompiledGoFiles []string   // .go files presented to compiler (when using -compiled)

In other words, to mimic the same list of Go files fed to the compiler,
we want CompiledGoFiles.

Note that, since the cgo files show up as generated files,
we currently do not support reversing their filenames.
That is left as a TODO for now.

Updates #555.
2 years ago
Daniel Martí fc91758b49 obfuscate Go names in asm header files
Assembly files can include header files within the same Go module,
and those header files can include "defines" which refer to Go names.

Since those Go names are likely being obfuscated,
we need to replace them just like we do in assembly files.

The added mechanism is rather basic; we add two TODOs to improve it.
This should help when building projects like go-ethereum.

Fixes #553.
2 years ago
Daniel Martí f9d99190d2 use -toolexec="garble toolexec"
This way, the child process knows that it's running a toolchain command
via -toolexec without having to guess via filepath.IsAbs.

While here, improve the docs and tests a bit.
2 years ago
Daniel Martí 99c12e396a replace testdata/scripts/*.txt with testdata/script/*.txtar
Following the best practices from upstream.
In particular, the "txt" extension is somewhat ambiguous.

This may cause some conflicts due to the git diff noise,
but hopefully we won't ever do this again.
2 years ago
Daniel Martí 9d46fe917a avoid a type assertion panic with generic code
I was wrongly assumed that, if `used` has an `Elem` method,
then `origin` must too. But it does not if it's a type parameter.

Add a test case too, which panicked before the fix.

Fixes #577.
2 years ago
Daniel Martí 8ad374d2fb start testing on Go 1.19.x
While here, start the changelog for the upcoming release,
which will likely be a bugfix release as it's a bit early to drop 1.18.

We also bump staticcheck to get a version that supports 1.19.

I also noticed the "Go version X or newer" messages were slightly weird
and inconsistent. Our policy, per the README, is "Go version X or newer",
so the errors given to the user were unnecessarily confusing.
For example, now that Go 1.19 is out, we shouldn't simply recommend that
they upgrade to 1.18; we should recommend 1.18 or later.
2 years ago
Daniel Martí 60dbece24f work around another go/printer bug to fix andybalholm/brotli
When obfuscating the following piece of code:

	func issue_573(s struct{ f int }) {
		var _ *int = &s.f
		/*x*/
	}

the function body would roughly end up printed as:
we would roughly end up with:

	var _ *int = &dZ4xYx3N
	/*x*/.rbg1IM3V

Note that the /*x*/ comment got moved earlier in the source code.
This happens because the new identifiers are longer, so the printer
thinks that the selector now ends past the comment.

That would be fine - we don't really mind where comments end up,
because these non-directive comments end up being removed anyway.

However, the resulting syntax is wrong, as the period for the selector
must be on the first line rather than the second.
This is a go/printer bug that we should fix upstream,
but until then, we must work around it in Go 1.18.x and 1.19.x.

The fix is somewhat obvious in hindsight. To reduce the chances that
go/printer will trip over comments and produce invalid syntax,
get rid of most comments before we use the printer.
We still keep the removal of comments after printing,
since go/printer consumes some comments in ast.Node Doc fields.

Add the minimized unit test case above, and add the upstream project
that found this bug to check-third-party.
andybalholm/brotli helps cover a compression algorithm and ccgo code
generation from C to Go, and it's also a fairly popular module,
particular with HTTP implementations which want pure-Go brotli.

While here, fix the check-third-party script: it was setting GOFLAGS
a bit too late, so it may run `go get` on the wrong mod file.

Fixes #573.
2 years ago
Daniel Martí d0c6ccd63d sleep between cp and exec in test scripts
Every now and then, I get test failures in the goenv test like:

	> [!windows] cp $EXEC_PATH $NAME/garble$exe
	> [!windows] exec $NAME/garble$exe build
	[fork/exec with"double"quotes/garble: text file busy]
	FAIL: testdata/scripts/goenv.txt:21: unexpected command failure

The root cause is https://go.dev/issue/22315, which isn't going to be
fixed anytime soon, as it is a race condition in Linux itself, triggered
by how heavily concurrent Go tends to be.

For now, try to make the race much less likely to happen.
2 years ago
Daniel Martí 4155854a2e go 1.18.x now sets -buildvcs=false for `go test`
That is, since Go 1.18.1, released back in April 2022.
We no longer need to worry about the buggy Go 1.18.0.

While here, use a clearer env var name; the settings are build settings.
2 years ago
Daniel Martí 8d36e1d80e avoid go/printer from breaking imports
We obfuscate import paths in import declarations like:

	"domain.com/somepkg"

by replacing them with the obfuscated package path:

	somepkg "HPS4Mskq"

Note how we add a name to the import if there wasn't one,
so that references like somepkg.Foo keep working in the code.

This could break in some edge cases involving comments between imports
in the Go code, because go/printer is somewhat brittle with positions:

	> garble build -tags buildtag
	[stderr]
	# test/main/importedpkg
	:16: syntax error: missing import path
	exit status 2
	exit status 2

To prevent that, ensure the name has a reasonable position.

This was preventing github.com/gorilla/websocket from being obufscated.
It is a fairly popular library in Go, but we don't add it to
scripts/check-third-party.sh for now as wireguard already gives us
coverage over networking and cryptography.
2 years ago
Daniel Martí 21dfbd3379 obfuscate cgo-generated-Go filenames
It's not a problem to leak filenames like _cgo_gotypes.go,
but it is a problem when it includes the import path:

	$ strings main | grep _cgo_gotypes
	test/main/_cgo_gotypes.go

Here, "test/main" is the module path, which we want to hide.
We hadn't caught this before because the cgo.txt test did not check that
module paths aren't being leaked - it does now.

The fix is rather simple; we let printFile handle cgo-generated files.
We used to avoid that due to compiler errors, as the compiler only
allows some special cgo comment directives to work in cgo-generated
code, to prevent misuse in user code.

The fix is rather easy: the obfuscated filenames should begin with
"_cgo_" to appease the compiler's check.
2 years ago
Daniel Martí 2d12f41e71 actually remove temporary directories after obfuscation
Back in February 2021, we changed the obfuscation logic so that the
entire `garble build` process would use one shared temporary directory
across all package builds, reducing the amount of files we created in
the top-level system temporary directory.

However, we made one mistake: we didn't swap os.Remove for os.RemoveAll.
Ever since then, we've been leaving temporary files behind.

Add regression tests, which failed before the fix, and fix the bug.
Note that we need to test `garble reverse` as well, as it calls
toolexecCmd separately, so it needs its own cleanup as well.

The cleanup happens via the env var, which doesn't feel worse than
having toolexecCmd return an extra string or cleanup func.

While here, also test that we support TMPDIRs with special characters.
2 years ago
Daniel Martí 21bd89ff73 slight simplifications and alloc reductions
Reuse a buffer and a map across loop iterations, because we can.

Make recordTypeDone only track named types, as that is enough to detect
type cycles. Without named types, there can be no cycles.

These two reduce allocs by a fraction of a percent:

	name      old time/op         new time/op         delta
	Build-16          10.4s ± 2%          10.4s ± 1%    ~     (p=0.739 n=10+10)

	name      old bin-B           new bin-B           delta
	Build-16          5.51M ± 0%          5.51M ± 0%    ~     (all equal)

	name      old cached-time/op  new cached-time/op  delta
	Build-16          391ms ± 9%          407ms ± 7%    ~     (p=0.095 n=10+9)

	name      old mallocs/op      new mallocs/op      delta
	Build-16          34.5M ± 0%          34.4M ± 0%  -0.12%  (p=0.000 n=10+10)

	name      old sys-time/op     new sys-time/op     delta
	Build-16          5.87s ± 5%          5.82s ± 5%    ~     (p=0.182 n=10+9)

It doesn't seem like much, but remember that these stats are for the
entire set of processes, where garble only accounts for about 10% of the
total wall time when compared to the compiler or linker. So a ~0.1%
decrease globally is still significant.

linkerVariableStrings is also indexed by *types.Var rather than types.Object,
since -ldflags=-X only supports setting the string value of variables.
This shouldn't make a significant difference in terms of allocs,
but at least the map is less prone to confusion with other object types.
To ensure the new code doesn't trip up on non-variables, we add test cases.

Finally, for the sake of clarity, index into the types.Info maps like
Defs and Uses rather than calling ObjectOf if we know whether the
identifier we have is a definition of a name or the use of a defined name.
This isn't better in terms of performance, as ObjectOf is a tiny method,
but just like with linkerVariableStrings before, the new code is clearer.
2 years ago
Daniel Martí 3fbefbbacf fix TODOs about code which is now unused
The _gomod_.go file inserted by the Go toolchain no longer shows up;
it's likely that either the -trimpath or -buildvcs=false flags are
preventing that extra bit of work from happening entirely.
The modinfo.txt test ensures that we're not breaking,
and the inner lines of code weren't hit as part of `go test`.

It also appears that we don't need to avoid obfuscating functions
defined with an `//export` directive. This is likely because cgo runs as
a pre-process step compared to the compiler, so us removing the
directive later does not make a difference.
We might need to revisit this in the future if we implement obfuscating
Go code instead of builds, e.g. `garble export`.

Just in case, I've expanded the cgo.txt test to also include one more
kind of cgo integration: an "import C" block including a C header file.

Either of these changes are slightly risky, as our tests don't cover all
edge cases. We've just done a release, so now is the time to try them.
2 years ago
Daniel Martí f37561589b properly quote the path to garble in -toolexec
If we don't quote it, paths containing spaces or quote characters will
fail. For instance, the added test without the fix fails:

        > env NAME='with spaces'
        > mkdir $NAME
        > cp $EXEC_PATH $NAME/garble$exe
        > exec $NAME/garble$exe build main.go
        [stderr]
        go tool compile: fork/exec $WORK/with: no such file or directory
        exit status 1

Luckily, the fix is easy: we bundle Go's cmd/internal/quoted package,
which implements a QuotedJoin API for this very purpose.

Fixes #544.
2 years ago
Daniel Martí 9fc19e8bdf support import paths ending with ".go"
When splitFlagsFromFiles saw "-p foo/bar.go",
it thought that was the first Go file, when in fact it's not.
We didn't notice because such import paths are pretty rare,
but they do exist, like github.com/nats-io/nats.go.

Before the fix, the added test case fails as expected:

	> garble build -tags buildtag
	[stderr]
	# test/main/goextension.go
	open test/main/goextension.go: no such file or directory

We could go through the trouble of teaching splitFlagsFromFiles about
all of the flags understood by the compiler and linker, but that feels
like far more code than the small alternative we went with.
And I'm pretty sure the alternative will work pretty reliably for now.

Fixes #539.
2 years ago
Daniel Martí 7fb390e403 fix support with the latest Go master version
It added packages which are only built with the boringcrypto build tag,
so trying to `go list` them will fail even though it doesn't matter.

While here, a few more minor cleanups:

1) Hide GarbleActionID and ToObfuscate from encoding/json, so that they
   can't possibly collide with the fields consumed from `go list -json`.

2) Add test cases for `garble build` with packages that fail to load.
   Note that this requires GOGARBLE=* to avoid its "does not match any
   package to be built" error.

3) Remove the last use of interface{}, in a testdata file.

Fixes #531.
2 years ago
lu4p 84ba444b7c
Disable seed obfuscator (#535)
The seed obfuscator uses a type declaration in order to declare a function,
which returns a function with the same type.

This breaks when obfuscating literals inside generic functions, because
type declarations inside generic functions are not currently supported.

Therefore the obfuscator gets disabled until
https://github.com/golang/go/issues/47631 is fixed.
2 years ago
Daniel Martí 14d3803a7b fix hashing of generic field names
Trying to make Go master work, I noticed that crypto/tls still failed to
build. The reason was generic structs; we would badly obfuscate their
field names when the types are instantiated:

	> garble build
	[stderr]
	# test/main
	Z4ZpcbMj.go:4: unknown field 'FOpszkrN' in struct literal of type SYdpWfK5[string]
	Z4ZpcbMj.go:5: m8hLTotb.FypXrbTd undefined (type SYdpWfK5[string] has no field or method FypXrbTd)
	exit status 2

See the added comment for what happened and how we fixed it. And add tests.
2 years ago
Daniel Martí 1a3d7868d9 make "garble version" friendlier for devel builds
The proposal at https://go.dev/issue/50603 has been approved,
so Go will at some point start producing module pseudo-versions
even if the main module was built from a VCS clone.

To not wait until a future release like Go 1.20,
implement that ourselves with the help of module.PseudoVersion.

The result is a friendlier version output; what used to be

	$ go install && garble version
	mvdan.cc/garble (devel)

	Build settings:
	[...]

will now look like

	$ go install && garble version
	mvdan.cc/garble v0.0.0-20220505210747-22e3d30216be

	Build settings:
	[...]

Note that we don't use VCS tags in any way, so the prefix is hard-coded
as v0.0.0. That seems fine for development builds, and Go doesn't embed
VCS tag information in binaries anyway.

Finally, note that we start printing the module sum, as it's redundant.
The VCS commit hash, at least in git, should be unique enough.
2 years ago
Daniel Martí 61bd95bb89 add a test with generic code
This ensures that we support obfuscating builds containing the use of
type parameters, the new feature in Go 1.18.
The test is small for now, but we can extend it over time.

There was just one bug that kept the code from obfuscating properly;
that has been fixed in https://go.dev/cl/405194,
and we update x/tools to the latest master version to include it.

Fixes #414.
2 years ago
shellhazard 22e3d30216
support code taking the address of a []byte literal (#530) 2 years ago
Daniel Martí 79b6e4db3d skip unnecessary "refusing to list" test errors
The added test case reproduces the failure if we uncomment the added
"continue" line in processImportCfg:

	# test/bar/exporttest [test/bar/exporttest.test]
	panic: refusing to list non-dependency package: test/bar/exporttest

	goroutine 1 [running]:
	mvdan.cc/garble.processImportCfg({0xc000166780?, 0xc0001f4a70?, 0x2?})
		/home/mvdan/src/garble/main.go:983 +0x58b
	mvdan.cc/garble.transformCompile({0xc000124020?, 0x11?, 0x12?})
		/home/mvdan/src/garble/main.go:736 +0x338

It seems like a quirk of cmd/go that it includes a redundant packagefile
line in this particular edge case, but it's generally harmless for "go
build". For "garble build" it's also harmless in principle, but in
practice we had sanity checks that got upset by the unexpected line.

For now, notice the edge case and ignore it.

Fixes #522.
2 years ago
Daniel Martí e3a59eae07 add missing context to two unmarshal errors
Returning a json or gob error directly to the user is generally not
helpful, as it lacks any form of context.
For example, from the json unmarshal of "go env -json":

	$ garble build
	invalid character 'w' looking for beginning of value

Also improve the error when the user ran garble in the wrong way,
resulting in no shared gob file. The context is now shorter, and we also
include the os.Open error in case it contains any useful details.

While here, apply Go tip's gofmt, which reformatted a godoc list.

For #523.
2 years ago
Daniel Martí 20ff64128e make KnownReflectAPIs aware of variadic funcs
When a function definition is variadic,
the number of parameters may not match the number of calling arguments.
Our existing code was a bit naive with this edge case,
leading to a panic when indexing call.Args.

Keep track of that information, and properly handle all variadic
arguments that may be used indirectly with reflection.

We add a test case that used to panic, where 0 arguments are used for a
variadic parameter, as well as a case where we need to disable
obfuscation for a Go type used as the second variadic argument rather
than the first.

Finally, the old code in findReflectFunctions looked at Go function
types from the point of view of go/ast.FuncType.
Use go/types.Signature instead, where we don't need to deal with the
grouping of variables in the original syntax, and which is more
consistent with the rest of the garble codebase.

Fixes #524.
2 years ago
pagran c8e0abf9c9 Fix removing named imports and fix removing imports with init() methods 2 years ago
Daniel Martí 6a39ad2d81 make "garble version" include VCS information
When someone builds garble from a git clone,
the resulting binary used to not contain any information:

	$ garble version
	(devel)

Since Go 1.18, VCS information is stamped by default into binaries.
We now print it, alongside any other available build settings:

	$ garble version
	mvdan.cc/garble (devel)

	Build settings:
	       -compiler gc
	     CGO_ENABLED 1
	          GOARCH amd64
	            GOOS linux
	         GOAMD64 v3
	             vcs git
	    vcs.revision 91ea246349
	        vcs.time 2022-03-18T13:45:11Z
	    vcs.modified true

Note that it's still possible for a garble build to contain no useful
version information, such as when built via "go build -buildvcs=false".
However, if a user opts into omitting the information, it's on them to
figure out what version of garble they actually built.

While here, bump test-gotip.

Fixes #491.
2 years ago
Daniel Martí 1c564ef091 slightly improve code thanks to Go 1.18 APIs
strings.Cut makes some string handling code more intuitive.
Note that we can't use it everywhere, as some places need LastIndexByte.

Start using x/exp/slices, too, which is our first use of generics.
Note that its API is experimental and may still change,
but since we are not a library, we can control its version updates.

I also noticed that we were using TrimSpace for importcfg files.
It's actually unnecessary if we swap strings.SplitAfter for Split,
as the only whitespace present was the trailing newline.

While here, I noticed an unused copy of printfWithoutPackage.
2 years ago
lu4p 1a0b028db7 all: drop support for Go 1.17
Now that we've released v0.6.0, that will be the last feature release to
feature support for Go 1.17. The upcoming v0.7.0 will be Go 1.18+.

Code-wise, the cleanup here isn't super noticeable,
but it will be easier to work on features like VCS-aware version
information and generics support without worrying about Go 1.17.
Plus, now CI is back to being much faster.

Note how "go 1.18" in go.mod makes "go mod tidy" more aggressive.
2 years ago
lu4p d555639657 Remove unused imports via go/types.
Fixes #481
2 years ago
Daniel Martí ab39ee804d fail if the current Go version is newer than what built garble
For instance, Go 1.18 added support for generics, so its compiler output
files changed format to accomodate for the new language feature.
If garble is built with Go 1.17 and then used to perform builds on Go
1.18, it will fail in a very confusing way, because garble's go/types
and go/importer packages will not know how to deal with that.

As already discussed in #269, require the version that built the garble
binary to be equal or newer. In that thread we discussed only comparing
the major version, so for example garble built on go1.18 could be used
on the toolchain go1.18.5. However, that could still fail in confusing
ways if a fix to go/types or go/importer happened in a point release.

While here, I noticed that we were still using Go 1.17 for some CI
checks. Fix that, except for staticcheck.

Fixes #269.
2 years ago
Daniel Martí 8b55dd4bd2 work around a build cache regression in the previous commit
The added comment in main.go explains the situation in detail.
The added test is a minimization of the scenario, which failed:

        > cd mod1
        > garble -seed=${SEED1} build -v gopkg.in/garbletest.v2
        > cd ../mod2
        > garble -seed=${SEED1} build -v
        [stderr]
        test/main/mod2
        # test/main/mod2
        cannot load garble export file for gopkg.in/garbletest.v2: open […]/go-build/ed/[…]-garble-ZV[…]-d: no such file or directory

To work around the problem, we'll always add each package's
GarbleActionID to its build artifact, even when not using -seed.
This will get us the previous behavior with regards to the build cache,
meaning that we fix the recent regression.
The added variable doesn't make it to the final binary either.

While here, improve the cached file loading error's context,
and add an extra sanity check for duplicates on ListedPackages.
2 years ago
Daniel Martí c1c90fee13 make obfuscation fully deterministic with -seed
The default behavior of garble is to seed via the build inputs,
including the build IDs of the entire Go build of each package.
This works well as a default, and does give us determinism,
but it means that building for different platforms
will result in different obfuscation per platform.

Instead, when -seed is provided, don't use any other hash seed or salt.
This means that a particular Go name will be obfuscated the same way
as long as the seed, package path, and name itself remain constant.

In other words, when the user supplies a custom -seed,
we assume they know what they're doing in terms of storage and rotation.

Expand the README docs with more examples and detail.

Fixes #449.
2 years ago
Daniel Martí ad87a0e2bc don't let -debug affect the build cache hashes
Noticed while debugging #492,
as adding -debug to a previously run command would unexpectedly
rebuild all packages in the build, as if -a was given.

While here, remove commented out testscript that were kept in error.
2 years ago
Daniel Martí 88a27d491b add support for -ldflags using quotes
In particular, using -ldflags with -

In particular, a command like:

	garble -literals build -ldflags='-X "main.foo=foo bar"'

would fail, because we would try to use "\"main" as the package name for
the -X qualified name, with the leading quote character.

This is because we used strings.Split(ldflags, " ").
Instead, use the same quoted.Split that cmd/go uses,
copied over thanks to x/tools/cmd/bundle and go:generate.

Updates #492.
2 years ago
Daniel Martí a9a721e352 concentrate and simplify "to obfuscate" logic
Back in the day, we used to call toObfuscate anytime we needed to know
whether a package should be obfuscated.
More recently, we started computing via the ToObfuscate field,
which then gets shared with all sub-processes via sharedCache.

We still had two places that directly called toObfuscate.
Replace those with ToObfuscate, and inline toObfuscate into shared.go.

obfuscatedImportPath is also a potential footgun for main packages.
Some use cases always want the original "main" package name,
such as for use in the compiler's "-p main" flag,
while other cases want the obfuscated package import path,
such as the entries in importcfg files.

Since each of these call sites handles the edge case well,
obfuscatedImportPath now panics on main packages to avoid any misuse.

Finally, test that we never leak main package paths via ldflags.txt.
We never did, but it's good to make sure.

Overall, this avoids confusion and trims the size of main.go a bit.
2 years ago
Daniel Martí 955c24856c properly record when type aliases are embedded as fields
There are two scenarios when it comes to embedding fields.
The first is easy, and we always handled it well:

	type Named struct { Foo int }

	type T struct { Named }

In this scenario, T ends up with an embedded field named "Named",
and a promoted field named "Foo".

Then there's the form with a type alias:

	type Named struct { Foo int }

	type Alias = Named

	type T struct { Alias }

This case is different: T ends up with an embedded field named "Alias",
and a promoted field named "Foo".
Note how the field gets its name from the referenced type,
even if said type is just an alias to another type.

This poses two problems.
First, we must obfuscate the field T.Alias as the name "Alias",
and not as the name "Named" that the alias points to.
Second, we must be careful of cases where Named and Alias are declared
in different packages, as they will obfuscate the same name differently.

Both of those problems compounded in the reported issue.
The actual reason is that quic-go has a type alias in the form of:

	type ConnectionState = qtls.ConnectionState

In other words, the entire problem boils down to a type alias which
points to a named type in a different package, where both types share
the same name. For example:

	package parent

	import "parent/p1"

	type T struct { p1.SameName }

	[...]

	package p1

	import "parent/p2"

	type SameName = p2.SameName

	[...]

	package p2

	type SameName struct { Foo int }

This broke garble because we had a heuristic to detect when an embedded
field was a type alias:

	// Instead, detect such a "foreign alias embed".
	// If we embed a final named type,
	// but the field name does not match its name,
	// then it must have been done via an alias.
	// We dig out the alias's TypeName via locateForeignAlias.
	if named.Obj().Name() != node.Name {

As the reader can deduce, this heuristic would incorrectly assume that
the snippet above does not embed a type alias, when in fact it does.
When obfuscating the field T.SameName, which uses a type alias,
we would correctly obfuscate the name "SameName",
but we would incorrectly obfuscate it with the package p2, not p1.
This would then result in build errors.

To fix this problem for good, we need to get rid of the heuristic.
Instead, we now mimic what was done for KnownCannotObfuscate,
but for embedded fields which use type aliases.
KnownEmbeddedAliasFields is now filled for each package
and stored in the cache as part of cachedOutput.
We can then detect the "embedded alias" case reliably,
even when the field is declared in an imported package.

On the plus side, we get to remove locateForeignAlias.
We also add a couple of TODOs to record further improvements.
Finally, add a test.

Fixes #466.
2 years ago
Daniel Martí 4c3b90c051 stop loading obfuscated type information from deps
If package P1 imports package P2, P1 needs to know which names from P2
weren't obfuscated. For instance, if P2 declares T2 and does
"reflect.TypeOf(T2{...})", then P2 won't obfuscate the name T2, and
neither should P1.

This information should flow from P2 to P1, as P2 builds before
P1. We do this via obfuscatedTypesPackage; P1 loads the type information
of the obfuscated version of P2, and does a lookup for T2. If T2 exists,
then it wasn't obfuscated.

This mechanism has served us well, but it has downsides:

1) It wastes CPU; we load the type information for the entire package.

2) It's complex; for instance, we need KnownObjectFiles as an extra.

3) It makes our code harder to understand, as we load both the original
   and obfuscated type informaiton.

Instead, we now have each package record what names were not obfuscated
as part of its cachedOuput file. Much like KnownObjectFiles, the map
records incrementally through the import graph, to avoid having to load
cachedOutput files for indirect dependencies.

We shouldn't need to worry about those maps getting large;
we only skip obfuscating declared names in a few uncommon scenarios,
such as the use of reflection or cgo's "//export".

Since go/types is relatively allocation-heavy, and the export files
contain a lot of data, we get a nice speed-up:

	name      old time/op         new time/op         delta
	Build-16          11.5s ± 2%          11.1s ± 3%  -3.77%  (p=0.008 n=5+5)

	name      old bin-B           new bin-B           delta
	Build-16          5.15M ± 0%          5.15M ± 0%    ~     (all equal)

	name      old cached-time/op  new cached-time/op  delta
	Build-16          375ms ± 3%          341ms ± 6%  -8.96%  (p=0.008 n=5+5)

	name      old sys-time/op     new sys-time/op     delta
	Build-16          283ms ±17%          289ms ±13%    ~     (p=0.841 n=5+5)

	name      old user-time/op    new user-time/op    delta
	Build-16          687ms ± 6%          664ms ± 7%    ~     (p=0.548 n=5+5)

Fixes #456.
Updates #475.
2 years ago
Daniel Martí f497821174 redesign benchmark to be more useful and realistic
First, join the two benchmarks into one.
The previous "cached" benchmark was borderline pointless,
as it built the same package with the existing output binary,
so it would quickly realise it had nothing to do and take ~100ms.

The previous "noncached" benchmark input had no dependencies,
so it was only really benchmarking the non-obfuscation of the runtime.
All in all, neither benchmark measured obfuscating multiple packages.

The new benchmark reuses the "cached" input, but with GOCACHE="*",
meaning that we now obfuscate dozens of standard library packages.

Each iteration first does a built from scratch, the worst case scenario,
and then does an incremental rebuild of just the main package,
which is the closest to a best case scenario without being a no-op.

Since each iteration now performs both kinds of builds,
we include a new "cached-time" metric to report what portion of the
"time" metric corresponds to the incremental build.
Thus, we can see a clean build takes ~11s, and a cached takes ~0.3s:

	name      time/op
	Build-16      11.6s ± 1%

	name      bin-B
	Build-16      5.34M ± 0%

	name      cached-time/op
	Build-16      326ms ± 5%

	name      sys-time/op
	Build-16      184ms ±13%

	name      user-time/op
	Build-16      611ms ± 5%

The benchmark is also no logner parallel; see the docs.

Note that the old benchmark also reported bin-B incorrectly,
as it looked at the binary size of garble itself, not the input program.
2 years ago
Daniel Martí c9341790d4 avoid obfuscating literals set via -ldflags=-X
The -X linker flag sets a string variable to a given value,
which is often used to inject strings such as versions.

The way garble's literal obfuscation works,
we replace string literals with anonymous functions which,
when evaluated, result in the original string.

Both of these features work fine separately,
but when intersecting, they break. For example, given:

	var myVar = "original"
	[...]
	-ldflags=-X=main.myVar=replaced

The -X flag effectively replaces the initial value,
and -literals adds code to be run at init time:

	var myVar = "replaced"
	func init() { myVar = func() string { ... } }

Since the init func runs later, -literals breaks -X.
To avoid that problem,
don't obfuscate literals whose variables are set via -ldflags=-X.

We also leave TODOs about obfuscating those in the future,
but we're also leaving regression tests to ensure we get it right.

Fixes #323.
2 years ago
Daniel Martí 96b15e0ac5 support GOGARBLE=* with -literals again
We recently made an important change when obfuscating the runtime,
so that if it's missing any linkname packages in ListedPackages,
it does an extra "go list" call to obtain their information.

This works very well, but we missed an edge case.
In main.go, we disable flagLiterals for the runtime package,
but not for other packages like sync/atomic.

And, since the runtime's extra "go list" has to compute GarbleActionIDs,
it uses the list of garble flags via appendFlags.
Unfortunately, it thinks "-literals" isn't set, when it is,
and the other packages see it as being set.

This discrepancy results in link time errors,
as each end of the linkname obfuscates with a different hash:

	> garble -literals build
	[stderr]
	# test/main
	jccGkbFG.(*yijmzGHo).String: relocation target jccGkbFG.e_77sflf not defined
	jQg9GEkg.(*NLxfRPAP).pB5p2ZP0: relocation target jQg9GEkg.ce66Fmzl not defined
	jQg9GEkg.(*NLxfRPAP).pB5p2ZP0: relocation target jQg9GEkg.e5kPa1qY not defined
	jQg9GEkg.(*NLxfRPAP).pB5p2ZP0: relocation target jQg9GEkg.aQ_3sL3Q not defined
	jQg9GEkg.(*NLxfRPAP).pB5p2ZP0: relocation target jQg9GEkg.zls3wmws not defined
	jQg9GEkg.(*NLxfRPAP).pB5p2ZP0: relocation target jQg9GEkg.g69WgKIS not defined

To fix the problem, treat flagLiterals as read-only after flag.Parse,
just like we already do with the other flags except flagDebugDir.
The code that turned flagLiterals to false is no longer needed,
as literals.Obfuscate is only called when ToObfuscate is true,
and ToObfuscate is false for runtimeAndDeps already.
3 years ago
Daniel Martí 716f007c2a testdata: make pointer regexp less prone to flakes
Pointers are of the form "0x" and a number of hex characters.
Those are all part of the obfuscated filename alphabet,
so if we're unlucky enough, we can get hits:

	> cmp stdout reverse.stdout
	--- stdout
	+++ reverse.stdout
	@@ -4,7 +4,7 @@
	 runtime/debug.Stack(...)
		runtime/debug/stack.go:24 +0x??
	 test/main/lib.printStackTrace(...)
	-	pv0x??mRa.go:1 +0x??
	+	test/main/lib/long_lib.go:32 +0x??
	 test/main/lib.(*ExportedLibType).ExportedLibMethod(...)
		test/main/lib/long_lib.go:19 +0x??
	 main.unexportedMainFunc.func1(...)

Include the plus sign in the regular expression,
which is used for showing pointers but isn't part of our alphabet.
3 years ago
Daniel Martí c506f02763 handle --long build flags properly
cmd/go treats "--foo=bar" juts like "-foo=bar",
just like any other program using the flag package.

However, we didn't support this longer form in filterForwardBuildFlags.
Because of it, "garble build -tags=foo" worked,
but "garble build --tags=foo" did not,
as we wouldn't forward "--tags=foo" as a build flag for "go list".

Fixes #429.
3 years ago
Daniel Martí d8e5351175 fix test flake in reverse.txt
Just ran into this failure while fixing a different bug:

	> ! grep 'ExportedLib(Type|Field)|unexportedMainFunc|test/main|main\.go|lib\.go' main.stderr
	[main.stderr]
	lib filename: A4Spnz0u.go

	goroutine 1 [running]:
	runtime/debug.Stack(...)
		runtime/debug/stack.go:24 +0x??
	kso0S_A6.at6JKzwa(...)
		p8_ovZPW.go:1 +0x??
	kso0S_A6.(*JKArRn6w).ExportedLibMethod(...)
		h4JncykI.go:1 +0x??
	main.cQXA3D6d.func1(...)
		FaQ2WcAJ.go:1
	main.cQXA3D6d(...)
		T7Ztgy1Q.go:1 +0x??
	main.main(...)
		OHzKYhm3.go:1 +0x??

	main filename: Myi8glib.go

	FAIL: testdata/scripts/reverse.txt:16: unexpected match for `ExportedLib(Type|Field)|unexportedMainFunc|test/main|main\.go|lib\.go` found in main.stderr: lib.go

Note that "main.go" ended up obfuscated as "Myi8glib.go",
which just so happens to match against "lib.go".

Use longer filenames, so that the chances of collisions are near-zero.
3 years ago
Daniel Martí ff803004d0 avoid build ID mismatches when using -debugdir
A recent change added the -debugdir value to addGarbleToHash,
which is part of the hash seed for all obfuscation taking place.

In principle, it was okay to add, just like any other garble flag.
In practice, it broke the added test case:

	> garble -debugdir ./debug1 build
	[stderr]
	# test/main
	FBi9xa6e.(*ac0bCOhR).String: relocation target FBi9xa6e.rV55e6H9 not defined
	qmECK6zf.init.0: relocation target qmECK6zf.eUU08z98 not defined
	[...]

This is because -debugdir gets turned into an absolute path,
and not all garble processes ended up using it consistently.

The fix is rather simple; since -debugdir doesn't affect obfuscation,
don't include it in the build hash seeding at all.

Fixes #451.
3 years ago
Daniel Martí 34cbd1b841 only list missing packages when obfuscating the runtime
We were listing all of std, which certainly worked,
but was quite slow at over 200 packages.
In practice, we can only be missing up to 20-30 packages.
It was a good change as it fixed a severe bug,
but it also introduced a fairly noticeable slow-down.

The numbers are clear; this change shaves off multiple seconds when
obfuscating the runtime with a cold cache:

	name              old time/op       new time/op       delta
	Build/NoCache-16        5.06s ± 1%        1.94s ± 1%  -61.64%  (p=0.008 n=5+5)

	name              old bin-B         new bin-B         delta
	Build/NoCache-16        6.70M ± 0%        6.71M ± 0%   +0.05%  (p=0.008 n=5+5)

	name              old sys-time/op   new sys-time/op   delta
	Build/NoCache-16        13.4s ± 2%         5.0s ± 2%  -62.45%  (p=0.008 n=5+5)

	name              old user-time/op  new user-time/op  delta
	Build/NoCache-16        60.6s ± 1%        19.8s ± 1%  -67.34%  (p=0.008 n=5+5)

Since we only want to call "go list" one extra time,
instead of once for every package we find out we're missing,
we want to know what packages we could be missing in advance.
Resurrect a smarter version of the runtime-related script.

Finally, remove the runtime-related.txt test script,
as it has now been superseeded by the sanity checks in listPackage.
That is, obfuscating the runtime package will now panic if we are
missing any necessary package information.

To double check that we get the runtime's linkname edge case right,
make gogarble.txt use runtime/debug.WriteHeapDump,
which is implemented via a direct runtime linkname.
This ensures we don't lose test coverage from runtime-related.txt.
3 years ago
Daniel Martí ff09a7c672 testdata: ensure go:noinline works via runtime.Callers
While here, I spotted that reverse.txt didn't declare ExportedLibField,
even though the test used a regular expression for it.
3 years ago