Field names are obfuscated with the identity of their containing struct as
a salt, which must be stable across packages so that converting between
identical struct types keeps working. Hashing field types into that salt
breaks for an anonymous struct{F Q} returned by a generic function: the
declaring package hashes the type parameter Q while a consuming package only
sees an instantiation such as struct{F int}, with no origin to recover Q.
The previous commit fixed this for a named generic field type, but not for
a bare type parameter.
Drop field types and fold in the field position instead, which is stable
across instantiations and conversions yet still keeps reordered structs
distinct. This only makes more fields share a salt, which is harmless as
non-identical structs are never converted between one another.
Since field types are no longer hashed, the salt never recurses into the
rest of the type graph, so the type-argument NOTE(garble) tweak to the
*types.Named case added by the previous commit is now dead code; revert it
to keep the bundled hasher faithful to upstream.
Field names are salted with the identity of their containing struct, and
the salt must be stable across instantiations. For named generic types we
recover the generic origin, but an anonymous struct returned by a generic
function, e.g. struct{F G[T]}, carries no origin info: the declaring
package hashes it with the type parameter T, while another package only
sees the instantiated struct{F G[int]}. Hashing the type arguments then
gave the field two different names and a broken build.
Drop type arguments from the field-name salt to keep it stable. This only
makes more fields share a salt, which is harmless as non-identical structs
are never converted between one another.
Thanks to David Roizenman for providing the test case.
Fixes#1041.
Shorten the usage text to match the other commands.
Return error values consistently between $BROWSER and pkg/browser.
Use slices.Sorted with maps.Keys for brevity.
Simplify the testscript, as we don't need to test basic CLI stuff.
Implements the feature requested in #219: `garble bug` opens the browser
on the garble issue tracker with a pre-filled bug report, including the
output of `garble version` and a diagnostic subset of `go env`.
Fixes#219.
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.
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>
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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`.
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.
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.
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
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.
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.
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.
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.
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.
- 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.
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.
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.
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
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
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.
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>