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, #799, #817, #881, #858, #843, #842

Closes #406
  • Loading branch information
lu4p committed Nov 26, 2024
1 parent 515358b commit 7eb700c
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 104 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
107 changes: 71 additions & 36 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,16 +948,43 @@ 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(lpkg *listedPackage, dir string, paths []string) (files []*ast.File, err error) {
main := lpkg.Name == "main" && lpkg.ForTest == ""

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

abiFileName := filepath.Join("internal", "abi", "type.go")
if strings.HasSuffix(path, abiFileName) {
src, err = abiNamePatch(path)
if err != nil {
return nil, err
}
} else if main {
data, err := reflectMainPrePatch(path)
if err != nil {
return nil, err
}

if data != nil {
// directly assigning a nil []byte to src changes the type to []byte
src = data
}
}

file, err := parser.ParseFile(fset, path, src, parser.SkipObjectResolution|parser.ParseComments)
if err != nil {
return nil, err
}

if main && src != nil {
astutil.AddNamedImport(fset, file, "_", "unsafe")
}

files = append(files, file)
}
return files, nil
Expand All @@ -972,7 +998,7 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) {
flags = append(flags, "-dwarf=false")

// The Go file paths given to the compiler are always absolute paths.
files, err := parseFiles("", paths)
files, err := parseFiles(tf.curPkg, "", paths)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1026,8 +1052,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 +1115,10 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) {
return nil, err
}

if tf.curPkg.Name == "main" && strings.HasSuffix(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 +1174,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 @@ -1208,29 +1242,26 @@ func (tf *transformer) transformLinkname(localName, newName string) (string, str

var newForeignName string
if receiver, name, ok := strings.Cut(foreignName, "."); ok {
if lpkg.ImportPath == "reflect" && (receiver == "(*rtype)" || receiver == "Value") {
// These receivers are not obfuscated.
// See the TODO below.
} else if strings.HasPrefix(receiver, "(*") {
if strings.HasPrefix(receiver, "(*") {
// 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 +1339,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 +1443,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 +1491,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 @@ -1479,10 +1510,16 @@ func loadPkgCache(lpkg *listedPackage, pkg *types.Package, files []*ast.File, in
}
return loaded, nil
}
return computePkgCache(fsCache, lpkg, pkg, files, info, ssaPkg)

return pkgCache{}, fmt.Errorf("pkg: %s not cached yet", lpkg.ImportPath)
}

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 +1534,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 @@ -1530,7 +1567,8 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa
// Missing or corrupted entry in the cache for a dependency.
// Could happen if GARBLE_CACHE was emptied but GOCACHE was not.
// Compute it, which can recurse if many entries are missing.
files, err := parseFiles(lpkg.Dir, lpkg.CompiledGoFiles)

files, err := parseFiles(lpkg, lpkg.Dir, lpkg.CompiledGoFiles)
if err != nil {
return err
}
Expand All @@ -1539,7 +1577,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 +1628,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 +2037,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 +2106,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 +2227,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 7eb700c

Please sign in to comment.