Skip to content

Commit

Permalink
revised reflection handling
Browse files Browse the repository at this point in the history
Instead of excluding specific names from obfuscation "all" names are now  obfuscated.

For reflected names, a mapping to the original name is injected in internal/abi to resolve them correctly.

Fixes #884, and probably most issues caused by selectively not obfuscating certain names.
  • Loading branch information
lu4p committed Nov 24, 2024
1 parent 515358b commit 3049f00
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 97 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/garble
/test
/bincmp_output/
debug
2 changes: 1 addition & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Daniel Martí <[email protected]>
Emmanuel Chee-zaram Okeke <[email protected]>
NHAS <[email protected]>
Nicholas Jones <[email protected]>
Paul Scheduikat <[email protected]>
Zachary Wasserman <[email protected]>
lu4p <[email protected]>
pagran <[email protected]>
shellhazard <[email protected]>
xuannv <[email protected]>
16 changes: 0 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,23 +157,7 @@ to document the current shortcomings of this tool.
be required by interfaces. This area is a work in progress; see
[#3](https://github.com/burrowers/garble/issues/3).

* Garble automatically detects which Go types are used with reflection
to avoid obfuscating them, as that might break your program.
Note that Garble obfuscates [one package at a time](#speed),
so if your reflection code inspects a type from an imported package,
you may need to add a "hint" in the imported package to exclude obfuscating it:
```go
type Message struct {
Command string
Args string
}

// Never obfuscate the Message type.
var _ = reflect.TypeOf(Message{})
```

* Aside from `GOGARBLE` to select patterns of packages to obfuscate,
and the hint above with `reflect.TypeOf` to exclude obfuscating particular types,
there is no supported way to exclude obfuscating a selection of files or packages.
More often than not, a user would want to do this to work around a bug; please file the bug instead.

Expand Down
18 changes: 4 additions & 14 deletions hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,7 @@ func entryOffKey() uint32 {
return runtimeHashWithCustomSalt([]byte("entryOffKey"))
}

func hashWithPackage(tf *transformer, pkg *listedPackage, name string) string {

// In some places it is not appropriate to access the transformer
if tf != nil {
// If the package is marked as "in-use" by reflection, the private structures are not obfuscated, so dont return them as a hash. Fixes #882
if _, ok := tf.curPkgCache.ReflectObjects[pkg.ImportPath+"."+name]; ok {
return name
}
}
func hashWithPackage(pkg *listedPackage, name string) string {
// If the user provided us with an obfuscation seed,
// we use that with the package import path directly..
// Otherwise, we use GarbleActionID as a fallback salt.
Expand Down Expand Up @@ -412,11 +404,9 @@ func hashWithCustomSalt(salt []byte, name string) string {
// Turn "afoo" into "Afoo".
b64Name[0] = toUpper(b64Name[0])
}
} else {
if isUpper(b64Name[0]) {
// Turn "Afoo" into "afoo".
b64Name[0] = toLower(b64Name[0])
}
} else if isUpper(b64Name[0]) {
// Turn "Afoo" into "afoo".
b64Name[0] = toLower(b64Name[0])
}
}
return string(b64Name)
Expand Down
87 changes: 57 additions & 30 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,11 +606,10 @@ This command wraps "go %s". Below is its help:
return nil, err
}

sharedTempDir, err = saveSharedCache()
if err != nil {
if err := saveSharedCache(); err != nil {
return nil, err
}
os.Setenv("GARBLE_SHARED", sharedTempDir)

wd, err := os.Getwd()
if err != nil {
return nil, err
Expand Down Expand Up @@ -696,7 +695,7 @@ func (tf *transformer) transformAsm(args []string) ([]string, error) {
newPaths := make([]string, 0, len(paths))
if !slices.Contains(args, "-gensymabis") {
for _, path := range paths {
name := hashWithPackage(tf, tf.curPkg, filepath.Base(path)) + ".s"
name := hashWithPackage(tf.curPkg, filepath.Base(path)) + ".s"
pkgDir := filepath.Join(sharedTempDir, tf.curPkg.obfuscatedImportPath())
newPath := filepath.Join(pkgDir, name)
newPaths = append(newPaths, newPath)
Expand Down Expand Up @@ -786,7 +785,7 @@ func (tf *transformer) transformAsm(args []string) ([]string, error) {
// directory, as assembly files do not support `/*line` directives.
// TODO(mvdan): per cmd/asm/internal/lex, they do support `#line`.
basename := filepath.Base(path)
newName := hashWithPackage(tf, tf.curPkg, basename) + ".s"
newName := hashWithPackage(tf.curPkg, basename) + ".s"
if path, err := tf.writeSourceFile(basename, newName, buf.Bytes()); err != nil {
return nil, err
} else {
Expand Down Expand Up @@ -902,7 +901,7 @@ func (tf *transformer) replaceAsmNames(buf *bytes.Buffer, remaining []byte) {
remaining = remaining[nameEnd:]

if lpkg.ToObfuscate && !compilerIntrinsics[lpkg.ImportPath][name] {
newName := hashWithPackage(tf, lpkg, name)
newName := hashWithPackage(lpkg, name)
if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed
log.Printf("asm name %q hashed with %x to %q", name, tf.curPkg.GarbleActionID, newName)
}
Expand Down Expand Up @@ -949,13 +948,21 @@ func (tf *transformer) writeSourceFile(basename, obfuscated string, content []by
// parseFiles parses a list of Go files.
// It supports relative file paths, such as those found in listedPackage.CompiledGoFiles,
// as long as dir is set to listedPackage.Dir.
func parseFiles(dir string, paths []string) ([]*ast.File, error) {
var files []*ast.File
func parseFiles(dir string, paths []string) (files []*ast.File, err error) {
for _, path := range paths {
if !filepath.IsAbs(path) {
path = filepath.Join(dir, path)
}
file, err := parser.ParseFile(fset, path, nil, parser.SkipObjectResolution|parser.ParseComments)

var src any
if strings.HasSuffix(path, "internal/abi/type.go") {
src, err = abiNamePatch(path)
if err != nil {
return nil, err
}
}

file, err := parser.ParseFile(fset, path, src, parser.SkipObjectResolution|parser.ParseComments)
if err != nil {
return nil, err
}
Expand All @@ -971,6 +978,15 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) {
// generating it.
flags = append(flags, "-dwarf=false")

if tf.curPkg.Name == "main" {
path, err := reflectMainPrePatch("")
if err != nil {
return nil, err
}

paths = append(paths, path)
}

// The Go file paths given to the compiler are always absolute paths.
files, err := parseFiles("", paths)
if err != nil {
Expand Down Expand Up @@ -1026,8 +1042,12 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) {
}
}

if tf.curPkgCache, err = loadPkgCache(tf.curPkg, tf.pkg, files, tf.info, ssaPkg); err != nil {
return nil, err
tf.curPkgCache, err = loadPkgCache(tf.curPkg)
if err != nil {
tf.curPkgCache, err = computePkgCache(tf.curPkg, tf.pkg, files, tf.info, ssaPkg)
if err != nil {
return nil, err
}
}

// These maps are not kept in pkgCache, since they are only needed to obfuscate curPkg.
Expand Down Expand Up @@ -1085,6 +1105,10 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) {
return nil, err
}

if tf.curPkg.Name == "main" && patchMainFile == basename {
src = reflectMainPostPatch(src, tf.curPkg)
}

// We hide Go source filenames via "//line" directives,
// so there is no need to use obfuscated filenames here.
if path, err := tf.writeSourceFile(basename, basename, src); err != nil {
Expand Down Expand Up @@ -1140,7 +1164,7 @@ func (tf *transformer) transformDirectives(comments []*ast.CommentGroup) {
func (tf *transformer) transformLinkname(localName, newName string) (string, string) {
// obfuscate the local name, if the current package is obfuscated
if tf.curPkg.ToObfuscate && !compilerIntrinsics[tf.curPkg.ImportPath][localName] {
localName = hashWithPackage(tf, tf.curPkg, localName)
localName = hashWithPackage(tf.curPkg, localName)
}
if newName == "" {
return localName, ""
Expand Down Expand Up @@ -1215,22 +1239,22 @@ func (tf *transformer) transformLinkname(localName, newName string) (string, str
// pkg/path.(*Receiver).method
receiver = strings.TrimPrefix(receiver, "(*")
receiver = strings.TrimSuffix(receiver, ")")
receiver = "(*" + hashWithPackage(tf, lpkg, receiver) + ")"
receiver = "(*" + hashWithPackage(lpkg, receiver) + ")"
} else {
// pkg/path.Receiver.method
receiver = hashWithPackage(tf, lpkg, receiver)
receiver = hashWithPackage(lpkg, receiver)
}
// Exported methods are never obfuscated.
//
// TODO(mvdan): We're duplicating the logic behind these decisions.
// Reuse the logic with transformCompile.
if !token.IsExported(name) {
name = hashWithPackage(tf, lpkg, name)
name = hashWithPackage(lpkg, name)
}
newForeignName = receiver + "." + name
} else {
// pkg/path.function
newForeignName = hashWithPackage(tf, lpkg, foreignName)
newForeignName = hashWithPackage(lpkg, foreignName)
}

newPkgPath := lpkg.ImportPath
Expand Down Expand Up @@ -1308,7 +1332,7 @@ func (tf *transformer) processImportCfg(flags []string, requiredPkgs []string) (
// For beforePath="vendor/foo", afterPath and
// lpkg.ImportPath can be just "foo".
// Don't use obfuscatedImportPath here.
beforePath = hashWithPackage(tf, lpkg, beforePath)
beforePath = hashWithPackage(lpkg, beforePath)

afterPath = lpkg.obfuscatedImportPath()
}
Expand Down Expand Up @@ -1412,7 +1436,7 @@ type pkgCache struct {
//
// This record is necessary for knowing what names from imported packages
// weren't obfuscated, so we can obfuscate their local uses accordingly.
ReflectObjects map[objectString]struct{}
ReflectObjects map[objectString]string

// EmbeddedAliasFields records which embedded fields use a type alias.
// They are the only instance where a type alias matters for obfuscation,
Expand Down Expand Up @@ -1460,7 +1484,7 @@ func openCache() (*cache.Cache, error) {
return cache.Open(dir)
}

func loadPkgCache(lpkg *listedPackage, pkg *types.Package, files []*ast.File, info *types.Info, ssaPkg *ssa.Package) (pkgCache, error) {
func loadPkgCache(lpkg *listedPackage) (pkgCache, error) {
fsCache, err := openCache()
if err != nil {
return pkgCache{}, err
Expand All @@ -1477,12 +1501,18 @@ func loadPkgCache(lpkg *listedPackage, pkg *types.Package, files []*ast.File, in
if err := gob.NewDecoder(f).Decode(&loaded); err != nil {
return pkgCache{}, fmt.Errorf("gob decode: %w", err)
}

return loaded, nil
}
return computePkgCache(fsCache, lpkg, pkg, files, info, ssaPkg)

return pkgCache{}, fmt.Errorf("pkg not cached yet")
}

func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Package, files []*ast.File, info *types.Info, ssaPkg *ssa.Package) (pkgCache, error) {
func computePkgCache(lpkg *listedPackage, pkg *types.Package, files []*ast.File, info *types.Info, ssaPkg *ssa.Package) (pkgCache, error) {
fsCache, err := openCache()
if err != nil {
return pkgCache{}, err
}
// Not yet in the cache. Load the cache entries for all direct dependencies,
// build our cache entry, and write it to disk.
// Note that practically all errors from Cache.GetFile are a cache miss;
Expand All @@ -1497,7 +1527,7 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa
"reflect.TypeOf": {0: true},
"reflect.ValueOf": {0: true},
},
ReflectObjects: map[objectString]struct{}{},
ReflectObjects: map[objectString]string{},
EmbeddedAliasFields: map[objectString]typeName{},
}
for _, imp := range lpkg.Imports {
Expand Down Expand Up @@ -1539,7 +1569,7 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa
if err != nil {
return err
}
computedImp, err := computePkgCache(fsCache, lpkg, pkg, files, info, nil)
computedImp, err := computePkgCache(lpkg, pkg, files, info, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -1590,9 +1620,11 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa
if err := gob.NewEncoder(&buf).Encode(computed); err != nil {
return pkgCache{}, err
}

if err := fsCache.PutBytes(lpkg.GarbleActionID, buf.Bytes()); err != nil {
return pkgCache{}, err
}

return computed, nil
}

Expand Down Expand Up @@ -1997,11 +2029,6 @@ func (tf *transformer) transformGoFile(file *ast.File) *ast.File {
}
}

// The package that declared this object did not obfuscate it.
if usedForReflect(tf.curPkgCache, obj) {
return true
}

lpkg, err := listPackage(tf.curPkg, path)
if err != nil {
panic(err) // shouldn't happen
Expand Down Expand Up @@ -2071,7 +2098,7 @@ func (tf *transformer) transformGoFile(file *ast.File) *ast.File {
return true // we only want to rename the above
}

node.Name = hashWithPackage(tf, lpkg, name)
node.Name = hashWithPackage(lpkg, name)
// TODO: probably move the debugf lines inside the hash funcs
if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed
log.Printf("%s %q hashed with %x… to %q", debugName, name, hashToUse[:4], node.Name)
Expand Down Expand Up @@ -2192,7 +2219,7 @@ func (tf *transformer) transformLink(args []string) ([]string, error) {
if path != "main" {
newPath = lpkg.obfuscatedImportPath()
}
newName := hashWithPackage(tf, lpkg, name)
newName := hashWithPackage(lpkg, name)
flags = append(flags, fmt.Sprintf("-X=%s.%s=%s", newPath, newName, stringValue))
})

Expand Down
3 changes: 2 additions & 1 deletion position.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func printFile(lpkg *listedPackage, file *ast.File) ([]byte, error) {
newName := ""
if !flagTiny {
origPos := fmt.Sprintf("%s:%d", filename, origOffset)
newName = hashWithPackage(nil, lpkg, origPos) + ".go"
newName = hashWithPackage(lpkg, origPos) + ".go"
// log.Printf("%q hashed with %x to %q", origPos, curPkg.GarbleActionID, newName)
}

Expand All @@ -144,4 +144,5 @@ func printFile(lpkg *listedPackage, file *ast.File) ([]byte, error) {
fmt.Fprintf(&printBuf2, " /*line %s%s:1*/ ", newPrefix, newName)
}
}

}
Loading

0 comments on commit 3049f00

Please sign in to comment.