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

Be smarter about which methods can have their names changed #3

Open
mvdan opened this issue Dec 9, 2019 · 19 comments
Open

Be smarter about which methods can have their names changed #3

mvdan opened this issue Dec 9, 2019 · 19 comments
Labels
enhancement New feature or request

Comments

@mvdan
Copy link
Member

mvdan commented Dec 9, 2019

Changing all method names will break some programs, as method names are necessary to implement interfaces.

On the otherhand, not touching any method names exposes far too much information. It's likely that only a minority of methods actually implement interfaces.

Of course, this is a difficult problem to solve, because for a library package it's impossible to tell which methods are meant to implement which interfaces. Perhaps it's only a package that imports our library which uses a type as an interface with methods, since a type doesn't have to be upfront about what interfaces it implements.

We implemented a conservative version of this algorithm for https://github.com/mvdan/unparam, so perhaps we can reuse it.

Right now, we grable method names only if they are unexported, which is a reasonable shortcut until we have a better mechanism.

@capnspacehook
Copy link
Member

capnspacehook commented Jul 5, 2020

We implemented a conservative version of this algorithm for https://github.com/mvdan/unparam, so perhaps we can reuse it.

What do you mean by saying the algorithm is conservative?

I think that interfaces defined in packages that we are actively garbling (ie in GOPRIVATE) and methods also in the same packages can be safely garbled, no?

@mvdan
Copy link
Member Author

mvdan commented Jul 5, 2020

unparam can only suggest unused parameters if those are safe to remove. A parameter isn't safe to remove if it's useful to implement an interface.

For better or worse, implementing interfaces in Go is entirely implicit, not explicit. So you could have something like https://play.golang.org/p/L3h9n74N6ax. Both unparam and garble work on a package-by-package basis, so when you're looking at the foo sub-package, you don't know that a dependent might require an exported method to satisfy an arbitrary interface.

unparam takes a form of middle ground, if I recall correctly. We only consider that methods need to implement interfaces which are used explicitly. For example, when a variable of a type is assigned to an interface type, or passed as an interface parameter, or otherwise converted to an interface type. Take a look at https://github.com/mvdan/unparam/blob/b37ab49443f75cf3121a1c153ca02be64ecdec07/check/check.go#L399-L411 and the places where we call it.

garble has a very similar situation. Unfortunately, I think we have a bigger problem here. unparam only complains about unused parameters, which are pretty rare, so the chances of it breaking such implicit interface implementations are slim. However, garble will attempt to rename all methods, so the playground example I gave above could break for sure.

@mvdan
Copy link
Member Author

mvdan commented Jul 5, 2020

I think that interfaces defined in packages that we are actively garbling (ie in GOPRIVATE) and methods also in the same packages can be safely garbled, no?

To answer your question - it depends. If both the interface and methods are unexported, and the interface isn't used for anything else, they could be garbled together. Otherwise, it's possible you're breaking something, like in the situation in the playground.

The problem is that we rely on the package that defines a name to provide a salt for hashing the name. Interface method names are global, not package-scoped, so the same kind of hashing just doesn't work.

Maybe I should rethink the entire feature. Maybe exported method names should be garbled with a truly global salt, not a per-package one.

@capnspacehook
Copy link
Member

I think a global salt would be beneficial for sure if it would let us garble more exported method names, which are pretty common in Go. It may also be helpful to change Garble's logic to gather all the imported/dependent packages and build a list of their interfaces so we can more intelligently decide if it's safe to garble exported methods.

@mvdan
Copy link
Member Author

mvdan commented Jul 5, 2020

A global salt does solve the scenario I shared in the playground, which I think will be pretty common. The only problem remaining is interfaces which are garbled where the types implementing the interface are not, and vice versa. Imagine one defining a String() string method while fmt.Stringer isn't garbled, or imagine one defining an interface implemented by some types in std which are not being garbled.

One possible way to fix this would be to always garble everything, so all method names would be garbled with the global salt. I would like to avoid that, though, because it sets a bad precedent. I would like to imagine that, in the future, we could have build caching which would mean we could avoid re-building any packages outside GOPRIVATE.

It may also be helpful to change Garble's logic to gather all the imported/dependent packages and build a list of their interfaces so we can more intelligently decide if it's safe to garble exported methods.

You mean look at all the packages in a build at once? We could do that (i.e. whole-program analysis), but it would be pretty expensive. It would also be pretty unfortunate, because we would lose the deterministic build property on a per-package basis. If garble builds the same package with the same set of dependencies, the output should always be the same, no matter what packages end up importing the original package.

If you mean just looking at a package's dependencies and all the interfaces they define, that would indeed be possible. That would be along the lines of the analysis that unparam does. We wouldn't cover the playground scenario, but I think that's OK - we could just rely on the developer to provide extra hints, like with reflect.

@mvdan
Copy link
Member Author

mvdan commented Jul 8, 2020

If you mean just looking at a package's dependencies and all the interfaces they define, that would indeed be possible. That would be along the lines of the analysis that unparam does.

Here's this idea, but developed a bit further:

  1. For each package we're garbling, recursively look at the package and all of its imports via go/types.
  2. For each exported interface we come across, add each of its method names to a "method name blacklist".
  3. When garbling methods, only garble those which are not in the blacklist.
  4. To garble exported method names, use a global salt instead of a per-package one.

This would be very conservative, but I think it would be a good starting point. If the interface were to be defined in an importer package (like in the playground link), I think point 4 would save us; both methods would be hashed with the same global salt. The only extra rule we'd have to enforce is that a non-garbled package can't import a garbled package. I think this is a rule/limitation we have already, just not well documented.

This strategy could be improved in the future, with ideas such as:

A) Build the blacklist via method name + method signature, instead of just the name, as those are the rules for interfaces
B) Don't add interface methods to the blacklist if the package where the interface is defined will be garbled (since we can just garble those methods too, with the global salt)
C) Always garble methods which use parameters of unexported types, since those will almost never be usable by other packages. Such methods can be considered unexported in practice.

@mvdan mvdan added the enhancement New feature or request label Mar 5, 2021
@mvdan
Copy link
Member Author

mvdan commented Mar 7, 2021

B) Don't add interface methods to the blacklist if the package where the interface is defined will be garbled (since we can just garble those methods too, with the global salt)

Mental note: this can break if an interface is defined in a private package, but satisfied by types in dependencies which are public. We would obfuscate the method name in the interface, but not the implementer types, so the program would fail to compile.

@mvdan
Copy link
Member Author

mvdan commented Apr 25, 2021

Assuming that soon enough we will be obfuscating all packages always (#276 (comment)), this gets a lot easier. We simply need to obfuscate method names in a way that won't change across packages, similar to what is being done for #310.

For example, we could use the function signature to hash the name with; that way, func (...) fooBar() would result in a different hash than func (...) fooBar(n int), for example. This would still be fine for interfaces, as they require the name and signature to match.

@pmaene
Copy link

pmaene commented Mar 15, 2022

First of, I've been following this work for a while and I'm very impressed by the progress that's been made! Thank you! I'm sorry for digging up such an old issue, but I was curious whether obfuscating interface methods is still on your roadmap?

@mvdan
Copy link
Member Author

mvdan commented Mar 15, 2022

No need to apologise :) Obfuscating most interface methods is still on the roadmap, roughly following #3 (comment). Right now we can't quite do that, because we don't support obfuscating all packages yet. #193 is one of the blockers, but there are still some third party packages that we fail to obfuscate as well, for which #240 should help.

We will also have to worry about reflection, because of https://pkg.go.dev/reflect#Value.MethodByName. However, that should be very similar to what we already do with exported fields, so I'm optimistically not too worried.

@pmaene
Copy link

pmaene commented Mar 15, 2022

Thanks a lot for the quick response! Great to hear that this is still on your mind, and we'll be the first to try this out once it lands ;).

@mvdan
Copy link
Member Author

mvdan commented Mar 15, 2022

Sounds good. Just don't expect immediate progress in a matter of weeks, because even though we're significantly closer than we were a year ago, there's still work to be done.

@pmaene
Copy link

pmaene commented Mar 15, 2022

Of course, we completely understand this is a complex issue. We'll continue keeping an eye on the progress. Thanks again!

@pmaene
Copy link

pmaene commented Mar 14, 2023

I've been keeping an eye on garble since and it's been great to see development continue! However, the reason I'm digging up this issue is because I was wondering whether there are still plans to implement interface method obfuscation? Thanks a lot!

@pmaene
Copy link

pmaene commented May 17, 2023

Sorry for bumping this issue again, but I was curious whether obfuscation of exported methods is still on your roadmap?

@mvdan
Copy link
Member Author

mvdan commented May 17, 2023

Yes it is; the issue remains open. It's just blocked by us not obfuscating the entire standard library yet.

@pmaene
Copy link

pmaene commented May 17, 2023

Thanks a lot for your quick response!

@DragonHunter2024
Copy link

Thank you.
This function is not interface function.
But, it's not obfuscated.
func (c *SSLclient) Handshake() bool {}

@zandercodes

This comment was marked as spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Development

No branches or pull requests

5 participants