815 Commits

Author SHA1 Message Date
KismetDev 900696b816 do not create ./build in cwd when cache init is skipped (#1035)
sharedCache is allocated before goVersionOK runs, but CacheDir is
only set once the version check passes. The trim defer in mainErr
only checked sharedCache != nil, so on the too-new-version path
openCache joined an empty CacheDir with "build" and created the
directory in CWD.

Fixes #995.
2026-04-21 17:00:42 +01:00
Paul eef2eae50b fix reflection handling for foreign types and aliases (#1032)
Preserve reflected names for foreign named types, alias-backed unnamed structs,
and foreign struct fields by hashing names with the declaring package or struct
identity as appropriate. Extend the reflect script coverage to assert the
foreign type and field names that must now remain visible.

Fixes #996.

Co-Autored by: 0xKismetDev <131729061+0xKismetDev@users.noreply.github.com>
2026-04-18 17:52:09 +02:00
Daniel Martí e5d31e389f README: shorten the content a bit
* shorten the @master suggestion
* merge some similar mechanism list items
* simplify the Go versions bit with GOTOOLCHAIN
2026-04-16 11:37:23 +02:00
Daniel Martí b55fa77e31 simplify the typeparams test case added for #1027
The marshaler interface and some of the other types were unnecessary.
The remaining test still fails without the fix,
because the crux of the issue is the separate package
with the Result generic type having Data as a type parameter and field.
2026-04-16 11:19:43 +02:00
Golo Roden cdc4b23ac5 fix fieldToStruct for generics instantiated across package boundaries (#1028)
When compiling a package that instantiates a generic type from another
package, info.Types only contains the instantiated *types.Named. The
*types.Struct case in recordFieldToStruct returns early on instantiated
structs (field != field.Origin()), so the origin struct was never
recorded in fieldToStruct. At rewrite time, looking up the origin field
then failed with "could not find struct for field X".

Fix by also recursing into typ.Origin() for instantiated named types,
so the uninstantiated struct is visited and its fields are recorded.

Fixes #1027.
2026-04-16 00:03:37 +01:00
Daniel Martí 28c2fd54c0 internal/literals: simplify the obfuscator selection code
The "next obfuscator" logic was split into three funcs for no reason;
it all fits into a single function in less than a dozen lines.

While here, avoid calling the obfRand variables the same as the
type itself, as that leads to significant confusion.
2026-04-15 23:24:06 +02:00
Daniel Martí cecc5d0017 testdata/script: stop checking that every literal obfuscator is used
This is an inherently flaky test, because even if we generate
thousands of literal strings of varying sizes,
we could still get unlucky and end up not using one of the obfuscators.

I briefly looked at a unit test instead, but at that point it's
a bit silly as the underlying logic picks a random element from a slice.
So the unit test looks almost exactly as the code it's testing.

Remove the test entirely. The code it's testing - that we select across
all of our available obfuscators - is simple enough where we can just
look at it if we want to double check that it does what we expect.
2026-04-15 23:24:06 +02:00
Daniel Martí 4326dcde14 split check-third-party in CI and disable github.com/samber/lo
The main "linux" job is the slowest by far at over 20 minutes.
The CPU on the hosted Actions runners is very slow,
but luckily, we get 20 concurrent Linux runners.
Split the main Linux job further.

Also, now that generics in Go are widespread,
we no longer need a Go generics library for good coverage.

While here, time each of the third party project commands.
2026-04-15 23:24:06 +02:00
Daniel Martí a68444505d CI: only use -race with -short, and in parallel
Given that the Linux test job is currently the slowest,
the added parallelism will help.
The full or "long" tests aren't needed to look for races,
as our short tests already cover almost all of our code.
This is a better compromise for CI speed.
2026-04-15 23:24:06 +02:00
Daniel Martí 093f8688f8 stop testing garble -literals build std
This is very expensive; it takes over 30s to run on my laptop.
We already cover `garble build std` in gogarble.txtar,
so this is merely about testing literal obfuscation.

Given the huge cost, this doesn't seem worth it.
If any bugs are uncovered in the literal obfuscators,
we can always add more test cases here.
2026-04-14 10:32:47 +02:00
Daniel Martí ef2385ee97 internal/literals: restrict the use of expensive obfuscators
The added benchmark script shows these numbers for building and running
with vanilla go on literal sizes between 16B and 2048B,
showing that vanilla Go isn't affected at all by these sizes:

                │      go      │
                │    sec/op    │
    Build/16B     118.1m ± ∞ ¹
    Build/64B     123.1m ± ∞ ¹
    Build/256B    119.7m ± ∞ ¹
    Build/1024B   119.1m ± ∞ ¹
    Build/2048B   124.3m ± ∞ ¹
    Run/16B       1.671m ± ∞ ¹
    Run/64B       1.143m ± ∞ ¹
    Run/256B      1.190m ± ∞ ¹
    Run/1024B     1.222m ± ∞ ¹
    Run/2048B     1.080m ± ∞ ¹

Our simple and swap obfuscators scale pretty well to these same sizes,
only causing moderate slow-downs to build and runtime speeds:

                │                simple                │                 swap                 │
                │    sec/op     vs base                │    sec/op     vs base                │
    Build/16B     268.0m ± ∞ ¹  +126.88% (p=1.000 n=1)   262.0m ± ∞ ¹  +121.80% (p=1.000 n=1)
    Build/64B     253.8m ± ∞ ¹  +106.16% (p=1.000 n=1)   252.4m ± ∞ ¹  +105.00% (p=1.000 n=1)
    Build/256B    265.4m ± ∞ ¹  +121.78% (p=1.000 n=1)   276.7m ± ∞ ¹  +131.16% (p=1.000 n=1)
    Build/1024B   267.4m ± ∞ ¹  +124.44% (p=1.000 n=1)   315.0m ± ∞ ¹  +164.48% (p=1.000 n=1)
    Build/2048B   277.4m ± ∞ ¹  +123.11% (p=1.000 n=1)   383.8m ± ∞ ¹  +208.70% (p=1.000 n=1)
    Run/16B       1.740m ± ∞ ¹    +4.12% (p=1.000 n=1)   1.463m ± ∞ ¹   -12.47% (p=1.000 n=1)
    Run/64B       1.470m ± ∞ ¹   +28.66% (p=1.000 n=1)   1.455m ± ∞ ¹   +27.35% (p=1.000 n=1)
    Run/256B      1.729m ± ∞ ¹   +45.25% (p=1.000 n=1)   1.812m ± ∞ ¹   +52.26% (p=1.000 n=1)
    Run/1024B     1.315m ± ∞ ¹    +7.62% (p=1.000 n=1)   1.352m ± ∞ ¹   +10.60% (p=1.000 n=1)
    Run/2048B     1.425m ± ∞ ¹   +31.93% (p=1.000 n=1)   1.316m ± ∞ ¹   +21.88% (p=1.000 n=1)

However, the other three cause huge slow-downs in both build and runtime speeds:

                │                   split                    │                shuffle                 │                    seed                    │
                │     sec/op       vs base                   │    sec/op      vs base                 │     sec/op       vs base                   │
    Build/16B        326.6m ± ∞ ¹     +176.53% (p=1.000 n=1)    363.1m ± ∞ ¹   +207.44% (p=1.000 n=1)      217.4m ± ∞ ¹      +84.05% (p=1.000 n=1)
    Build/64B        400.6m ± ∞ ¹     +225.34% (p=1.000 n=1)    328.0m ± ∞ ¹   +166.39% (p=1.000 n=1)      262.1m ± ∞ ¹     +112.87% (p=1.000 n=1)
    Build/256B       824.7m ± ∞ ¹     +589.08% (p=1.000 n=1)    588.2m ± ∞ ¹   +391.45% (p=1.000 n=1)      873.7m ± ∞ ¹     +630.05% (p=1.000 n=1)
    Build/1024B     3257.7m ± ∞ ¹    +2634.84% (p=1.000 n=1)   1671.4m ± ∞ ¹  +1303.15% (p=1.000 n=1)     5000.0m ± ∞ ¹    +4097.53% (p=1.000 n=1)
    Build/2048B     5000.0m ± ∞ ¹    +3921.73% (p=1.000 n=1)   3936.4m ± ∞ ¹  +3066.21% (p=1.000 n=1)     5000.0m ± ∞ ¹    +3921.73% (p=1.000 n=1)
    Run/16B          1.680m ± ∞ ¹       +0.51% (p=1.000 n=1)    1.426m ± ∞ ¹    -14.67% (p=1.000 n=1)      1.908m ± ∞ ¹      +14.13% (p=1.000 n=1)
    Run/64B          1.560m ± ∞ ¹      +36.53% (p=1.000 n=1)    1.345m ± ∞ ¹    +17.74% (p=1.000 n=1)      1.704m ± ∞ ¹      +49.10% (p=1.000 n=1)
    Run/256B         2.133m ± ∞ ¹      +79.24% (p=1.000 n=1)    1.833m ± ∞ ¹    +53.98% (p=1.000 n=1)      1.838m ± ∞ ¹      +54.40% (p=1.000 n=1)
    Run/1024B        2.863m ± ∞ ¹     +134.21% (p=1.000 n=1)    1.786m ± ∞ ¹    +46.12% (p=1.000 n=1)   5000.000m ± ∞ ¹  +408975.92% (p=1.000 n=1)
    Run/2048B     5000.000m ± ∞ ¹  +462837.24% (p=1.000 n=1)    2.900m ± ∞ ¹   +168.54% (p=1.000 n=1)   5000.000m ± ∞ ¹  +462837.24% (p=1.000 n=1)

As such, limit the scope of when we apply our obfuscators in two ways.

First, always apply a limit of 2048 bytes for all obfuscators.
As we can see above, the two cheap obfuscators still add some overhead,
and we aren't testing what happens if they run on truly huge strings.
It's likely that they will still cause unexpected slow-down.

Second, split the obfuscators along this line and call them
"cheap" versus "expensive". The expensive ones are only used
for sizes of up to 256 bytes. We still measure moderate slow-downs
in build times of 400-600%, but this is a reasonable compromise for now.
We will be filing bugs with upstream Go about the compiler overhead.

We adjust generateLiterals accordingly;
it now generates 100 literals between MinSize and MaxSize,
which must be obfuscated in some way, and 5 literals past MaxSize,
which don't need to be obfuscated, to check for issues like crashes.

Fixes #928.
2026-04-14 10:32:47 +02:00
Daniel Martí 626c8c86ee scripts: rewrite bench_literals.go
The new script still produces output compatible with benchstat,
but it does the benchmarking directly via os/exec
rather than using `garble test`. This has a few benefits:

1) We can warm up the "base" caches before all runs
2) We can ensure that a run isn't affected by caching from other runs
3) We can still collect data when obfuscators hit a timeout
4) We can produce output including vanilla "go" as baseline
2026-04-14 10:32:47 +02:00
Daniel Martí f7b9166616 transform go_asm.h constant names in assembly files
The Go assembler generates go_asm.h with constants like TypeName__size
and TypeName_FieldName for struct types. When garble obfuscates type
and field names, these constants use the obfuscated names, but the
assembly source still refers to the originals, causing build failures.

During transformCompile, save the original-to-obfuscated name mapping
to the build cache. In the second asm pass (after compile has run),
load the mapping and rewrite the .s files with a strings.Replacer.

The work is shared between the compiler and assembler steps
because it would be too much work for the assembler step by itself.
These constants follow patterns like Type_field or Type__size,
so they don't use special characters that we can easily locate.

And the go_asm.h file that the compiler generates already uses
the obfuscated names, which we cannot reverse without the originals.
Fetching the original names and fields would require parsing
and typechecking, which is something that the compiler step does.

Without the fix, the added test fails as follows:

    > exec garble build
    [stderr]
    # test/with.many.dots/main
    ./.tmp/[...]/IQSArvqP.s:30: expected pseudo-register; found R11
    ./.tmp/[...]/IQSArvqP.s:31: expected pseudo-register; found R11
    ./.tmp/[...]/IQSArvqP.s:37: illegal or missing addressing mode for symbol Args__size
    asm: assembly of ./.tmp/[...]/IQSArvqP.s failed

Thanks to Golo Roden for figuring out a test reproducer
as well as the overall approach for a fix to the problem.
The test added here is simplified from his code,
and the fix is inspired by his approach.

Fixes #948.
2026-04-14 01:08:52 +02:00
Daniel Martí 1186bca36d make flagValueIter an iter.Seq
This is purely a cosmetic change, to make the callers nicer.
2026-04-14 01:08:52 +02:00
dajinglingpake 2e5692934c fix random seed warning 2026-04-13 08:05:06 +01:00
Daniel Martí 60a6019f19 CHANGELOG: add entry for v0.16.0 v0.16.0 2026-04-12 20:37:22 +01:00
Daniel Martí f23f15953a update x/tools dependencies ahead of the next release 2026-04-12 20:29:30 +01:00
Paul Scheduikat 437813d096 transform cgo_import directives as used by Windows
There are no integration tests because writing one is fairly tricky.
Luckily, the added code is fairly simple.
Windows in CI works as a form of regression test.
2026-04-12 15:19:28 +02:00
Daniel Martí b33003f77b CI: re-enable windows now that Go 1.26.2 is out
Go 1.26.2 should have fixed the runtime crash.
2026-04-12 15:19:28 +02:00
Daniel Martí 082b277fbf do not record any instantiated types in fieldToStruct
First, the current iteration of this code only needs to look at
go/types.Info.Types; looking at the Uses and Defs is redundant.
Note that we still need to fill Uses and Defs for ObjectOf to work.

Second, for consistent hashing in the face of generic types,
we have been using uninstantiated or "origin" field vars
when looking up fieldToStruct entries.

computeFieldToStruct tried to only look at these origin types,
but it didn't do so correctly. The test case added in #1016
made this evident given the added inconsistency panic check.

Third, resolve this by only checking the Origin method when we are
looping over the struct fields; if any of them is an instance,
abort as we are currently on an instantiated type.
This is not ideal, hence the TODO, but it works for now.

This resolves intermittent test failures with the test case
added in #1016; the fix there wasn't entirely incorrect,
but the code was buggy as we would loop over go/types.Info.Types
in the random map iteration order, so it was a coin flip
whether we would record the uninstantiated or instantiated types.

We don't add a test because adding one is tricky, but luckily,
the added assertion catches any such inconsistency in our logic,
so that should work as a form of a regression test.
2026-04-10 20:50:26 +02:00
Ariel Mashraki 8503b1b55a fix generic anonymous structs as return paramters (#1016) 2026-03-21 21:06:44 +01:00
Daniel Martí 2322872d3b avoid obfuscating crypto/internal/fips140/...
Attempting to fix a failure on GOOS=darwin:

    > exec garble -literals build std
    [stderr]
    # crypto/internal/fips140
    <autogenerated>:1: crypto/internal/fips140.YVgfcHoMsU1M.func1.jump7: invalid relocation R_ADDR in fips data (SDATAFIPS)

And other failures I was seeing on Linux relating to `var RODATA`.
2026-03-15 23:36:56 +01:00
Daniel Martí 639946fded CI: always run all jobs even if one fails 2026-03-15 23:36:56 +01:00
Daniel Martí 5834e88b27 internal/literals: do not obfuscate literals in //go:nosplit funcs
This was causing a number of failures on GOOS=darwin in CI.

Thanks to LOVECHEN on GitHub for suggesting this.
2026-03-15 23:36:56 +01:00
Daniel Martí b0263759e9 CI: disable Windows for now
See: https://github.com/golang/go/issues/77975
2026-03-15 23:36:56 +01:00
Daniel Martí 7114403786 drop Go 1.25, support Go 1.26
Beyond the usual updating of version strings, re-running `go generate`,
and updating the patches for the linker, we needed extra changes.

First, the linker started being stricter about which packages
are allowed to linkname runtime names by import path.
Stop obfuscating import paths in runtimeAndLinknamed.

Second, the compiler's intrinsics code adds an "add" function
much like the existing "addF", so tweak the regular expression.

Third, note that there is a new runtimeAndDeps windows-only package,
which we find thanks to the recent improvement to cover many platforms.

Fourth, the code around magic numbers in the runtime has changed,
so rewrite the code which patches it via go/ast.

Fixes #990.
2026-03-15 23:36:56 +01:00
Daniel Martí 529ee19380 improve go_std_tables.go generation
Make runtimeAndLinknamed a set, like the other tables, for easy lookups
which will come in handy for Go 1.26 support.

Run `go list` across multiple platforms to ensure we cover all bases.
This again will help for Go 1.26 support, which introduces more
packages specific to MacOS and Windows.
2026-03-15 23:36:56 +01:00
Daniel Martí 41b45f9a9c windows/arm is gone for Go 1.26, use GOARCH=386 instead
We still test GOARCH=arm64 as part of darwin/arm64 below.
2026-03-15 23:36:56 +01:00
Daniel Martí 45b248860e include runtime/pprof in the stdimporter testscript
This starts causing a failure on Go 1.26, which we only capture
in another test via `go test` by accident, so make sure we cover it.
Without a fix and on go1.26, I now see:

    > exec garble build -o=main$exe ./stdimporter
    [stderr]
    # test/main/stdimporter
    link: x9BrC6t: invalid reference to runtime.pprof_goroutineLeakProfileWithLabels
2026-03-15 23:36:56 +01:00
Daniel Martí 3e26a41431 stop running diffoscope directly as part of bincmp
It can be rather slow on multi-megabyte binaries,
and its output can also be pretty massive at times.
Let the user run it separately on the files we store
to the bincmp_output directory.
2026-03-15 23:36:56 +01:00
Daniel Martí 6f2a1f8892 scripts: fix a sneaky bug in the compiler intrinsics generator
We were checking for "have we seen this package path before" via i!=0,
but this would always be true for the package unlucky enough
to be matched first in the file.

As such, we would end up not collecting the intrinsic funcs for this
unlucky first package correctly.

Fix the bug, and re-run the script, which finds two funcs we dropped.
Re-run it with go1.25.8 as well, although this causes no changes.
2026-03-15 11:07:51 +00:00
love 1630d09020 internal/ctrlflow: return errors instead of panicking on invalid directive params
Change getInt helper from panicking to returning an error when
encountering invalid or out-of-range control flow directive parameters.
This gives a clear error message to the user instead of a stack trace.
2026-03-05 13:14:26 +01:00
love a2ac681351 internal/linker: check copyFile close error
Check the return value of targetFile.Close() to catch delayed write
errors. The previous defer pattern silently discarded errors from
the final flush, which could hide data corruption on some filesystems.
2026-03-05 12:47:07 +01:00
love 9a4ecc2fff internal/linker: return error instead of panicking for unsupported patches
Return a descriptive error instead of panicking when loadLinkerPatches
encounters an unsupported patch type (new/delete/copy/rename). This
gives a clear error message to the user instead of a stack trace.
2026-03-05 12:46:23 +01:00
love ab88bbc70a reflect: fix comment typos
Fix "paramter" -> "parameter" (5 occurrences) and
"obfucated" -> "obfuscated" (2 occurrences).
2026-03-05 12:44:28 +01:00
love f3552a080c internal/literals: fix obfuscator selection bug and docstring
- nextLinearTimeObfuscator was indexing linearTimeObfuscators instead
  of linearTimePrimeObfuscators, making the prime variant unreachable.
- Fix copy-pasted docstring on byteLitWithExtKey (said "simple xor"
  but the function uses an extended key).
- Add comment explaining the XOR self-inverse mapping.
2026-03-05 12:43:43 +01:00
love 88d963720f main: warn when -seed exceeds 8 bytes
Only the first 8 bytes of the seed are used for deterministic
obfuscation. Warn the user via stderr when extra bytes are provided,
and remove the TODO that noted this was confusing.
2026-03-05 12:43:05 +01:00
love f7a10bf961 main: fix "oursrelves" typo in comment
Fix typo in splitFlagsFromFiles comment.
2026-03-05 12:42:24 +01:00
Daniel Martí 5c2470f075 clean up uses of encoding/gob and go/ssa
encoding/gob.Decoder already has "map copy" semantics;
any decoded map keys get inserted, and other existing map keys are left.
This is exactly the same behavior that the new code did with maps.Copy,
so the new code is unnecessary.

If there is any bug with this code, it needs to be shown via a test.
As far as I can tell, there is no bug, because no two packages
should clash in terms of map keys.

Add a helper to obtain a go/ssa.Function's origin if there is one,
falling back to itself if there is none.
This mimics how go/types.Func.Origin works,
and lets us simplify quite a bit of code.

Finally, fieldToStruct uses origin objects only,
so don't do a double lookup in reverse.go.

While here, fix up the func mentions in two type aliases.
2026-03-02 00:29:12 +01:00
Paul Scheduikat cb98b4daab fix: canonicalize generic field objects for struct hashing
Use `types.Var.Origin()` when mapping and hashing struct fields so selector and
declaration sites share the same canonical field identity. This fixes build
failures for generic aliases and named types over those aliases.

Fixes #924
2026-03-01 22:55:02 +00:00
Paul Scheduikat 1b6cc14d93 fix reflection propagation for generic template calls
Ensure reflected arguments keep field names when passing through generic helpers by propagating through ssa.ChangeType and normalizing generic API call handling.

Also merge dependency reflection metadata correctly from cache hits.

fixes #912
2026-03-01 22:55:02 +00:00
Paul Scheduikat 53c5f022b4 Make -debugdir also output the unobfuscated source tree
This makes the unobfuscated state easily accessible, for debugging.

Obfuscated code is output to: `./<debugdir>/garbled`
The original source code is output to:
`./<debugdir>/source`

Also enable caching for debugdir to allow for faster iteration.
2026-03-01 22:55:02 +00:00
Daniel Martí e693cd0632 stabilize generic type param hashing for anonymous struct fields
Avoid hashing free type parameters by object/name identity in type
hashing and use traversal-local canonical IDs instead.
This keeps obfuscated field names stable across alpha-renamed generic
signatures (e.g. T vs newT), fixing interface/method mismatches.

The added test case fails without the fix:

    > exec garble build
    [stderr]
    # test/main
    YiaBFlNH.go:48: cannot use bT6psGs5O (variable of type *QdwRyqV[aEqmF4M, zpotHfQdnNB]) as E_OKfdHoPH[zpotHfQdnNB] value in return statement: *QdwRyqV[aEqmF4M, zpotHfQdnNB] does not implement E_OKfdHoPH[zpotHfQdnNB] (wrong type for method Redirect)
    		have Redirect(struct{palTavb zpotHfQdnNB})
    		want Redirect(struct{g3N8A_R zpotHfQdnNB})

The fix is Paul's; this is rebased on an updated version of typeutil,
and the test is now part of typeparams.txtar and much smaller.

Fixes #991.

Co-authored-by: Paul Scheduikat <lu4p@pm.me>
2026-02-28 01:22:11 +01:00
Daniel Martí 6c956e00e5 re-bundle the latest version of typeutil.Hasher
And document how we do this and why.
2026-02-28 01:22:11 +01:00
Daniel Martí 799a451ce7 update x/tools and x/mod
Primarily to improve Go 1.25/1.26 support.
2026-02-28 01:22:11 +01:00
Daniel Martí 0e3374174b README: note that -literals can be reversed 2025-12-23 18:41:14 +01:00
Daniel Martí 65ffaa0efb stop ignoring GODEBUG when -tiny is used
GODEBUG started being used for configuring the behavior of the Go
toolchain and standard library, for the sake of smoother transitions
in terms of backwards and forwards compatibility.
See: https://go.dev/doc/godebug

As such, it is not right to have `garble build -tiny` ignore all
GODEBUG settings, because many GODEBUG keys nowadays do not actually
involve debugging what a Go binary is doing.

Moreover, the mechanism we were using broke with Go 1.25.2,
which refactored `func parsedebugvars()` into
`func parseRuntimeDebugVars(godebug string)`,
so our test started breaking as our runtime patching was broken.
2025-10-18 06:42:52 +02:00
Daniel Martí 37e582d581 support Go versions with X: suffixes for GOEXPERIMENTs
A workaround until https://github.com/golang/go/issues/75953 is fixed.

See #978.
2025-10-18 06:42:52 +02:00
Daniel Martí 15a385283b reject invalid Go toolchain versions early
Otherwise we might do odd things, such as trying to open the linker
patches directory "patches/" given that go/version.Lang
returns an empty string for invalid versions.

See #978.
2025-10-18 06:42:52 +02:00
Daniel Martí 9d7c84b0c6 parse go env GOVERSION with go/version directly
We don't need to use a regular expression to find "goN.M" anymore,
as go/version seems to deal with "devel" versions from master just fine.
We can then also stop having two separate fields for the version
of the Go toolchain currently being used.
2025-10-18 06:42:52 +02:00