Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support fetching original package names and import paths via reflection #904

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

mvdan
Copy link
Member

@mvdan mvdan commented Jan 4, 2025

(see commit messages - please do not squash)

Updates #849.

mvdan added 6 commits January 4, 2025 22:23
This function returns obfuscated names, so use that as its name.
Moreover, some of the callers still called the result "objStr",
which misled me into thinking the string was a unique object path.

Leave a TODO behind about using go/types/objectpath too.
Our own recordedObjectString is sort of similar, but not as principled.
Only two callers did pass nil, and there's no reason for them to do so.
They should be the ones to check that typeToObj did not return nil.
There's no need to reach for sharedCache.ListedPackages
when the caller is computePkgCache, which already has the lpkg value.

While here, compare *go/types.Package pointers directly
rather than via their import path strings.
Reflection can show package names alone via reflect.Type.String,
and I'm sure they are available in other ways.
We were obfuscating "package p" exactly like the import path "foo.com/p"
which worked OK for the most part, but not for reflection.

This is also a slight improvement to the quality of the obfuscation,
given that package names and import paths will no longer be aligned
when obfuscated.
obfuscatedImportPath already handled ToObfuscate, so the callers
don't have to do that as well. Handle main packages too, whose logic
was sprinkled and repeated throughout the project.
Just like we support fetching original type, field, and method names
via reflection to ensure that no Go package is broken by garble,
do the same for package names and import paths, which are reachable too.

This means we can remove the test function printfWithoutPackage,
which in hindsight should have been a clue as we were working around
a bug in garble.

Updates burrowers#849.
Copy link
Member

@lu4p lu4p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR is too aggressive at including import paths.

In your last commit message explain that we're doing this to support "wails" builds or things like it.

Comment on lines +49 to +56
// At least one type in this package is used with reflection;
// the package name and import path are reachable via reflection as well.
// Note that package main is compiled as having import path "main" as well.
ri.result.ReflectObjectNames[ri.lpkg.obfuscatedPackageName()] = ri.lpkg.Name
if ri.lpkg.Name != "main" {
ri.result.ReflectObjectNames[ri.lpkg.obfuscatedImportPath()] = ri.lpkg.ImportPath
}

Copy link
Member

@lu4p lu4p Jan 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is too broad, reflection is very common I expect this would exclude most import paths of real world packages from obfuscation.

Maybe instead hide this behavior behind a flag like -includePaths, or make it opt-in on a package level via a magic comment/ constant. Or maybe even add the ability to exclude specific packages from obfuscation via the inverse of GOGARBLE.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this would lower the quality of obfuscation in some cases, but I strongly believe that the default behavior should be for any Go package to work out of the box. Stronger obfuscation which may break some code should be opt-in, not opt-out. That is better UX - stronger obfuscation is a bit of a power user feature - and should also prevent us from getting dozens more "garble breaks with package X" issues filed when the problem tends to be related to reflection.

So, yes, we can and should add some way to opt into stronger obfuscation even when reflection is used. I would suggest that we tackle that in a follow-up PR because it would be a slightly involved change. I lean towards some sort of directive comment like you suggest.

Copy link
Member

@lu4p lu4p Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is very similar to giving up on import path obfuscation entirely. I also think that import path obfuscation is one of the most important things we do.

Other than wails I think there has been no one who's programs break due to import path obfuscation, so I think this is a rare edge case and should be opt-in.

Comment on lines -470 to -472
if obj == nil {
return ""
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the obfuscated name of a nil obj is "" is fine. I don't see a benefit of the callers having to make sure obj is non-nil.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a big deal either way, but I personally disagree - a nil obj is an edge case that we should break out of as soon as reasonable, rather than keep going down the logic. All other callers already ensure that obj is non-nil, it was just two that were careless. If any code path ends up with a nil obj, that's possibly a bug that we need to investigate and fix - rather than silently assuming that the result is an empty string.

Comment on lines -192 to +201
if usedForReflect(ri.result, obj) {
if obj != nil && ri.usedForReflect(ri.result, obj) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

Comment on lines -198 to +207
if usedForReflect(ri.result, obj) {
if obj != nil && ri.usedForReflect(ri.result, obj) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

! binsubstr main$exe 'garble_main.go' 'test/main' 'importedpkg.' 'DownstreamObfuscated' 'SiblingObfuscated' 'IndirectObfuscated' 'IndirectNamedWithoutReflect' 'AliasIndirectNamedWithReflect' 'AliasIndirectNamedWithoutReflect' 'FmtTypeField' 'LocalObfuscated'
! binsubstr main$exe 'garble_main.go' 'DownstreamObfuscated' 'SiblingObfuscated' 'IndirectObfuscated' 'IndirectNamedWithoutReflect' 'AliasIndirectNamedWithReflect' 'AliasIndirectNamedWithoutReflect' 'FmtTypeField' 'LocalObfuscated'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see importedpkg replaced by indirect to check that only certain import paths are excluded from obfuscation. Also check that importedpkg can be found in the binary with binsubstr.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it's a fair point - I should update this PR to ensure that package import paths unrelated to reflection are excluded from the binary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that once this is opt-in you show that one packages import path is obfuscated while another's is not although both packages use reflection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants