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

revised reflection handling #889

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
84 changes: 49 additions & 35 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,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 +786,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 +902,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 +949,39 @@ 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) {
mainPackage := 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

if lpkg.ImportPath == "internal/abi" && filepath.Base(path) == "type.go" {
src, err = abiNamePatch(path)
if err != nil {
return nil, err
}
} else if mainPackage && reflectPatchFile == "" {
src, err = reflectMainPrePatch(path)
if err != nil {
return nil, err
}

reflectPatchFile = path
}

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

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

files = append(files, file)
}
return files, nil
Expand All @@ -972,7 +995,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 @@ -1085,6 +1108,10 @@ func (tf *transformer) transformCompile(args []string) ([]string, error) {
return nil, err
}

if tf.curPkg.Name == "main" && strings.HasSuffix(reflectPatchFile, basename) {
src = reflectMainPostPatch(src, tf.curPkg, tf.curPkgCache)
}

// 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 +1167,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 +1235,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 +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 @@ -1405,14 +1429,9 @@ type pkgCache struct {
// unless we were smart enough to detect which arguments get used as %#v or %T.
ReflectAPIs map[funcFullName]map[int]bool

// ReflectObjects is filled with the fully qualified names from each
// package that we cannot obfuscate due to reflection.
// The included objects are named types and their fields,
// since it is those names being obfuscated that could break the use of reflect.
//
// 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{}
// ReflectObjectNames maps obfuscated names which are reflected to their "real"
// non-obfuscated names.
ReflectObjectNames 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 All @@ -1425,7 +1444,7 @@ type pkgCache struct {

func (c *pkgCache) CopyFrom(c2 pkgCache) {
maps.Copy(c.ReflectAPIs, c2.ReflectAPIs)
maps.Copy(c.ReflectObjects, c2.ReflectObjects)
maps.Copy(c.ReflectObjectNames, c2.ReflectObjectNames)
maps.Copy(c.EmbeddedAliasFields, c2.EmbeddedAliasFields)
}

Expand Down Expand Up @@ -1497,7 +1516,7 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa
"reflect.TypeOf": {0: true},
"reflect.ValueOf": {0: true},
},
ReflectObjects: map[objectString]struct{}{},
ReflectObjectNames: map[objectString]string{},
EmbeddedAliasFields: map[objectString]typeName{},
}
for _, imp := range lpkg.Imports {
Expand Down Expand Up @@ -1530,7 +1549,7 @@ 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 Down Expand Up @@ -1997,11 +2016,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 +2085,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 +2206,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
2 changes: 1 addition & 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 Down
Loading