Commit Graph

286 Commits (v0.9.2)

Author SHA1 Message Date
Daniel Martí 68b39e8195 fix a link issue when obfuscating complex cgo packages
The added test case, which is obfuscating and linking os/user, would fail
before this fix:

	> garble build
	[stderr]
	# test/main
	/usr/lib/go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
	/usr/bin/ld: $WORK/.tmp/go-link-073246656/go.o: in function `Chz0Yfs2._cgo_cmalloc':
	go.go:(.text+0x993cc): undefined reference to `Chz0Yfs2.runtime_throw'
	/usr/bin/ld: $WORK/.tmp/go-link-073246656/go.o: in function `Chz0Yfs2.tDfhQ8uK':
	go.go:(.text+0x99801): undefined reference to `Chz0Yfs2._cgo_runtime_gostring'
	/usr/bin/ld: go.go:(.text+0x9982a): undefined reference to `Chz0Yfs2._cgo_runtime_gostring'
	/usr/bin/ld: go.go:(.text+0x99853): undefined reference to `Chz0Yfs2._cgo_runtime_gostring'
	collect2: error: ld returned 1 exit status

The reason is that we would alter the linkname directives of cgo-generated
code, but we would not obfuscate the code itself at all.

The generated code would end up being transformed into:

	//go:linkname zh_oKZIy runtime.throw
	func runtime_throw(string)

One can clearly see the error there; handleDirectives obfuscated the
local linkname name, but since transformGo didn't run, the actual Go
declaration was not obfuscated in the same way. Thus, the linker fails
to find a function body for runtime_throw, and fails.

The solution is simple: handleDirectives assumes that it's running on
code being obfuscated, so only run it when transformGo is running.

We can also remove the cgo skip check in handleDirectives, as it never
runs on cgo-generated code now.

Fixes a number of build errors that have been noticed since
907aebd770.
3 years ago
Daniel Martí 64317883c9 handle aliases to foreign named types properly
When such an alias name was used to define an embedded field, we handled
that case gracefully via the code using:

	tf.info.Uses[node].(*types.TypeName)

Unfortunately, when the same field name was used elsewhere, such as a
composite literal, tf.Info.Uses gave us a *types.Var, not a
*types.TypeName, meaning we could no longer tell if this was an alias,
or what it pointed to.

Thus, we failed to obfuscate the name properly in the added test case:

	> garble build
	[stderr]
	# test/main/sub
	xxWZf66u.go:36: unknown field 'foreignAlias' in struct literal of type smhWelwn

It doesn't seem like any of the go/types APIs allows us to obtain the
*types.TypeName directly in this scenario. Thus, use a trick that we
used before: after typechecking, but before obfuscating, record all
embedded struct field *types.Var which are aliases via a map, where the
value holds the *types.TypeName for the alias.

Updates #349.
3 years ago
Daniel Martí e2ddce75a7 support embedding via embed.FS
We already added support for "//go:embed" with string and []byte,
by not obfuscating the "embed" import path.

However, embed.FS was still failing:

	> garble build
	[stderr]
	# test/main
	:13: go:embed cannot apply to var of type embed.WtKNvwbN

The compiler detects the type by matching its name to exactly "embed.FS",
so don't obfuscate the name "FS" either.

While at it, ensure that the embed code behaves the same with "go build".

Updates #349.
3 years ago
Daniel Martí 7fc424ca26 make "garble command -h" give command-specific help
Before, "garble build -h" would print the same as "garble -h", which is
too much and confusing, as it doesn't tell us much about "build".

Now it's far better, and includes the output of "go build -h":

	$ garble build -h
	usage: garble [garble flags] build [arguments]

	This command wraps "go build". Below is its help:

	usage: go build [-o output] [build flags] [packages]
	Run 'go help build' for details.

We do the same for "garble reverse -h", since it doesn't wrap a Go tool
command.
3 years ago
Daniel Martí 750340db5b testdata: deduplicate GODEBUG cleanup line
I added a second one recently without noticing, since the original was
too far below and I had written commands above it.

Deduplicate them, and put the new env line as early as possible, to
prevent further issues and confusion.
3 years ago
Daniel Martí 5fa4acf580 testdata: use longer Go filenames for binsubstr
Every now and then, a CI run would fail:

	FAIL: testdata/scripts/reflect.txt:7: unexpected match for ["main.go"] in main

These were rare, and very hard to reproduce or debug.

My best guess is that, since "main.go" is a short string and we use
random eight-character obfuscated filenames ending with ".go", it was
possible that the random filename happened to end in "main" in some
cases.

Given the base64 encoding, the chances of a single suffix collision are
about 0.000006%. Note, however, that a single obfuscated build will most
likely obfuscate many filenames, especially for the tests obfuscating
multiple packages. For a single CI run with many tests across three OSs,
the chances of any collision are likely very low, but realistic.

All this has a simple fix: use longer filenames to match with. We choose
"garble_main.go" since it's long enough, but also because it's still
clear it's a "main" Go file, and it's very unlikely to cause conflicts
with filenames in upstream Go given the "garble_" prefix.
3 years ago
Daniel Martí 65ff07875b obfuscate alias names like any other objects
Before this change, we would try to never obfuscate alias names. That
was far from ideal, as they can end up in field names via anonymous
fields.

Even then, we would sometimes still fail to build, because we would
inconsistently obfuscate alias names. For example, in the added test
case:

	--- FAIL: TestScripts/syntax (0.23s)
	    testscript.go:397:
	        > env GOPRIVATE='test/main,private.source'
	        > garble build
	        [stderr]
	        # test/main/sub
	        Lv_a8gRD.go:15: undefined: KCvSpxmQ

To fix this problem, we set obj to be the TypeName corresponding to the
alias when it is used as an embedded field. We can then make the right
choice when obfuscating the name.

Right now, all aliases will be obfuscated. A TODO exists about not
obfuscating alias names when they're used as embedded fields in a struct
type in the same package, and that package is used for reflection -
since then, the alias name ends up as the field name.

With these changes, the protobuf module now builds.
3 years ago
Daniel Martí 68f07389b2 fix a number of issues involving types from indirect imports
obfuscatedTypesPackage is used to figure out if a name in a dependency
package was obfuscated or not. For example, if that package used
reflection on a named type, it wasn't obfuscated, so we must have the
same information to not obfuscate the same name downstream.

obfuscatedTypesPackage could return nil if the package was indirectly
imported, though. This can happen if a direct import has a function that
returns an indirect type, or if a direct import exposes a name that's a
type alias to an indirect type.

We sort of dealt with this in two pieces of code by checking for
obfPkg!=nil, but a third one did not have this check and caused a panic
in the added test case:

	--- FAIL: TestScripts/reflect (0.81s)
	    testscript.go:397:
	        > env GOPRIVATE=test/main
	        > garble build
	        [stderr]
	        # test/main
	        panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	        	panic: runtime error: invalid memory address or nil pointer dereference
	        [signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x8a5e39]

More importantly though, the nil check only avoids panics. It doesn't
fix the root cause of the problem: that importcfg does not contain
indirectly imported packages. The added test case would still fail, as
we would obfuscate a type in the main package, but not in the indirectly
imported package where the type is defined.

To fix this, resurrect a bit of code from earlier garble versions, which
uses "go list -toolexec=garble" to fetch a package's export file. This
lets us fill the indirect import gaps in importcfg, working around the
problem entirely.

This solution is still not particularly great, so we add a TODO about
possibly rethinking this in the future. It does add some overhead and
complexity, though thankfully indirect imports should be uncommon.

This fixes a few panics while building the protobuf module.
3 years ago
Daniel Martí 654841e1fb skip reflection detection for sibling packages
Our allPkgs boolean logic was wrong, because it could still lead to
garble obfuscating a type when used in the main package, but not in its
defining package. The added test case shows such a case.

To fix that, use a package path to only record the named objects from
the target package, which is a narrower operation without this problem,
but still makes all our tests pass.

This makes the google.golang.org/protobuf/internal/filetype package
start building.
3 years ago
Daniel Martí 4156f35570 remove tinyfmt implementation from a test script
We've had proper build caching for a while now. No need to avoid std
imports like fmt.
3 years ago
Daniel Martí adb4f44fb2 reverse lone filenames as well
I've wanted this to more easily debug build failures.

To not force a build failure in a test script, as that would require
some trickery to remain stable, we use runtime.Caller without printing
the line number. Before this patch, those filenames without line numbers
would not be reversed at all.
3 years ago
Daniel Martí b6dee63b32 improve handling of reflect on foreign unnamed types
If a package A imports package B, and uses reflect.TypeOf on an unnamed
struct type B.T (such as an alias), we don't want to record B.T's fields
as "do not obfuscate". This is for the same reason that we don't if B.T
is a named struct type: the detection only works for the package
defining the type, as otherwise it's inconsistent.

We failed to handle this case well, because we assumed all struct types
would be under some named type. This is not the case for type aliases.

Fortunately, struct fields are named, and as such they are objects.
Check their package too, just like we do for named types.

Fixes another build error when obfuscating the protobuf module.
We add a simplified version of the example above as a test case,
which originated from debugging the protobuf build failure.
3 years ago
Daniel Martí 8961e0a39a testdata: split reflection test cases into reflect.txt
The detection of reflection usage is tricky and there are plenty of edge
cases to test for. We definitely want one script for it, rather than
splitting those cases between other scripts like imports.txt and
syntax.txt.

Moreover, those two were rather generic and large, so this helps keep a
balance.
3 years ago
Daniel Martí d8de5a4306 avoid reproducibility issues with full rebuilds
We were using temporary filenames for modified Go and assembly files.
For example, an obfuscated "encoding/json/encode.go" would end up as:

	/tmp/garble-shared123/encode.go.456.go

where "123" and "456" are random numbers, usually longer.

This was usually fine for two reasons:

1) We would add "/tmp/garble-shared123/" to -trimpath, so the temporary
   directory and its random number would be invisible.

2) We would add "//line" directives to the source files, replacing
   the filename with obfuscated versions excluding any random number.

Unfortunately, this broke in multiple ways. Most notably, assembly files
do not have any line directives, and it's not clear that there's any
support for them. So the random number in their basename could end up in
the binary, breaking reproducibility.

Another issue is that the -trimpath addition described above was only
done for cmd/compile, not cmd/asm, so assembly filenames included the
randomized temporary directory.

To fix the issues above, the same "encoding/json/encode.go" would now
end up as:

	/tmp/garble-shared123/encoding/json/encode.go

Such a path is still unique even though the "456" random number is gone,
as import paths are unique within a single build.

This fixes issues with the base name of each file, so we no longer rely
on line directives as the only way to remove the second original random
number.

We still rely on -trimpath to get rid of the temporary directory in
filenames. To fix its problem with assembly files, also amend the
-trimpath flag when running the assembler tool.

Finally, add a test that reproducible builds still work when a full
rebuild is done. We choose goprivate.txt for such a test as its
stdimporter package imports a number of std packages, including uses of
assembly and cgo.

For the time being, we don't use such a "full rebuild" reproducibility
test in other test scripts, as this step is expensive, rebuilding many
packages from scratch.

This issue went unnoticed for over a year because such random numbers
"123" and "456" were created when a package was obfuscated, and that
only happened once per package version as long as the build cache was
kept intact.

When clearing the build cache, or forcing a rebuild with -a, one gets
new random numbers, and thus a different binary resulting from the same
build input. That's not something that most users would do regularly,
and our tests did not cover that edge case either, until now.

Fixes #328.
3 years ago
Daniel Martí 58c15aa680 testdata: scope GODEBUG to a single test case
I've tripped over this GODEBUG env var four or five times already.
Since it affects any Go program by making them print tons of runtime
information, it also affects "go env", breaking garble horribly.

To prevent further issues, unset the env var when done.
3 years ago
Daniel Martí c9b0b07853 hash field names equally in all packages
Packages P1 and P2 can define identical struct types T1 and T2, and one
can convert from type T1 to T2 or vice versa.

The spec defines two identical struct types as:

	Two struct types are identical if they have the same sequence of
	fields, and if corresponding fields have the same names, and
	identical types, and identical tags. Non-exported field names
	from different packages are always different.

Unfortunately, garble broke this: since we obfuscated field names
differently depending on the package, cross-package conversions like the
case above would result in typechecking errors.

To fix this, implement Joe Tsai's idea: hash struct field names with the
string representation of the entire struct. This way, identical struct
types will have their field names obfuscated in the same way in all
packages across a build.

Note that we had to refactor "reverse" a bit to start using transformer,
since now it needs to keep track of struct types as well.

This failure was affecting the build of google.golang.org/protobuf,
since it makes regular use of cross-package struct conversions.

Note that the protobuf module still fails to build, but for other
reasons. The package that used to fail now succeeds, so the build gets a
bit further than before. #240 tracks adding relevant third-party Go
modules to CI, so we'll track the other remaining failures there.

Fixes #310.
3 years ago
Andrew LeFevre b3db7d6fa7 fix obfuscating linkname directives that where the package name contained a dot 3 years ago
Andrew LeFevre 65b4692dbc hopefully fix position test on Windows by not matching on 'main.go' 3 years ago
Daniel Martí b4fc735a1e fix windows/arm cross-build linking
Obfuscating some std packages for windows/arm triggered a bug; when
encountering a call to runtime·memmove, we'd hash "memmove" with the
current package's action ID.

This is wrong on two levels: First, we aren't obfuscating the runtime
package yet. And second, if we did, we would have to hash the symbol
appropriately, with that package's action ID.

For now, only hashing the local names does the trick. That's all that
the code currently supports, anyway.
3 years ago
Daniel Martí 6ac7dce4a0 remove obsolete TODO in the tiny.txt test
Since 1a8e32227f we have been setting line numbers on call sites, and
that line number is always the minimum value for -tiny: 1.

Still not as zero-overhead as the old mechanism which entirely deleted
line numbers, but a useless and small line number is still pretty good.
3 years ago
Daniel Martí 0150aa8bb0 support reversing field names
THey don't show up in stack traces, and if they show up in regular
program output, we should in theory not obfuscate those names via the
detection of reflection.

However, there's one relatively common scenario where obfuscated field
names can appear: in build error messages, when obfuscation fails a
build.
3 years ago
Daniel Martí 05d35350cf record types into ignoreObjects more reliably
Our previous logic only took care of fairly simple types, such as a
simple struct or a pointer to a struct. If we had a struct embedding
another struct, we'd fail to record the objects for the fields in the
inner struct, and that would lead to miscompilation:

	> garble build
	[stderr]
	# test/main
	LZmt64Nm.go:7: outer.InnerField undefined (type *CcUt1wkQ.EmbeddingOuter has no field or method InnerField)

To fix this issue, make the function that records all objects under a
types.Type smarter. Since it now does more than just dealing with
structs, it's also renamed.

Since the function now walks types properly, we get to remove the extra
ast.Inspect in recordReflectArgs, which is nice.

We also make it a method, to avoid the map parameter. A boolean
parameter is also added, since we need this feature to only look at the
current package when looking at reflect calls.

Finally, we add a test case, a simplified version of the original bug
report.

Fixes #315.
3 years ago
Daniel Martí 2fad0e1583 wrap types.Importer to canonicalize import paths
The docs for go/importer.ForCompiler say:

	The lookup function is called each time the resulting importer
	needs to resolve an import path. In this mode the importer can
	only be invoked with canonical import paths (not relative or
	absolute ones); it is assumed that the translation to canonical
	import paths is being done by the client of the importer.

We use a lookup func for two reasons: first, to support modules, and
second, to be able to use our information from "go list -json -export".

However, go/types does not canonicalize import paths before calling
ImportFrom. This is somewhat understandable; it doesn't know whether an
importer was created with a lookup func, and ImportFrom only requires
the input path to be canonicalized in that scenario. When the lookup
func is nil, the importer canonicalizes by itself via go/build.Import.

Before this change, the added crossbuild test would fail:

	> garble build net/http
	[stderr]
	# vendor/golang.org/x/crypto/chacha20
	typecheck error: /usr/lib/go/src/vendor/golang.org/x/crypto/chacha20/chacha_generic.go:10:2: could not import crypto/cipher (can't find import: "crypto/cipher")
	# vendor/golang.org/x/text/secure/bidirule
	typecheck error: /usr/lib/go/src/vendor/golang.org/x/text/secure/bidirule/bidirule.go:12:2: could not import errors (can't find import: "errors")
	# vendor/golang.org/x/crypto/cryptobyte
	typecheck error: /usr/lib/go/src/vendor/golang.org/x/crypto/cryptobyte/asn1.go:8:16: could not import encoding/asn1 (can't find import: "encoding/asn1")
	# vendor/golang.org/x/text/unicode/norm
	typecheck error: /usr/lib/go/src/vendor/golang.org/x/text/unicode/norm/composition.go:7:8: could not import unicode/utf8 (can't find import: "unicode/utf8")

This is because we'd fall back to importer.Default, which only knows how
to find packages in $GOROOT/pkg. Those are missing for cross-builds,
unsurprisingly, as those built archives end up in the build cache.

After this change, we properly support importing std-vendored packages,
so we can get rid of the importer.Default workaround. And, by extension,
cross-builds now work as well.

Note that, in the added test script, the full build of the binary fails,
as there seems to be some sort of linker problem:

	> garble build
	[stderr]
	# test/main
	d9rqJyxo.uoqIiDs5: relocation target runtime.os9A16A3 not defined

We leave that as a TODO for now, as this change is subtle enough as it
is.
3 years ago
lu4p 1a9fdb4e8e
Fix calls to linkname functions (#314) 3 years ago
Daniel Martí 3afc993266 use "go env -json" to collect env info all at once
In the worst case scenario, when GOPRIVATE isn't set at all, we would
run these three commands:

* "go env GOPRIVATE", to fetch GOPRIVATE itself
* "go list -m", for GOPRIVATE's fallback
* "go version", to check the version of Go being used

Now that we support Go 1.16 and later, all these three can be obtained
via "go env -json":

	$ go env -json GOPRIVATE GOMOD GOVERSION
	{
		"GOMOD": "/home/mvdan/src/garble/go.mod",
		"GOPRIVATE": "",
		"GOVERSION": "go1.16.3"
	}

Note that we don't get the module path directly, but we can use the
x/mod/modfile Go API to parse it from the GOMOD file cheaply.

Notably, this also simplifies our Go version checking logic, as now we
get just the version string without the "go version" prefix and
"GOOS/GOARCH" suffix we don't care about.

This makes our code a bit more maintainable and robust. When running a
short incremental build, we can also see a small speed-up, as saving two
"go" invocations can save a few milliseconds:

	name           old time/op       new time/op       delta
	Build/Cache-8        168ms ± 0%        166ms ± 1%  -1.26%  (p=0.009 n=6+6)

	name           old bin-B         new bin-B         delta
	Build/Cache-8        6.36M ± 0%        6.36M ± 0%  +0.12%  (p=0.002 n=6+6)

	name           old sys-time/op   new sys-time/op   delta
	Build/Cache-8        222ms ± 2%        219ms ± 3%    ~     (p=0.589 n=6+6)

	name           old user-time/op  new user-time/op  delta
	Build/Cache-8        857ms ± 1%        846ms ± 1%  -1.31%  (p=0.041 n=6+6)
3 years ago
Daniel Martí 24d5ff362c fix a regression involving imported linkname funcs
In ce2c45440a, we simplified the code a bit and removed one call to
obfuscatedTypesPackage.

Unfortunately, we introduced a regression; if an exported function is
linknamed to another symbol name, and it's called from an importer
package, we would have a build failure now:

	> garble build
	[stderr]
	# test/main
	ZiOACuw7.go:1: undefined: ODC0xN52.BaDqbhkj

This is because the imported package would not hash the original name,
via its ignoreObjects logic. And, since the importer package has no
access to that knowledge, it would hash the same name, and fail to find
it in the final build.

The regression happened because we used to have a types.Scope Lookup
that saved us in this scenario. Add the test, and re-add the Lookup,
this time only for this particular scenario with function names.

Thanks to Andrew LeFevre for reporting and describing the test case.

While at it, replace more uses of "garbled" to "obfuscated".
3 years ago
Daniel Martí 5de519694a CI: pin a commit when testing against Go tip
Since it changes rapidly, especially during merge cycles, and we don't
want CI to surprisingly blow up in our faces from one day to another.

Pin this to a commit from yesterday which works, since some changes
merged today moved where the Go build version is recorded and broke
garble.

While at it, replace "git clone" with a wget of a source archive. This
is much, much faster, mainly because a tarball is significantly smaller.
We now download about 20MiB instead of over 350MiB.

One downside is that, without git, make.bash can't construct a devel
version on its own. For that reason, add a pretty basic manual version
via the VERSION file.

This means that we must not reject custom devel version strings. This is
a good thing anyway, because custom devel strings are already common
when building Go in custom ways. Those people tend to be advanced users,
such as CI, so fall back to assuming they know what they are doing and
don't error.

Plus, starting last week, devel versions in Go master now contain the
major Go version like in build tags, such as "go1.17-commit...", so we
will soon start relying on that instead of parsing dates:

	$ go version
	go version devel go1.17-a7e16abb22 Thu Apr 8 07:33:58 2021 +0000 linux/amd64
3 years ago
Daniel Martí 6b1a062c6f make -literals succeed on all of std
Two bugs were remaining which made the build with -literals of std fail.

First, we were ignoring too many objects in constant expressions,
including type names. This resulted in type names declared in
dependencies which were incorrectly not obfuscated in the current
package:

	# go/constant
	O1ku7TCe.go:1: undefined: alzLJ5Fd.Word
	b0ieEGVQ.go:1: undefined: alzLJ5Fd.Word
	LEpgYKdb.go:4: undefined: alzLJ5Fd.Word
	FkhHJCfm.go:1: undefined: alzLJ5Fd.Word

This edge case is easy to reproduce, so a test case is added to
literals.txt.

The second issue is trickier; in some packages like os/user, we would
get syntax errors because of comments printed out of place:

	../tip/os/user/getgrouplist_unix.go:35:130: syntax error: unexpected newline, expecting comma or )

This is a similar kind of error that we tried to fix with e2f06cce94. In
particular, it's fixed by also setting CallExpr.Rparen in withPos. We
also add many other missing Pos fields for good measure, even though
we're not sure they help just yet.

Unfortunately, all my attempts to minimize this into a reproducible
failure have failed. We can't just copy the failing file from os/user,
as it only builds on some OSs. It seems like it was the perfect mix of
cgo (which adds line directive comments) plus unlucky positioning of
literals.

For that last reason, as well as for ensuring that -literals works well
with a wide variety of software, we add a build of all of std with
-literals when not testing with -short. This is akin to what we do in
goprivate.txt, but with the -literals flag. This does make "go test"
more expensive, but also more thorough.

Fixes #285, hopefully for good this time.
3 years ago
Daniel Martí e2f06cce94 set positions when using cursor.Replace
The regular obfuscation process simply modifies some simple nodes, such
as identifiers and strings. In those cases, we modify the nodes
in-place, meaning that their positions remain the same. This hasn't
caused any problems.

Literal obfuscation is trickier. Since we replace one expression with an
entirely different one, we use cursor.Replace. The new expression is
entirely made up on the spot, so it lacks position information.

This was causing problems. For example, in the added test input:

	> garble -literals build
	[stderr]
	# test/main
	dgcm4t6w.go:3: misplaced compiler directive
	dgcm4t6w.go:4: misplaced compiler directive
	dgcm4t6w.go:3: misplaced compiler directive
	dgcm4t6w.go:6: misplaced compiler directive
	dgcm4t6w.go:7: misplaced compiler directive
	dgcm4t6w.go:3: misplaced compiler directive
	dgcm4t6w.go:9: misplaced compiler directive
	dgcm4t6w.go:3: misplaced compiler directive
	dgcm4t6w.go:3: too many errors

The build errors are because we'd move the compiler directives, which
makes the compiler unhappy as they must be directly followed by a
function declaration.

The root cause there seems to be that, since the replacement nodes lack
position information, go/printer would try to estimate its printing
position by adding to the last known position. Since -literals adds
code, this would result in the printer position increasing rapidly, and
potentially printing directive comments earlier than needed.

For now, making the replacement nodes have the same position as the
original node seems to stop go/printer from making this mistake.

It's possible that this workaround won't be bulletproof forever, but it
works well for now, and I don't see a simpler workaround right now.
It would be possible to use fancier mechanisms like go/ast.CommentMap or
dave/dst, but those are a significant amount of added complexity as well.

Fixes #285.
3 years ago
Daniel Martí 7294469a7f testdata: reduce the cost of short tests
Reduces "go test -short" with a warm build cache from ~9s to ~4s. The
main offender was the use of "-a" in the "garble test" call; I think I
added that flag to rebuild all packages when debugging an error, and
forgot to remove it.
3 years ago
Daniel Martí e94b8e3750 make "help" refuse arguments for now
Since they are otherwise silently ignored.
3 years ago
Daniel Martí 1662fc93b4 make reverse error if no changes were made
Just the exit status code is used. Printing an error would be confusing,
since we're printing the input too. Plus, the error is binary anyway -
either we changed something, or we did not.

One could make a case for only printing if we changed anything, and
showing an error otherwise. But that would mean buffering the entire
input, which could be problematic or slow when e.g. reversing large log
files.
3 years ago
Daniel Martí fe095ef132
handle unknown flags in reverse (#290)
While at it, expand the tests for build and test too.
3 years ago
Daniel Martí 1a8e32227f
improve "reverse" even further (#289)
Fix up a few TODOs, and simplify the way we handle comments.

We now add whitespace around inline /*line*/ directives, to ensure we
don't break programs. A test case is added too.

We now add line directives to call sites, not function declarations,
since those are what actually shows up in stack traces.
It's unclear if we care about any other lines inside functions at all.
This also fixes reversing with -literals, since that feature adds a
significant amount of code which shuffles line numbers around.

Finally, we extend the tests with types, methods, and anonymous
functions, and we make all of them work well.

Updates #5.
3 years ago
Daniel Martí 10ec00b37a
make flags like -literals and GOPRIVATE affect hashing (#288)
In 6898d61637, we switched from using action IDs from "go list
-toolexec=garble" to those from the original "go list". We still wanted
the obfuscation and hashing to change if the version of garble changes,
so we hashed that "original action ID" with garble's own content ID, and
called the new hash "garble action ID".

While working on a different patch, I noticed something weird: with the
new mechanism, adding or removing flags like -literals did not alter
those hashes, unlike the old method. This is because the old method used
ownContentID, which includes such bits of information, but the new
method does not.

Change that, and add a test that locks in the behavior we want. In
seed.txt, we check that a single function name gets hashed in particular
ways in different scenarios.

Note that we use a mix of "cmp" and "! bincmp", since the former has no
negated form.

While at it, the seed.txt test is revamped a bit. Now, we only run with
-literals once, as this test is mainly about -seed. We also declare seed
strings once, as environment variables, which makes it easier to track
what each step is doing.
3 years ago
Daniel Martí a8c5d534d1
support reversing stack trace positions (#287)
In particular, the positions within function declarations, including the
positions of call sites to other functions.

Note that this isn't well tested just yet, particularly not with other
features like -literals. We can extend the tests and code over time.
This gets us the core basics.

The issue will be closed once the feature is documented for users, in a
follow-up PR.

Updates #5.
3 years ago
Daniel Martí 091f8239c0 rework the build benchmarks
First, stop writing binaries into the current directory, which pollutes
the git clone.

Second, split the benchmark into two. The old benchmark always used the
build cache after the first iteration, meaning that we weren't really
measuring the cost of cold fresh builds.

The new benchmarks show a build with an always-warm cache, and one
without any cache.

Note that NoCache with the main package importing "fmt" took about 4s
wall time, which makes benchmarking too slow. For that reason, the new
bench-nocache program has no std dependencies other than runtime, which
already pulls in half a dozen dependencies we recompile at every
iteration. This reduces the wall time to 2s, which is bearable.

On the other hand, Cache is already fast, so we add a second and
slightly heavier dependency, net/http. The build still takes under 300ms
of wall time. This also helps the Cache benchmark imitate larger rebuilds
with a warm cache.

Longer term, both benchmarks will be useful, because we want both
scenarios to be as efficient as possible.

	name             time/op
	Build/Cache-8      161ms ± 1%
	Build/NoCache-8    1.21s ± 1%

	name             bin-B
	Build/Cache-8      6.35M ± 0%
	Build/NoCache-8    6.35M ± 0%

	name             sys-time/op
	Build/Cache-8      218ms ± 7%
	Build/NoCache-8    522ms ± 4%

	name             user-time/op
	Build/Cache-8      825ms ± 1%
	Build/NoCache-8    8.17s ± 1%
3 years ago
Daniel Martí ea19e39aa4 use hashWith for obfuscation position information
Position information was obfuscated with math/rand manually, which meant
that the resulting positions were pretty small like "x.go:34", but they
were also very hard to reverse due to their short length and difficulty
to reproduce.

We now hash them with hashWith and the package's GarbleActionID:

	"main.go:203" hashed with 933ad1c700755b7c3a9913c55cade1 to "mwu1xuNz.go"

The input to the hash is the base filename and the byte offset of the
declaration within the file, meaning that it's unique within a package.
The output filename is long enough to allow easy reversal.

The line number is always 1, since the information needed for reversing
is contained entirely within the filename. It doesn't really matter if
we encode data in the filename or line number, but it's easier for us to
use a string.

For #5.
3 years ago
Daniel Martí 748c6a0538
obfuscate asm function names as well (#273)
Historically, it was impossible to rename those funcs as the
implementation was in assembly files, and we only transformed Go code.

Now that transformAsm exists, it's only about fifty lines to do some
very basic parsing and rewriting of assembly files.

This fixes the obfuscated builds of multiple std packages, including a
few dependencies of net/http, since they included assembly funcs which
called pure Go functions. Those pure Go functions had their names
obfuscated, breaking the call sites in assembly.

Fixes #258.
Fixes #261.
3 years ago
lu4p 810bdf448e don't obfuscate the "embed" import path
Updates #263
3 years ago
Daniel Martí def9351b25
fix and re-enable "garble test" (#268)
With the many refactors building up to v0.1.0, we broke "garble test" as
we no longer dealt with test packages well.

Luckily, now that we can depend on TOOLEXEC_IMPORTPATH, we can support
the test command again, as we can always figure out what package we're
currently compiling, without having to track a "main" package.

Note that one major pitfall there is test packages, where
TOOLEXEC_IMPORTPATH does not agree with ImportPath from "go list -json".
However, we can still work around that with a bit of glue code, which is
also copiously documented.

The second change necessary is to consider test packages private
depending on whether their non-test package is private or not. This can
be done via the ForTest field in "go list -json".

The third change is to obfuscate "_testmain.go" files, which are the
code-generated main functions which actually run tests. We used to not
need to obfuscate them, since test function names are never obfuscated
and we used to not obfuscate import paths at compilation time. Now we do
rewrite import paths, so we must do that for "_testmain.go" too.

The fourth change is to re-enable test.txt, and expand it with more
sanity checks and edge cases.

Finally, document "garble test" again.

Fixes #241.
3 years ago
Daniel Martí b71ff42877
testdata: remove some unnecessary execs (#267)
Our use of go-internal's gotooltest.Setup means that "go" is set up as a
top-level command in the scripts, so we can simply "go build" instead of
having to "exec go build". The result is practically the same, but the
scripts are simpler.

While at it, I had left an "exec cat" behind; remove it.
3 years ago
Daniel Martí 4e9ee17ec8
refactor "current package" with TOOLEXEC_IMPORTPATH (#266)
Now that we've dropped support for Go 1.15.x, we can finally rely on
this environment variable for toolexec calls, present in Go 1.16.

Before, we had hacky ways of trying to figure out the current package's
import path, mostly from the -p flag. The biggest rough edge there was
that, for main packages, that was simply the package name, and not its
full import path.

To work around that, we had a restriction on a single main package, so
we could work around that issue. That restriction is now gone.

The new code is simpler, especially because we can set curPkg in a
single place for all toolexec transform funcs.

Since we can always rely on curPkg not being nil now, we can also start
reusing listedPackage.Private and avoid the majority of repeated calls
to isPrivate. The function is cheap, but still not free.

isPrivate itself can also get simpler. We no longer have to worry about
the "main" edge case. Plus, the sanity check for invalid package paths
is now unnecessary; we only got malformed paths from goobj2, and we now
require exact matches with the ImportPath field from "go list -json".

Another effect of clearing up the "main" edge case is that -debugdir now
uses the right directory for main packages. We also start using
consistent debugdir paths in the tests, for the sake of being easier to
read and maintain.

Finally, note that commandReverse did not need the extra call to "go
list -toolexec", as the "shared" call stored in the cache is enough. We
still call toolexecCmd to get said cache, which should probably be
simplified in a future PR.

While at it, replace the use of the "-std" compiler flag with the
Standard field from "go list -json".
3 years ago
Daniel Martí ff0bea73b5
all: drop support for Go 1.15.x (#265)
This mainly cleans up the few bits of code where we explicitly kept
support for Go 1.15.x. With v0.1.0 released, we can drop support now,
since the next v0.2.0 release will only support Go 1.16.x.

Also updates all modules, including test ones, to 'go 1.16'.

Note that the TOOLEXEC_IMPORTPATH refactor is not done here, despite all
the TODOs about doing so when we drop 1.15 support. This is because that
refactor needs to be done carefully and might have side effects, so it's
best to keep it to a separate commit.

Finally, update the deps.
3 years ago
Daniel Martí 1267e2eced
fix link errors when importing crypto/ecdsa (#262)
First, we had some link errors such as:

	cannot find package J6OzO8GN (using -importcfg)

This was caused by the code that writes an updated importcfg, which did
not handle import maps well. That code is now fixed, and we also add an
obfuscatedImportPath method for clarity.

Once fixed, we ran into other link errors:

	Pw3g97ww.addVW: relocation target Pw3g97ww.addVWlarge not defined

After some digging, the cause of those is assembly code that we do not
yet support obfuscating. #261 tracks that.

Meanwhile, to fix "GOPRIVATE=* garble build" and to be able to have a
test for the original import path bug, we add the packages which use
that form of assembly code to runtimeRelated - math/big and
crypto/sha512. There might be more, but these were the ones found by
trying to link crypto/tls, a fairly common dependency.

Fixes #256.
3 years ago
Daniel Martí 99887c13c2 ignore -ldflags=-X flags mentioning unknown packages
That would panic, since the *listedPackage would be nil for a package
path we aren't aware of:

	panic: runtime error: invalid memory address or nil pointer dereference
	[signal SIGSEGV: segmentation violation code=0x1 addr=0x88 pc=0x126b57d]

	goroutine 1 [running]:
	main.transformLink.func1(0x7ffeefbff28b, 0x5d)
		mvdan.cc/garble@v0.0.0-20210302140807-b03cd08c0946/main.go:1260 +0x17d
	main.flagValueIter(0xc0000a8e20, 0x2f, 0x2f, 0x12e278e, 0x2, 0xc000129e28)
		mvdan.cc/garble@v0.0.0-20210302140807-b03cd08c0946/main.go:1410 +0x1e9
	main.transformLink(0xc0000a8e20, 0x30, 0x36, 0x4, 0xc000114648, 0x23, 0x12dfd60, 0x0)
		mvdan.cc/garble@v0.0.0-20210302140807-b03cd08c0946/main.go:1241 +0x1b9
	main.mainErr(0xc0000a8e10, 0x31, 0x37, 0x37, 0x0)
		mvdan.cc/garble@v0.0.0-20210302140807-b03cd08c0946/main.go:287 +0x389
	main.main1(0xc000096058)
		mvdan.cc/garble@v0.0.0-20210302140807-b03cd08c0946/main.go:150 +0xe7
	main.main()
		mvdan.cc/garble@v0.0.0-20210302140807-b03cd08c0946/main.go:83 +0x25

The linker ignores such unknown references, so we should too.

Fixes #259.
3 years ago
lu4p a397a8e94e Literals: Skip constants with inferred values.
Obfuscating literals broke constants
with values inferred via iota before,
because it would be moved to a variable declaration instead.
3 years ago
Daniel Martí c0c5a75454
testdata: fix flakiness of tiny.txt on Windows (#252)
Sometimes it prints relatively low addresses, like:

	(0xbc200,0xdd658)

We required 6 to 8 hex digits, and sometimes it printed 5. Make the
regular expression more conservative.

Fixes #184.
3 years ago
Daniel Martí 09e244986e formally add support for Go 1.16
This was pretty much just fixing the README and closing the issue. The
only other noteworthy user-facing change is that, if the Go version is
detected to be too old, we now suggest 1.16.x instead of 1.15.x.

While at it, refactor goversion.txt a bit. I wanted it to print a
clearer "mocking the go build" error if another command was used like
"go build", but I didn't want to learn BAT. So, instead use a simple Go
program and build it, which will work on all platforms. The added
"go build" step barely takes 100ms on my machine, given how simple the
program is.

The [short] line also doesn't seem necessary to me. The entire script
runs in under 200ms for me, so it's well within the realm of "short", at
least compared to many of the other test scripts.

Fixes #124.
3 years ago
Andrew LeFevre 081cba38d7 strip a few more unneeded runtime functions
Strip a few more traceback printing related functions in the runtime, new for Go 1.16. Also test that GODEBUG=inittrace=1 does not print anything when -tiny is passed.
3 years ago
Daniel Martí 2ee6604408
replace the -p path when assembling (#247)
The asm tool runs twice for a package with assembly. The second time it
does, the path given to the -p flag matters, just like in the compiler,
as we generate an object file.

We don't have a -buildid flag in the asm tool, so obtaining the action
ID to obfuscate the package path with is a bit tricky. We store it from
transformCompile, and read it from transformAsm. See the detailed docs
for more.

This was the last "skip" line in the tests due to Go 1.16. After all PRs
are merged, one last PR documenting that 1.16 is supported will be sent,
closing the issue for good.

It's unclear why this wasn't an issue in Go 1.15. My best guess is that
the ABI changes only happened in Go 1.16, and this causes exported asm
funcs to start showing up in object files with their package paths.

Updates #124.
3 years ago
Daniel Martí 5e3ba2fc09
update the list of runtime-related packages for 1.16 (#246)
With a few extra lines, we can keep Go 1.15 support in the table too.

Re-enables the goprivate.txt test for Go 1.16.

While at it, make the script's use of grep a bit simpler with -E, which
also uses the same syntax as Go's regexp. Its skip logic was also buggy,
resulting in the macos results always being empty.

Updates #124.
3 years ago
Daniel Martí 89dbdb69a1
start working on Go 1.16 support (#244)
There are three minor bugs breaking Go 1.16 with the current version of
garble, after the import path obfuscation refactor:

1) Stripping the runtime results in an unused import error. This PR
   fixes that.

2) The asm.txt test seems to be broken; something to do with the export
   data not being right for the exported assembly func.

3) The obfuscated build of std fails, since our runtimeRelated table was
   generated for Go 1.15, not 1.16.

This PR fixes the first issue, adds conditional skip lines for 1.16 for
the other two issues, and enables 1.16 on CI.

Note that 1.16 support is not here just yet, because of the other two
issues. As such, no doc changes.

Updates #124.
3 years ago
Daniel Martí 180d64236a
properly fix obfuscated imports with their package names (#245)
The TODO I left there didn't take long to surface as a bug. If the
package path ends with a word containing a hyphen, that's not a valid
identifier, so we end up with invalid Go syntax.

Add that test case, as well as one where an import was already named.

To fix the issue, we just need to use the package name we got from
'go list -json'.

Fixes #243.
3 years ago
Daniel Martí 05d0dd1801
reimplement import path obfuscation without goobj2 (#242)
We used to rely on a parallel implementation of an object file parser
and writer to be able to obfuscate import paths. After compiling each
package, we would parse the object file, replace the import paths, and
write the updated object file in-place.

That worked well, in most cases. Unfortunately, it had some flaws:

* Complexity. Even when most of the code is maintained in a separate
  module, the import_obfuscation.go file was still close to a thousand
  lines of code.

* Go compatibility. The object file format changes between Go releases,
  so we were supporting Go 1.15, but not 1.16. Fixing the object file
  package to work with 1.16 would probably break 1.15 support.

* Bugs. For example, we recently had to add a workaround for #224, since
  import paths containing dots after the domain would end up escaped.
  Another example is #190, which seems to be caused by the object file
  parser or writer corrupting the compiled code and causing segfaults in
  some rare edge cases.

Instead, let's drop that method entirely, and force the compiler and
linker to do the work for us. The steps necessary when compiling a
package to obfuscate are:

1) Replace its "package foo" lines with the obfuscated package path. No
   need to separate the package path and name, since the obfuscated path
   does not contain slashes.

2) Replace the "-p pkg/foo" flag with the obfuscated path.

3) Replace the "import" spec lines with the obfuscated package paths,
   for those dependencies which were obfuscated.

4) Replace the "-importcfg [...]" file with a version that uses the
   obfuscated paths instead.

The linker also needs that last step, since it also uses an importcfg
file to find object files.

There are three noteworthy drawbacks to this new method:

1) Since we no longer write object files, we can't use them to store
   data to be cached. As such, the -debugdir flag goes back to using the
   "-a" build flag to always rebuild all packages. On the plus side,
   that caching didn't work very well; see #176.

2) The package name "main" remains in all declarations under it, not
   just "func main", since we can only rename entire packages. This
   seems fine, as it gives little information to the end user.

3) The -tiny mode no longer sets all lines to 0, since it did that by
   modifying object files. As a temporary measure, we instead set all
   top-level declarations to be on line 1. A TODO is added to hopefully
   improve this again in the near future.

The upside is that we get rid of all the issues mentioned before. Plus,
garble now nearly works with Go 1.16, with the exception of two very
minor bugs that look fixable. A follow-up PR will take care of that and
start testing on 1.16.

Fixes #176.
Fixes #190.
3 years ago
Daniel Martí e2a32634a6
simplify, improve, and test line obfuscation (#239)
First, remove the shuffling of the declarations list within each file.
This is what we used at the very start to shuffle positions. Ever since
we started obfuscating positions via //line comments, that has been
entirely unnecessary.

Second, add a proper test that will fail if we don't obfuscate line
numbers well enough. Filenames were already decently covered by other
tests.

Third, simplify the line obfuscation code. It does not require
astutil.Apply, and ranging over file.Decls is easier.

Finally, also obfuscate the position of top-level vars, since we only
used to do it for top-level funcs. Without that fix, the test would fail
as varLines was unexpectedly sorted.
3 years ago
Daniel Martí 63c42c3cc7
support typechecking all of std (#236)
There was one bug keeping the command below from working:

	GOPRIVATE='*' garble build std

The bug is rather obscure; I'm still working on a minimal reproducer
that I can submit upstream, and I'm not yet convinced about where the
bug lives and how it can be fixed.

In short, the command would fail with:

	typecheck error: /go/src/crypto/ecdsa/ecdsa.go:122:12: cannot use asn1.SEQUENCE (constant 48 of type asn1.Tag) as asn1.Tag value in argument to b.AddASN1

Note that the error is ambiguous; there are two asn1 packages, but they
are actually mismatching. We can see that by manually adding debug
prints to go/types:

	constant: asn1.SEQUENCE (constant 48 of type golang.org/x/crypto/cryptobyte/asn1.Tag)
	argument type: vendor/golang.org/x/crypto/cryptobyte/asn1.Tag

It's clear that, for some reason, go/types ends up confused and loading
a vendored and non-vendored version of asn1. There also seems to be no
way to work around this with our lookup function, as it just receives an
import path as a parameter, and returns an object file reader.

For now, work around the issue by *not* using a custom lookup function
in this rare edge case involving vendored dependencies in std packages.
The added code has a lengthy comment explaining the reasoning.

I still intend to investigate this further, but there's no reason to
keep garble failing if we can work around the bug.

Fixes #223.
3 years ago
Daniel Martí 2fa5697189
avoid panic on funcs that almost look like tests (#235)
The added test case used to crash garble:

	panic: runtime error: invalid memory address or nil pointer dereference [recovered]
		panic: runtime error: invalid memory address or nil pointer dereference
	[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x8fe71b]

	goroutine 1 [running]:
	golang.org/x/tools/go/ast/astutil.Apply.func1(0xc0001e8880, 0xc000221570)
		/go/pkg/mod/golang.org/x/tools@v0.0.0-20210115202250-e0d201561e39/go/ast/astutil/rewrite.go:47 +0x97
	panic(0x975bc0, 0xd6c610)
		/sdk/go1.15.8/src/runtime/panic.go:969 +0x1b9
	go/types.(*Named).Obj(...)
		/sdk/go1.15.8/src/go/types/type.go:473
	mvdan.cc/garble.isTestSignature(0xc0001e7080, 0xa02e84)
		/src/garble/main.go:1170 +0x7b
	mvdan.cc/garble.(*transformer).transformGo.func2(0xc000122df0, 0xaac301)
		/src/garble/main.go:1028 +0xff1

We were assuming that the first parameter was a named type, but that
might not be the case.

This crash was found out in the wild, from which a minimal repro was
written. We add two variants of it to the test data, just in case.
3 years ago
Daniel Martí e33179d480
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.
3 years ago
Daniel Martí a499a6bcd7
testdata: simplify goversion test (#232)
As per the TODO, just set up the files in the right places.
3 years ago
Daniel Martí af517a20f8 make the handling of import paths more robust
First, make isPrivate panic on malformed import paths, since that should
never happen. This catches the errors that some users had run into with
packages like gopkg.in/yaml.v2 and github.com/satori/go.uuid:

	panic: malformed import path "gopkg.in/garbletest%2ev2": invalid char '%'

This seems to trigger when a module path contains a dot after the first
element, *and* that module is fetched via the proxy. This results in the
toolchain URL-encoding the second dot, and garble ends up seeing that
encoded path.

We reproduce this behavior with a fake gopkg.in module added to the test
module proxy. Using yaml.v2 directly would have been easier, but it's
pretty large. Note that we tried a replace directive, but that does not
trigger the URL-encoding bug.

Also note that we do not obfuscate the gopkg.in package; that's fine, as
the isPrivate path validity check catches the bug either way.

For now, make initImport use url.PathUnescape to work around this issue.
The underlying bug is likely in either the goobj2 fork, or in the
upstream Go toolchain itself.

hashImport also gives a better error if it cannot find a package now,
rather than just an "empty seed" panic.

Finally, the sanity check in isPrivate unearthed the fact that we do not
support garbling test packages at all, since they were invalid paths
which never matched GOPRIVATE. Add an explicit check and TODO about
that.

Fixes #224.
Fixes #228.
3 years ago
Daniel Martí 79c775e218
obfuscate unexported names like exported ones (#227)
In 90fa325da7, the obfuscation logic was changed to use hashes for
exported names, but incremental names starting at just one letter for
unexported names. Presumably, this was done for the sake of binary size.

I argue that this is not a good idea for the default mode for a number
of reasons:

1) It makes reversing of stack traces nearly impossible for unexported
   names, since replacing an obfuscated name "c" with "originalName"
   would trigger too many false positives by matching single characters.

2) Exported and unexported names aren't different. We need to know how
   names were obfuscated at a later time in both cases, thanks to use
   cases like -ldflags=-X. Using short names for one but not the other
   doesn't make a lot of sense, and makes the logic inconsistent.

3) Shaving off three bytes for unexported names doesn't seem like a huge
   deal for the default mode, when we already have -tiny to optimize for
   size.

This saves us a bit of work, but most importantly, simplifies the
obfuscation state as we no longer need to carry privateNameMap between
the compile and link stages.

	name     old time/op       new time/op       delta
	Build-8        153ms ± 2%        150ms ± 2%    ~     (p=0.065 n=6+6)

	name     old bin-B         new bin-B         delta
	Build-8        7.09M ± 0%        7.08M ± 0%  -0.24%  (p=0.002 n=6+6)

	name     old sys-time/op   new sys-time/op   delta
	Build-8        296ms ± 5%        277ms ± 6%  -6.50%  (p=0.026 n=6+6)

	name     old user-time/op  new user-time/op  delta
	Build-8        562ms ± 1%        558ms ± 3%    ~     (p=0.329 n=5+6)

Note that I do not oppose using short names for both exported and
unexported names in the future for -tiny, since reversing of stack
traces will by design not work there. The code can be resurrected from
the git history if we want to improve -tiny that way in the future, as
we'd need to store state in header files again.

Another major cleanup we can do here is to no longer use the
garbledImports map. From a look at obfuscateImports, we hash a package's
import path with its action ID, much like exported names, so we can
simply re-do that hashing for the linker's -X flag.

garbledImports does have some logic to handle duplicate package names,
but it's worth noting that should not affect package paths, as they are
always unique. That area of code could probably do with some
simplification in the future, too.

While at it, make hashWith panic if either parameter is empty.
obfuscateImports was hashing the main package path without a salt due to
a bug, so we want to catch those in the future.

Finally, make some tiny spacing and typo tweaks to the README.
3 years ago
Daniel Martí d8e8738216
initial support for reversing panic output (#225)
For now, this only implements reversing of exported names which are
hashed with action IDs. Many other kinds of obfuscation, like positions
and private names, are not yet implemented.

Note that we don't document this new command yet on purpose, since it's
not finished.

Some other minor cleanups were done for future changes, such as making
transformLineInfo into a method that also receives the original
filename, and making header names more self-describing.

Updates #5.
3 years ago
Daniel Martí 39d60f91e6
include a "garble version" test (#221)
Not sure why the last commit came with none.

While at it, I noticed that the version command would ignore arguments.
Error instead.
3 years ago
Daniel Martí 1db6e1e230
make -coverprofile include toolexec processes (#216)
testscript already included magic to also account for commands in the
total code coverage. That does not happen with plain tests, since those
only include coverage from the main test process.

The main problem was that, before, indirectly executed commands did not
properly save their coverage profile anywhere for testscript to collect
it at the end. In other words, we only collected coverage from direct
garble executions like "garble -help", but not indirect ones like "go
build -toolexec=garble".

	$ go test -coverprofile=cover.out
	PASS
	coverage: 3.6% of statements
	total coverage: 16.6% of statements
	ok  	mvdan.cc/garble	6.453s

After the delicate changes to testscript, any direct or indirect
executions of commands all go through $PATH and properly count towards
the total coverage:

	$ go test -coverprofile=cover.out
	PASS
	coverage: 3.6% of statements
	total coverage: 90.5% of statements
	ok  	mvdan.cc/garble	33.258s

Note that we can also get rid of our code to set up $PATH, since
testscript now does it for us.

goversion.txt needed minor tweaks, since we no longer set up $WORK/.bin.

Finally, note that we disable the reuse of $GOCACHE when collecting
coverage information. This is to do "full builds", as otherwise the
cached package builds would result in lower coverage.

Fixes #35.
3 years ago
Andrew LeFevre 2d720ae155
fixed some bugs related to additional linkname corner cases (#210) 3 years ago
Daniel Martí 835f4aadf3
testdata: add test case for init funcs in many files (#208)
We tested that init funcs within a single file retain their order, but
not when they are split between many files.

Add one such case, with ten files and the same global-var-append
mechanism.

While at it, add some docs and make each init func take just one line.
4 years ago
Daniel Martí ba19a1d49c
do not try to obfuscate huge literals (#204)
It's common for asset bundling code generators to produce huge literals,
for example in strings. Our literal obfuscators are meant for relatively
small string-like literals that a human would write, such as URLs, file
paths, and English text.

I ran some quick experiments, and it seems like "garble build -literals"
appears to hang trying to obfuscate literals starting at 5-20KiB. It's
not really hung; it's just doing a lot of busy work obfuscating those
literals. The code it produces is also far from ideal, so it also takes
some time to finally compile.

The generated code also led to crashes. For example, using "garble build
-literals -tiny" on a package containing literals of over a megabyte,
our use of asthelper to remove comments and shuffle line numbers could
run out of stack memory.

This all points in one direction: we never designed "-literals" to deal
with large sizes. Set a source-code-size limit of 2KiB.

We alter the literals.txt test as well, to include a few 128KiB string
literals. Before this fix, "go test" would seemingly hang on that test
for over a minute (I did not wait any longer). With the fix, those large
literals are not obfuscated, so the test ends in its usual 1-3s.

As said in the const comment, I don't believe any of this is a big
problem. Come Go 1.16, most developers should stop using asset-bundling
code generators and use go:embed instead. If we wanted to somehow
obfuscate those, it would be an entirely separate feature.

And, if someone wants to work on obfuscating truly large literals for
any reason, we need good tests and benchmarks to ensure garble does not
consume CPU for minutes or run out of memory.

I also simplified the generate-literals test command. The only argument
that matters to the script is the filename, since it's used later on.

Fixes #178.
4 years ago
Daniel Martí 249501b5e9
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
Daniel Martí c0731921c2
rewrite go:linkname directives with garbled names (#200)
If code includes a linkname directive pointing at a name in an imported
package, like:

	//go:linkname localName importedpackage.RemoteName
	func localName()

We should rewrite the comment to replace "RemoteName" with its
obfuscated counterpart, if the package in question was obfuscated and
that name was as well.

We already had some code to handle linkname directives, but only to
ensure that "localName" was never obfuscated. This behavior is kept, to
ensure that the directive applies to the right name. In the future, we
could instead rewrite "localName" in the directive, like we do with
"RemoteName".

Add plenty of tests, too. The linkname directive used to be tested in
imports.txt and syntax.txt, but that was hard to maintain as each file
tested different edge cases.

Now that we have build caching, adding one extra testscript file isn't a
big problem anymoree. Add linkname.txt, which is self-explanatory. The
other two scripts also get a bit less complex.

Fixes #197.
4 years ago
Daniel Martí 4f65e6f99c
testdata: avoid messing with the host's mod cache (#198)
It turns out that the modules we include in testdata/mod via
txtar-addmod don't result in the same h1 hash that one gets when using
proxy.golang.org.

As proof:

	$ go clean -modcache && go get -d rsc.io/quote@v1.5.2 && go test -short
	[...]
	--- FAIL: TestScripts/imports (0.06s)
		> garble build -tags buildtag
		go list error: exit status 1: verifying rsc.io/quote@v1.5.2: checksum mismatch
		    downloaded: h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
		    go.sum:     h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0=

The added comment explains the situation in detail.

For now, simply work around the issue by not sharing GOMODCACHE with the
host.
4 years ago
Daniel Martí c9deff810b
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.
4 years ago
lu4p cf290b8e6d
Share data between processes via a shared file. (#192)
Previously garble heavily used env vars to share data between processes.
This also makes it easy to share complex data between processes.

The complexity of main.go is considerably reduced.
4 years ago
Andrew LeFevre 8c03afee95
Use latest Binject/debug version to support importmap directives, fixes #146 (#189)
* Use latest Binject/debug version to support importmap directives in the importcfg file

* Uncomment line in goprivate testscript to test ImportMap

* Fixed issue where a package in specified in importmap would be hashed differently in a package that imported it, due to the mapping of import paths.

Also commented out the 'net' import in the goprivate testscript (again) due to cgo compile errors
4 years ago
Daniel Martí 3e7416ee9e
add test case for ImportMap support (#186)
We also update the "original types importer" to support ImportMap.

The test now gets further along, no longer getting stuck on "path not
found in listed packages". Instead, we get stuck on:

	error parsing importcfg: <...>/importcfg:2: unknown directive "importmap"

This bug has been filed at https://github.com/Binject/debug/issues/17.
Until it's fixed, we can't really proceed on #146, so the net import in
the test file (which triggers this case) is commented out for now.

Updates #146.
4 years ago
Daniel Martí 39372a8c9b testdata: don't let tests rely on rewriting mod files
In Go 1.15, if a dependency is required but not listed in go.mod/go.sum,
it's resolved and added automatically.

This is changing in 1.16. From that release, one will have to explicitly
update the mod files via 'go mod tidy' or 'go get'.

To get ahead of the curve, start using -mod=readonly to get the same
behavior in 1.15, and fix all existing tests.

The only tests that failed were imports.txt and syntax.txt, the only
ones to require other modules. But since we're here, let's add the 'go'
line to all go.mod files as well.
4 years ago
Daniel Martí 4e79bfc01a
warn when a public package imports a private package (#182)
That is, a package that is built without obfuscation imports an
obfuscated package. This will result in confusing compilation error
messages, because the importer can't find the exported names from the
imported package by their non-obfuscated names:

	> ! garble build ./importer
	[stderr]
	# test/main/importer
	importer/importer.go:5:9: undefined: imported.Name
	exit status 2

Instead, detect this bad input case and provide a nice error:

	public package "test/main/importer" can't depend on obfuscated package "test/main/imported" (matched via GOPRIVATE="test/main/imported")

For now, this is by design. It also makes little sense for a public
package to import an obfuscated package in general, because the public
package would have to leak details about the private package's API and
behavior.

While at it, fix a quirk where we thought the unsafe package could be
private. It can't be, because the runtime package is always public and
it imports the runtime package:

	public package "internal/bytealg" can't depend on obfuscated package "unsafe" (matched via GOPRIVATE="*")

Instead of trying to obfuscate "unsafe" and doing nothing, simply add it
to the neverPrivate list, which is also a better name than
"privateBlacklist" (for #169).

Fixes #164.

Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
Andrew LeFevre bca460e36e
Print interfaces/pointers when -tiny is used (#170)
fixes #167
4 years ago
Daniel Martí 7ad246c6fe
testdata: make tiny.txt simpler and faster (#168)
By avoiding the fmt import, we save some work: 'go test -run Script/tiny'
goes down from 0.8s to 0.5s even with a warm build cache.

Since the output changes between runs, we use stderr grep lines instead
of cmp. This way we can also check that the "oh noes" panic is entirely
hidden in the tiny mode.
4 years ago
Daniel Martí 95501f0fcd
testdata: avoid unnecessary deps in init.txt (#166)
We can use println instead of fmt.Println. Similarly, we can avoid
strings.Join by just appending bytes to a []byte.

This is less important now that we have build caching, but it still
helps to do less work overall and link smaller binaries.

Reduces 'go test -run Script/init' from 0.5s to 0.3s on my laptop.

Also, properly format the Go in that file, since the space indentation
wasn't noticed during code review. We might want to enforce gofmt in Go
files within txtar files if this keeps happening.
4 years ago
Andrew LeFevre 6cf1eb6d49
keep init funcs in original order (#165) 4 years ago
Andrew LeFevre 1fc990dcf8
Fix bug where structs would get garbled in some packages but not in others (#161)
* fix bug where structs would get garbled in some packages but not in others

* only check if struct/field was not defined in current package

* fix a related bug when two objects share the same name in the same package and one is garbled but the other one is not

* renamed parameter for clarity
4 years ago
Andrew LeFevre 047aa254e2
properly remove all filenames when -tiny is passed (#160)
* properly remove all filenames when -tiny is passed

* document filename symbol removal
4 years ago
Andrew LeFevre 0e0a9fc594
Allow struct fields to be garbled, fixes #48 (#159)
* Allow struct fields to be garbled, fixes #48

* fix syntax test script

* simplified code according to review
4 years ago
pagran 803c1d9439
Store obfuscated sources in object files (#158)
Now the flag "-debugdir" does not trigger a full recompilation.
Obfuscated source files are saved to object files and are extracted during linking.
4 years ago
Daniel Martí b823b07443 testdata: avoid 'go build' with -short in literals.txt
Use a static main.stderr file, like in the other tests. This means we
don't need to always start the test with a 'go build', and the output is
also obvious by just reading the txtar file.

We can also move generate-literals to a later stage, so that 'go test
-short' needs to do even less work.

'go test -short -run Script/literals' drops from ~0.4s to ~0.2s on my
laptop.

Finally, make the printing of byte lists not use trailing spaces, so
that the txtar file itself doesn't have trailing whitespace in its lines
either.

Fixes #103.
4 years ago
Daniel Martí 1e19d136c7 testdata: make syntax.txt pass when offline
The test intended to use an extra module to be obfuscated, rsc.io/quote,
which we were bundling in the local proxy as well. Unfortunately, the
use of GOPRIVATE also meant that we did not actually fetch the module
from the proxy, and we would instead do a full roundtrip to the internet
to "git clone" the actual upstream repository.

To prevent that roundtrip, instead use a locally replaced module. This
fits the syntax.txt test too, since it's one more edge case that we want
to make sure works well with garble. Since rsc.io/quote is used in
another test, simply make up our own tiny module.

Reduces a 'go test -run Syntax/syntax' run with warm cache from ~5s to
~0.5s, thanks to removing the multiple roundtrips. A warm 'go test' run
still sits at ~6s, since we still need that much CPU time in total.

While at it, fix a staticcheck warning and fix inconsistent indentation
in a couple of tests.
4 years ago
Daniel Martí 2a0ac434fb
initial support for build caching (#142)
As per the discussion in https://github.com/golang/go/issues/41145, it
turns out that we don't need special support for build caching in
-toolexec. We can simply modify the behavior of "[...]/compile -V=full"
and "[...]/link -V=full" so that they include garble's own version and
options in the printed build ID.

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
output as "buildID=...", we simply add "+garble buildID=_/_/_/${hash}".
The slashes let us imitate a full binary build ID, but we assume that
the other components such as the action ID are not necessary, since the
only reader here is cmd/go and it only consumes the content ID.

The reported content ID includes the tool's original content ID,
garble's own content ID from the built binary, and the garble options
which modify how we obfuscate code. If any of the three changes, we
should use a different build cache key. GOPRIVATE also affects caching,
since a different GOPRIVATE value means that we might have to garble a
different set of packages.

Include tests, which mainly check that 'garble build -v' prints package
lines when we expect to always need to rebuild packages, and that it
prints nothing when we should be reusing the build cache even when the
built binary is missing.

After this change, 'go test' on Go 1.15.2 stabilizes at about 8s on my
machine, whereas it used to be at around 25s before.
4 years ago
pagran ea4a01df87
More correct comments transformation (#152)
More correct comments transformation was implemented.

Added processing of //go:linkname localname [importpath.name] directive, now localname is not renamed. This is safe and does not cause a name disclosure because the functions marked //linkname do not have a name in the resulting binary.

Added cgo directives support

Fixed filename leak protection for cgo

Part of #149
4 years ago
pagran 991fbb042b
avoid potential short name collisions
Fix for bug when a conflict occurred between generated short names
and local variables/functions/types/structs.

The already existing names are collected and if the generated short name
already exists, the package counter is increased until a free name is found.

Part of #149.
4 years ago
pagran 434df0476d
fixed comments cleaning
Added cleanup of the Comment field.
In some cases, the appearance of a comment in a random place
may break the compilation (e.g. cgo and runtime package).

This is safe because the Comment field cannot contain any directives.

Part of #149.
4 years ago
Daniel Martí c3bee46a26 testdata: use the debugdir flag less often
In tiny.txt, we already check line numbers via stderr, so there's no
need to do that via -debugdir.

In syntax.txt, we only really care about what names remain in the
binary, not the names which remain in the source but don't affect the
binary.

These changes are important because -debugdir adds a non-trivial amount
of work, which will impede build caching once that feature lands. We
will likely make -debugdir support build caching eventually, but for
now, this preliminary change will make 'go test' much faster with build
caching.

And of course, the tests get simpler, which is nice.
4 years ago
pagran 00c1d5b11d
add test for Go version checking (#140)
Add tests for Go version checking

Fix panic if go version has invalid format

Fixes: #121
Co-authored-by: Daniel Martí <mvdan@mvdan.cc>
4 years ago
pagran 90fa325da7
Rewrite renaming logic for private names and reduce length of public names (#135)
1. Now private names are obfuscated based on the counter in scope of the package.
2. The length of public names is reduced to 4 bytes.
4 years ago
Andrew LeFevre 0d182a3dbd remove unnecessary data from runtime if -tiny is passed
Fixes #127. Saves an additional ~1-2% binary size in my testing.
4 years ago
Andrew LeFevre d679944408
Strip all filename and position info when -tiny is passed (#128)
Co-authored-by: pagran <pagran@protonmail.com>
Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
Daniel Martí 805c895d59 set up an AUTHORS file to attribute copyright
Many files were missing copyright, so also add a short script to add the
missing lines with the current year, and run it.

The AUTHORS file is also self-explanatory. Contributors can add
themselves there, or we can simply update it from time to time via
git-shortlog.

Since we have two scripts now, set up a directory for them.
4 years ago
lu4p d8d784639f
Validate the user provided seed. (#126)
Also allow base64 seeds without padding.

Fixes #123.
4 years ago
Daniel Martí f764467e9b all: update the docs a bit
Rework the features section in the README, leaving optional features at
the end of the list. Simplify the caveats list, too; the build cache and
exported field/method bits only need one point each. Overall, the
section was far too wordy for little reason.

Also redo the help text a bit. There's now a line to briefly introduce
the tool, as well as a link to the README with all the details. Finally,
the flags have shorter and more consistent help strings.

While at it, remove two unused global vars as spotted by staticcheck.
4 years ago
Andrew LeFevre c8d61c772f
Garble imports and package paths in GOPRIVATE (#116)
Finally, finally this is done. This allows import paths to be obfuscated by modifying
object/archive files and garbling import paths contained within. The bulk of the
code that makes parsing and writing Go object/archive files possible lives at
https://github.com/Binject/debug/tree/master/goobj2, which I wrote as well.

I have tested by garbling and checking for import paths via strings and grep
(in order of difficulty) https://github.com/lu4p/binclude, garble itself, and
https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck.

This only supports object/archive files produced from the Go 1.15 compiler.
The object file format changed at 1.15, and 1.14 and earlier is not supported.

Fixes #13.
4 years ago
Andrew LeFevre 30df5e9bbd
Disable plugin test (#120)
* disable plugin test for now, add note to README

* add link to issue in README

* fix README link to issue
4 years ago
pagran bd46c29380
add blacklist for runtime std packages 4 years ago
lu4p 388ff7d1a4
remove buggy number literal obfuscation
Also remove boolean literal obfuscation.
4 years ago
Daniel Martí d3af58b558 complain when GOPRIVATE matches no packages
This is important, because it would mean that we would obfuscate
nothing. At best, it would be confusing; at worst, it could mislead
the user into thinking the binary is obfuscated.

Fixes #20.
Updates #108.
4 years ago
Daniel Martí 81b4c49702 move the "missing -trimpath" test to the slow group (#106)
This shouldn't break often, so it doesn't need to be covered by 'go test
-short'. Moreover, it's still a relatively expensive step, since we end
up reaching package compilation.
4 years ago
Daniel Martí 511779d8ff testdata: set GOPRIVATE in all but two tests (#104)
basic.txt just builds main.go without a module. Similarly, we leave
imports.txt without a GOPRIVATE, to test the 'go list -m' fallback.

For all other tests, explicitly set GOPRIVATE, to avoid two exec calls -
both 'go env GOPRIVATE' as well as 'go list -m'. Each of those calls
takes in the order of 10ms, so saving ~26 exec calls should easily add
to 200-300ms saved from 'go test -short'.
4 years ago
lu4p ea51e78283 Check that all files use LF line endings in CI 4 years ago
Daniel Martí 75e904f6d4
various minor cleanups and fixes (#99)
Error strings should never be capitalized.

A binsubstr line in one of the tests was duplicate and thus useless.

Remove duplicate or trailing spaces in test scripts.

Finally, add a TODO for an optimization I just spotted.
4 years ago
pagran 2735555ab2
Update filename and add line number obfuscation (#94)
Fixes  #2.

Line numbers are now obfuscated, via `//line` comments.
Filenames are now obfuscated via `//line` comments, instead of changing the actual filename.
New flag `-tiny` to reduce the binary size, at the cost of reversibility.
4 years ago
lu4p 7df14ad860 Fix reflect detection if -literals is passed.
Fixes #93.

The second typecheck lead to the creation of different type objects,
which didn't match the types in the blacklist anymore.

It turns out we don't need the second typecheck,
therfore it is now removed.
4 years ago
Daniel Martí cdac2cd3d6 testdata: avoid fmt in the implement test script
Like other tests, importing fmt results in quite a lot of extra work,
due to the lack of build caching.

In this particular test, we wanted fmt.Println so that T.String would be
called in an indirect way, without defining or referencing Stringer
interface in the main package.

We can do that by rolling our own "tinyfmt" package in a dozen or so
lines of code.

Below is how 'go test -short -vet=off -run Script/implement' is
affected, measured via benchcmd and benchstat:

	name                   old time/op         new time/op         delta
	GoTestScriptImplement          3.67s ± 9%          2.65s ±11%  -27.68%  (p=0.008 n=5+5)

	name                   old user-time/op    new user-time/op    delta
	GoTestScriptImplement          8.18s ± 4%          4.55s ± 9%  -44.35%  (p=0.008 n=5+5)

	name                   old sys-time/op     new sys-time/op     delta
	GoTestScriptImplement          1.27s ±12%          0.71s ±13%  -44.07%  (p=0.008 n=5+5)

	name                   old peak-RSS-bytes  new peak-RSS-bytes  delta
	GoTestScriptImplement          145MB ± 1%          145MB ± 2%     ~     (p=1.000 n=5+5)

All in all, we shave about one full second. It doesn't seem to affect
the total 'go test -short' noticeably, but every little bit counts.
4 years ago
pagran 28adbaa73b
Randomize operator (xor, add, subtract) on all obfuscators (#90)
Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
pagran 2eba744530
Add XorSeed obfuscator (#86)
Co-authored-by: lu4p <lu4p@pm.me>
4 years ago
Daniel Martí aa9767b0d2 add a regression test for #82
The test case we had didn't have a realistic-looking module path with a
dot, so we hadn't noticed the bug with IndexByte.

Fix that. We verified that the new test fails if we undo the fix.
4 years ago
Daniel Martí d0e01478f0 keep build flags when calling 'go list'
Otherwise any build flags like -tags won't be used, and we might easily
end up with errors or incorrect packages.

The common case with -tags is covered by one of the integration test
scripts. On top of that, we add a table-driven unit test to cover all
edge cases, since there are many we can do quickly in a unit test.

Fixes #82.
4 years ago
Daniel Martí 7fe0bf4787 simplify the main code flow somewhat
We don't really care about tools other than "compile" and "link". Stop
trying to keep a complete list.

Use "if err := f(); err != nil {" where it makes sense.

Simplify some declarations, and use a better variable name than "fW".
4 years ago
pagran 9c25f4c2b2
Add xorShuffle obfuscator (#85)
* Refactoring

* Rename Xor2 to XorShuffle
4 years ago
pagran c51e08ef37
Add split obfuscator (#81) 4 years ago
Daniel Martí 65461aabce reuse a single 'go list -json -export -deps' call
Instead of doing a 'go list' call every time we need to fetch a
dependency's export file, we now do a single 'go list' call before the
build begins. With the '-deps' flag, it gives us all the dependency
packages recursively.

We store that data in the gob format in a temporary file, and share it
with the future garble sub-processes via an env var.

This required lazy parsing of flags for the 'build' and 'test' commands,
since now we need to run 'go list' with the same package pattern
arguments.

Fixes #63.
4 years ago
pagran c2079ac0a1
Add test for literal obfuscators (#80)
* Combine literals-all-obfuscators.txt nad literals.txt
Rewrite literals.txt logic

* Remove unused \s

* Refactoring and add float ast helpers
4 years ago
lu4p 21c67b91b1
Only obfuscate required identifiers (#79)
The following identifiers are now skipped,
because they never show up in the binary:

- constant identifiers
- identifiers of local variables
(includes function params and named returns)
- identifiers of local types
4 years ago
lu4p 50d24cdf51 Add float, int, and boolean literal obfuscation.
Add ast helper functions to reduce ast footprint.

Add binsubfloat and binsubint functions for testing.

Fixes #55.
4 years ago
lu4p 705f9d3a28 Fix byte array and untyped constant obfuscation.
Byte arrays were previously,
obfuscated as byte slices.

Untyped constants are now skipped,
because they cannot be replaced with typed variables.
4 years ago
lu4p d48bdbadae Use XOR instead of AES for literal obfuscation.
Implement a literal obfuscator interface,
to allow the easy addition of new encodings.

Add literal obfuscation for byte literals.

Choose a random obfuscator on literal obfuscation,
useful when multiple obfuscators are implemented.

Fixes #62
4 years ago
Daniel Martí 9c4b7d5a44 add the first benchmark and CONTRIBUTING doc 4 years ago
lu4p b3616f19c4
fix implementedOutsideGo, fixes #56 (#59)
Injected functions were mistaken for functions implemented outside go.

Asm functions:
obj.Scope().Pos() == 0
obj.Scope().End() == 0

Injected functions:
obj.Scope().Pos() == 0
obj.Scope().End() == 1

We now check for the End instead of the Pos.
4 years ago
Andrew LeFevre 7ede37cc0b
add runtime API to suppress printing fatal errors
Fixes #50.
4 years ago
lu4p f1bf6f91ee
skip literals used in constant expressions
Fixes #39.
4 years ago
Daniel Martí 3e4f3821ea don't leak build version information via a const either
This requires a bit of extra magic to replace one constant in
runtime/internal/sys, but that was simple enough given that we can reuse
a lot of the code to parse the files and write them to a temporary dir.

We can also drop the -X flags, as runtime.buildVersion is based on the
constant that we replace here.

Fixes #44, again.
4 years ago
Daniel Martí 649cc2f6ba strip Go version information from the binary too
Fixes #44.
4 years ago
Daniel Martí c7d1fc7c60 strip buildid information from linked binaries
Otherwise, one can use 'go tool buildid' to obtain the main package's
build ID, which can make de-obfuscating the main package much simpler.

Fixes #43.
4 years ago
lu4p 234174b418
don't obfuscate some literals which might break typechecking 4 years ago
lu4p 4c64b13506
make -seed=random use the same random seed for all packages
Otherwise, a different random seed per package will break imported names.
4 years ago
lu4p 0cf8d4e7a6
add seed flag to control how builds are reproducible
Fixes #26.
4 years ago
Daniel Martí 5604a2aa9e avoid importing fmt in strings test
Reduces its 'go test -short' time from ~3s to ~2.4s on my laptop, since
we have to compile fewer dependencies.
4 years ago
Daniel Martí a09b197fe2 remove the code to handle a nil file.Imports
I could not reproduce the supposed panic, even after I was able to reach
a nil x.Imports in that line in question with the modified test.
4 years ago
lu4p dd1fc4ed87
don't replace all consts with vars
In some cases, such as iotas or when constants are later required to be constants,
we could break compilation. Be more conservative.

Fixes #32.
4 years ago
Nicholas Jones ecbcc61a62
handle embedded struct fields with universe scope
Whilst it may not be particularly common, it is legal to embed fields
where the type has universe scope (e.g. int, error, etc). This can
cause a panic in 2 difference places:

- When embedding `error`, a named type is resolved but the package is
nil. The call to `pkg.Name()` results in a panic
- When embedding a basic type such as `int`, no named type is resolved
at all. The call to `namedType(obj.Type()).Obj()` results in a panic

I'm assuming it is OK to return early when a named type cannot be
resolved.. we could let it continue but I think `pkg` should be set to
nil to be correct, so it'd end up returning straight away anyway.
4 years ago
Daniel Martí 04e8beed32 testdata: add sections to scripts/test.txt 4 years ago
lu4p 65ceb9b7ca
allow easy inpection of garbled code
Fixes #17.
4 years ago
lu4p 077d02d43a
add basic literal obfuscation, starting with strings
Fixes #16.
4 years ago
Daniel Martí 462f60a307 sto pusing -toolexec directly in the tests
Since we introduced $GARBLE_DIR, we stopped recommending the use of
toolexec directly. It's still possible to set up the right flags and env
vars, but that will be a moving target.

In particular, string obfuscation in #16 will require using $GARBLE_DIR
in more scenarios. A work-in-progress patch for string obfuscation
triggered this test script to start failing for the reason above.

While at it, we don't care about what the second build contains, since
we already compare it with the previous build.
4 years ago
Daniel Martí 80538f19c7 blacklist struct fields with reflection too
In the added test, the unexported field used to be garbled.

Reflection can only reach exported methods, exported fields, and
unexported fields. Exported methods and fields are currently never
garbled, so unexported fields was the only missing piece.
4 years ago
Daniel Martí 4bc64ef8fb make detection of reflect more robust
It now works with variables and composite type expressions too.
4 years ago
lu4p 8b898ad0d2
exclude identifiers used via reflection
If reflect.TypeOf or reflect.ValueOf are used on a type declared in the same package,
don't garble that type name or any of its fields.

Fixes #15.
4 years ago
Daniel Martí e8074d4665 support building ad-hoc plugin packages
That is, plugin packages by source file names, not by package path.

Fixes #19.
4 years ago
Daniel Martí e4b58b1452 reduce unnecessary std imports in tests
Since we have to recompile all dependencies, this reduces a substantial
amount of work, reducing 'go test -short' time from ~16s to ~12s on my
laptop.
4 years ago
Daniel Martí 56a1fd0257 support -ldflags=-X=pkg.name=str with garbled names
Because the linker has access to all the build IDs, just like the
compiler, we can support this transparently. Add a test too.

Fixes #21.
4 years ago
Daniel Martí 3c97725ccc skip plugin test case on Windows
Spotted by CI.
4 years ago
Daniel Martí 7321b29efe first version of plugins working
Add a caveat about -trimpath too.

Fixes #18.
4 years ago