Commit Graph

659 Commits (master)
 

Author SHA1 Message Date
Daniel Martí 7bb040e635 update to Go tip from April 28th
A couple of new packages in runtimeAndDeps,
and go list's Package.DepsErrors may now include package build errors
which we want to ignore as we would print those as duplicates.
1 year ago
Dominic Breuker b587d8c01a
use the "simple" obfuscator for large literals
Changes literal obfuscation such that literals of any size will be obfuscated,
but beyond `maxSize` we only use the `simple` obfuscator.
This one seems to apply AND, OR, or XOR operators byte-wise and should be safe to use,
unlike some of the other obfuscators which are quadratic on the literal size or worse.

The test for literals is changed a bit to verify that obfuscation is applied.
The code written to the `extra_literals.go` file by the test helper now ensures
that Go does not optimize the literals away when we build the binary.
We also append a unique string to all literals so that we can test that
an unobfuscated build contains this string while an obfuscated build does not.
1 year ago
Daniel Martí 4d7546703a update gotip in CI and fix -tiny on the latest tip
printOneCgoTraceback now returns a boolean rather than an int.
Since we need to have different logic based on the Go version,
and toolchainVersionSemver was only set for the main process,
move the string to the shared cache global.
This is a nice thing to do anyway, to reduce the number of globals.

While here, update actions/setup-go to v4, which starts caching
GOMODCACHE and GOCACHE by default now.
Disable it, because it still doesn't help in our case,
and GitHub's Actions caching is still really inefficient.

And update staticcheck too.
1 year ago
Daniel Martí cd003eade0 support `garble run`
It's the third time I've wanted it to quickly test out standalone
reproducers when people submit bugs.

Fixes #661.
1 year ago
Daniel Martí d1da066120 print chosen seed when building with -seed=random
The seedFlag.random field had never worked,
as my refactor in December 2021 never set it to true.

Even if the boolean was working, we only printed the random seed
when we failed. It's still useful to see it when a build succeeds,
for example when wanting to reproduce the same binary
or when wanting to reverse a panic from the produced binary.

Add a test this time.

Fixes #696.
1 year ago
Daniel Martí a186419d3d avoid rebuilding garble in the main benchmark
Similar to what testscript does, we can reuse the test binary by telling
TestMain to run the main function rather than the Go tests.

This saves a few hundred milliseconds out of each benchmark run.
1 year ago
Daniel Martí 1f39d0af72 update gotip and adapt to upstream changes
https://go.dev/cl/466095 lightly refactored the runtime
in a way that broke extractNameOff. In particular, the code

	func cfuncname(f funcInfo) *byte {
		if !f.valid() || f.nameOff == 0 {
			return nil
		}
		return &f.datap.funcnametab[f.nameOff]
	}
	func funcname(f funcInfo) string {
		return gostringnocopy(cfuncname(f))
	}

is now simply

	func funcname(f funcInfo) string {
		if !f.valid() {
			return ""
		}
		return f.datap.funcName(f.nameOff)
	}

Since extractNameOff looked for the func named cfuncname,
and looked for the nameOff selector inside an index expression,
all of that code no longer worked properly.

It all existed to find the name of the field, nameOff,
so that we would automatically adapt if upstream renames it.
Unsurprisingly, the code using the field got refactored first.
It doesn't seem like the extra code on our part is helping us,
and assuming the name of the field works for all Go versions,
so do that instead.

If upstream does rename the field in the future,
the obfuscated Go builds will start failing in an obvious way.
If or when that comes to pass, we can change our constant string.
1 year ago
Daniel Martí 9d04637009 ensure the alignment of sync/atomic types works
Added in Go 1.19, types like sync/atomic.Uint64 are handy,
because they ensure proper alignment even on 32-bit GOOSes.
However, this was done via a magic `type align64 struct{}`,
which the compiler spotted by name.

To keep that magic working, do not obfuscate the name.
Neither package path was being obfuscated,
as both packages contain compiler intrinsics already.

Fixes #686.
1 year ago
Daniel Martí 30927da637 avoid panic when a package's imports cannot be loaded
I mistakenly understood that, when the DepsErrors field has errors,
the Error field would contain an error as well.
That is not always the case; for example,
the imports_missing package in the added test script
had DepsErrors set but Error empty, causing a nil dereference panic.

Make the code more robust, and report both kinds of load errors.

Fixes #694.
1 year ago
Daniel Martí cf49f7f3b1 use the host's GOEXE when building the linker
We're building the linker binary for the host GOOS,
not the target GOOS that we happen to be building for.

I noticed that, after running `go test`, my garble cache
would contain both link and link.exe, which made no sense
as I run linux and not windows.

`go env` has GOHOSTOS to mirror GOOS, but there is no
GOHOSTEXE to mirror GOEXE, so we reconstruct it from
runtime.GOOS, which is equivalent to GOHOSTOS.

Add a regression test as well.
1 year ago
pagran b87830eb97
use git in english when matching its output
See db58416d7f.

Fixes #698.
1 year ago
Daniel Martí 059c1d68e2 use fewer build flags when building std or cmd
When we use `go list` on the standard library, we need to be careful
about what flags are passed from the top-level build command,
because some flags are not going to be appropriate.
In particular, GOFLAGS=-modfile=... resulted in a failure,
reproduced via the GOFLAGS variable added to linker.txtar:

	go: inconsistent vendoring in /home/mvdan/tip/src:
		golang.org/x/crypto@v0.5.1-0.20230203195927-310bfa40f1e4: is marked as explicit in vendor/modules.txt, but not explicitly required in go.mod
		golang.org/x/net@v0.7.0: is marked as explicit in vendor/modules.txt, but not explicitly required in go.mod
		golang.org/x/sys@v0.5.1-0.20230208141308-4fee21c92339: is marked as explicit in vendor/modules.txt, but not explicitly required in go.mod
		golang.org/x/text@v0.7.1-0.20230207171107-30dadde3188b: is marked as explicit in vendor/modules.txt, but not explicitly required in go.mod

		To ignore the vendor directory, use -mod=readonly or -mod=mod.
		To sync the vendor directory, run:
			go mod vendor

To work around this problem, reset the -mod and -modfile flags when
calling "go list" on the standard library, as those are the only two
flags which alter how we load the main module in a build.

The code which builds a modified cmd/link has a similar problem;
it already reset GOOS and GOARCH, but it could similarly run into
problems if other env vars like GOFLAGS were set.
To be on the safe side, we also disable GOENV and GOEXPERIMENT,
which we borrow from Go's bootstrapping commands.
1 year ago
pagran 6a8dda9b8e Add pagran to FUNDING.yml 1 year ago
Daniel Martí 9598e439ed CI: test against a newer Go tip
While here, update staticcheck.
1 year ago
Daniel Martí 6c4274c326 scripts: remove TODO about building third party programs
`go build ./...` does indeed compile and link main packages,
it just does not move the resulting binaries anywhere permanent
like `go install` does.

As such, the TODO isn't relevant; the fact that we build all packages
inside each module means we are already linking any binaries matched via
`./...` from the module root.

We don't run any of the binaries, which would catch panics at run-time,
but we already have a note at the top about using `garble test`.
1 year ago
Daniel Martí d0c8c8d844 scripts: start checking google.golang.org/protobuf
The current garble release is able to obfuscate it with Go 1.20.

While here, re-generate all files to use "go 1.20" directives,
and add a TODO about also testing binary builds for each project.

See #600.
1 year ago
Daniel Martí bb69facbd8 cut test time with coverage by half
Per the inline comment, we want to build every package in the test suite
at least once, otherwise we won't get proper code coverage information.
For example, some code only triggers when obfuscating the runtime,
and if I run "go test" twice in a row, the second might reuse the
first's obfuscation of the runtime and skip the code entirely.

However, per the TODO, we used to solve this in a rather wasteful way,
by making each test use its own separate GOCACHE directory.
Instead, use a new GOCACHE directory, but share it between tests.
This is still enough, as we still build each package at least once.

Running the command below twice in a row with Go 1.20:

	go test -cover

took about 1m55s on my laptop before, and now takes about 1m10s.
Not great, but a noticeable improvement.
Both report a total coverage of 88.7%.

While here, do the same for GARBLE_CACHE_DIR,
and use testscript.Env.Setenv for simplicity.
1 year ago
pagran 86b7e334ba
implement funcInfo.entryoff encryption
At linker stage, we now encrypt funcInfo.entryoff value with a simple algorithm (1 xor + 1 mul). 
This makes it harder to relate function metadata (e.g. name) to function itself in binary, almost without affecting performance.
1 year ago
Daniel Martí 89b27fa7f9 tweaks thanks to code coverage info
The test package hack still appears to be needed as of Go 1.20.

Interestingly, the cgo filename check does not trigger at all anymore.
Presumably this means that it's not needed at all, and obfuscating code
generated by cgo appears to be fine. Go with that for now.
1 year ago
Daniel Martí e37f39054d update testscript to support code coverage with Go 1.20
See https://github.com/rogpeppe/go-internal/pull/201.
1 year ago
Daniel Martí 859a5877c9 internal/literals: re-enable the seed obfuscator
Now that we dropped Go 1.19, we can use it again,
since the referenced bug was fixed in Go 1.20.

This is a separate commit, as this change does alter the way we
obfuscate Go builds, so it's not just a cleanup.
1 year ago
Daniel Martí 658060851d drop bits of code to support Go 1.19 1 year ago
Daniel Martí b322876efe drop support for Go 1.19
Now that we're done with garble v0.9.x,
v0.10 will only support Go 1.20 as a minimum version.
1 year ago
pagran 9a8608f061
internal/literals: add benchmark to measure the run-time overhead 1 year ago
Daniel Martí 89facf1648 CHANGELOG: prepare for v0.9.3 1 year ago
Daniel Martí 5effe20c19 avoid breaking github.com/davecgh/go-spew
It assumes that reflect.Value has a field named "flag",
which wasn't the case with obfuscated builds as we obfuscated it.

We already treated the reflect package as special,
for instance when not obfuscating Method or MethodByName.
In a similar fashion, mark reflect's rtype and Value types to not be
obfuscated alongside their field names. Note that rtype is the
implementation behind the reflect.Type interface.

This fix is fairly manual and repetitive.
transformCompile, transformLinkname, and transformAsm should all
use the same mechanism to tell if names should be obfuscated.
However, they do not do that right now, and that refactor feels too
risky for a bugfix release. We add more TODOs instead.

We're not adding go-spew to scripts/check-third-party.sh since the
project is largely abandoned. It's not even a Go module yet.
The only broken bit from it is what we've added to our tests.

Fixes #676.
1 year ago
pagran b0f8dfb409
prevent "git apply" from walking parent directories
Even when setting git's current directory to a temporary directory,
it could find a git repository in a parent directory and still malfunction.
Use the --git-dir flag to ensure that walking doesn't happen at all.

While here, ensure that "git apply" is always applying our patches,
and add a regression test to linker.txtar when not testing with -short.
1 year ago
Daniel Martí 95ef0357da support inline and non-spaced comments in assembly
Go contains such inline comments, like:

	crypto/aes/gcm_ppc64x.s:129: VPMSUMD IN, HL, XL // H.lo·H.lo

We didn't notice since these comments were somewhat rare.
While here, "//" does not need to be followed by a space.

The code turns out to be pretty easy with strings.Cut.

Fixes #672.
1 year ago
pagran f7bde1d40e
don't use the current directory to patch and build cmd/link
We were noticing sporadic `go test` failures where running
an obfuscated binary could panic with:

    fatal error: invalid function symbol table

It turns out that this could happen when modinfo.txtar runs first;
when it patched the linker with `git -C apply`, its current directory
had a valid git repository initialized, and apparently that somehow
prevents the patches from being applied.

It's unclear whether this is git's fault or our own, but in any case,
using a temporary working directory is easier and fixes the bug.
Do the same for `go build`, just in case.
1 year ago
Daniel Martí 99d30c033e go.mod: update dependencies before a release 1 year ago
Daniel Martí 3e0d360777 README: update the minimum Go version required
We had dropped supprot for Go 1.18.x back in October,
but completely forgot about the README.
1 year ago
Daniel Martí 08302ed5fc prepare changelog for v0.9.2
I will do the tag tomorrow morning.
1 year ago
Daniel Martí 6d54a77771 update flag tables with Go 1.20
`go help build` now has -C, -cover, -coverpkg, and -pgo.

There are no new boolean flags, but note that -cover is now a common
build flag rather than just a test flag.

While here, sort the lists so that they are easier to skim for missing
items in the future.
1 year ago
Daniel Martí 4919be0658 switch to final Go 1.20 release and re-enable gotip in CI
Go master, the upcoming Go 1.21, has had its merge window open for over
two weeks at this point, and it seems calmer at this point.

ALso update staticcheck to its latest release, which supports Go 1.20.
1 year ago
Daniel Martí d0a6faa4e6 README: rewrite seeds section
A user correctly points out that some sentences were confusing, like:

	It can also make reverse-engineering harder, as an end user could
	guess what version of Go or garble you're using.

Rewrite the whole section. It now also explains what I meant by that
sentence with a practical example.

I also merged the bits about "provide your own seed" and "rotating the
seeds", because they're both talking about the same mechanism.

Fixes #666.
1 year ago
Daniel Martí 2ee9cf7a43 support go:linkname directives pointing at methods
This is not common, but it is done by a few projects.
Namely, github.com/goccy/go-json reached into reflect's guts,
which included a number of methods:

	internal/runtime/rtype.go
	11://go:linkname rtype_Align reflect.(*rtype).Align
	19://go:linkname rtype_FieldAlign reflect.(*rtype).FieldAlign
	27://go:linkname rtype_Method reflect.(*rtype).Method
	35://go:linkname rtype_MethodByName reflect.(*rtype).MethodByName
	[...]

Add tests for such go:linkname directives pointing at methods.
Note that there are two possible symbol string variants;
"pkg/path.(*Receiver).method" for methods with pointer receivers,
and "pkg/path.Receiver.method" for the rest.

We can't assume that the presence of two dots means a method either.
For example, a package path may be "pkg/path.with.dots",
and so "pkg/path.with.dots.SomeFunc" is the function "SomeFunc"
rather than the method "SomeFunc" on a type "dots".
To account for this ambiguity, rather than splitting on the last dot
like we used to, try to find a package path prefix by splitting on an
increasing number of first dots.

This can in theory still be ambiguous. For example,
we could have the package "pkg/path" expose the method "foo.bar",
and the package "pkg/path.foo" expose the func "bar".
Then, the symbol string "pkg/path.foo.bar" could mean either of them.
However, this seems extremely unlikely to happen in practice,
and I'm not sure that Go's toolchain would support it either.

I also noticed that goccy/go-json still failed to build after the fix.
The reason was that the type reflect.rtype wasn't being obfuscated.
We could, and likely should, teach our assembly and linkname
transformers about which names we chose not to obfuscate due to the use
of reflection. However, in this particular case, reflect's own types
can be obfuscated safely, so just do that.

Fixes #656.
1 year ago
Daniel Martí 09a17375e3 reduce verbosity in the last change
Declare the ast.GenDecl node once,
which allows us to deduplicate and inline code.
resolveImportSpec doesn't need to be separate either.

We also don't need explicit panics for unexpected nils;
we can rely on nil checks inserted by the compiler.

Finally, move the bulk of the code outside of the scope.Names loop,
which unindents most of the logic.

While here, add a few more inline docs.
1 year ago
pagran 7c50979899
ensure that all imports are used after obfuscation
garble's -literals flag and its patching of the runtime may leave unused imports.
We used to try to detect those and remove the imports,
but that was still buggy with edge cases like dot imports or renamed imports.

Moreover, it was potentially incorrect.
Completely removing an import from a package means we don't run its init funcs,
which could have side effects changing the behavior of a program.
As an example, database/sql drivers are registered at init time.

Instead, for each import in an obfuscated Go file,
add an unnamed declaration which references the imported package.
This may not be necessary for all imported packages,
as only a minority become unused due to garble,
but it's also relatively harmless to do so.

Fixes #658.
1 year ago
Daniel Martí f34be3f572 CHANGELOG: prepare for v0.9.1 release 1 year ago
Daniel Martí 0ec363d9c8 avoid breaking intrinsics when obfuscating names
We obfuscate import paths as well as their declared names.
The compiler treats some packages and APIs in special ways,
and the way it detects those is by looking at import paths and names.

In the past, we have avoided obfuscating some names like embed.FS or
reflect.Value.MethodByName for this reason. Otherwise,
go:embed or the linker's deadcode elimination might be broken.

This matching by path and name also happens with compiler intrinsics.
Intrinsics allow the compiler to rewrite some standard library calls
with small and efficient assembly, depending on the target GOARCH.
For example, math/bits.TrailingZeros32 gets replaced with ssa.OpCtz32,
which on amd64 may result in using the TZCNTL instruction.

We never noticed that we were breaking many of these intrinsics.
The intrinsics for funcs declared in the runtime and its dependencies
still worked properly, as we do not obfuscate those packages yet.
However, for other packages like math/bits and sync/atomic,
the intrinsics were being entirely disabled due to obfuscated names.

Skipping intrinsics is particularly bad for performance,
and it also leads to slightly larger binaries:

			 │      old      │                 new                 │
			 │     bin-B     │     bin-B      vs base              │
	Build-16   5.450Mi ± ∞ ¹   5.333Mi ± ∞ ¹  -2.15% (p=0.029 n=4)

Finally, the main reason we noticed that intrinsics were broken
is that apparently GOARCH=mips fails to link without them,
as some symbols end up being not defined at all.
This patch fixes builds for the MIPS family of architectures.

Rather than building and linking all of std for every GOARCH,
test that intrinsics work by asking the compiler to print which
intrinsics are being applied, and checking that math/bits gets them.

This fix is relatively unfortunate, as it means we stop obfuscating
about 120 function names and a handful of package paths.
However, fixing builds and intrinsics is much more important.
We can figure out better ways to deal with intrinsics in the future.

Fixes #646.
1 year ago
Daniel Martí 598d5182fb update x/tools version used in go:generate
Fixes running this go:generate line with Go tip.
1 year ago
Daniel Martí 0b096c9e75 generate go_std_tables.go in its entirety
No more having to manually run the script and adapting it to Go code.
1 year ago
Daniel Martí 33ceca7ef8 split runtimeAndDeps and runtimeLinknamed into a separate Go file
The next commit will start generating these via //go:generate,
so this first change keeps the diffs easier to review.
1 year ago
pagran d3c6ed6729
add support for obfuscation of dot-imported string constants 1 year ago
Daniel Martí d4e7abc28c reuse calls to testing.B.TempDir in the build benchmark
Multiple calls to TempDir give new unique temporary directories.
We don't need that, as we already used subdirectories.
1 year ago
Daniel Martí d9e74dabbb CI: add Go 1.20rc3, disable gotip for now
tip is now the start of Go 1.21 as of a couple of days ago.
However, the first week or two is when the biggest changes land,
which means that Go tip is far more prone to bugs and changes that might
break garble.

We'll start tracking tip again in a few weeks, once the dust has settled
and we can look at what changes might have broken garble.
1 year ago
Daniel Martí 71eda055c2
CHANGELOG: prepare for v0.9.0 1 year ago
Daniel Martí 294450fdc3 CHANGELOG: draft for v0.9.0 1 year ago
pagran e04c605c93 preallocate data in shuffle and split literal obfuscator 1 year ago
lu4p 5b2193351f Decrease binary size for -literals
Only string literals over 8 characters in length are now being
obfuscated. This leads to around 20% smaller binaries when building with
-literals.

Fixes #618
1 year ago