diff --git a/.gitignore b/.gitignore index dc5a9738..5f6d9263 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /garble /test /bincmp_output/ +debug diff --git a/AUTHORS b/AUTHORS index f7bd6582..2a402795 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,8 +11,8 @@ Daniel Martí Emmanuel Chee-zaram Okeke NHAS Nicholas Jones +Paul Scheduikat Zachary Wasserman -lu4p pagran shellhazard xuannv diff --git a/README.md b/README.md index 4cfc0bb9..51fc019b 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/hash.go b/hash.go index bcf442db..0b0357e0 100644 --- a/hash.go +++ b/hash.go @@ -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. @@ -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) diff --git a/main.go b/main.go index 42e53af6..fcb17047 100644 --- a/main.go +++ b/main.go @@ -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 @@ -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) @@ -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 { @@ -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) } @@ -949,16 +948,41 @@ 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 + if strings.HasSuffix(path, "internal/abi/type.go") { + 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 @@ -972,7 +996,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 } @@ -1026,8 +1050,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. @@ -1085,6 +1113,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 { @@ -1140,7 +1172,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, "" @@ -1208,29 +1240,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 @@ -1308,7 +1337,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() } @@ -1412,7 +1441,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, @@ -1460,7 +1489,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 @@ -1479,10 +1508,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; @@ -1497,7 +1532,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 { @@ -1530,7 +1565,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 } @@ -1539,7 +1575,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 } @@ -1590,9 +1626,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 } @@ -1997,11 +2035,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 @@ -2071,7 +2104,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) @@ -2192,7 +2225,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)) }) diff --git a/position.go b/position.go index a502bb04..9db895ee 100644 --- a/position.go +++ b/position.go @@ -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) } @@ -144,4 +144,5 @@ func printFile(lpkg *listedPackage, file *ast.File) ([]byte, error) { fmt.Fprintf(&printBuf2, " /*line %s%s:1*/ ", newPrefix, newName) } } + } diff --git a/reflect.go b/reflect.go index b88a766f..ae70c847 100644 --- a/reflect.go +++ b/reflect.go @@ -58,8 +58,8 @@ func (ri *reflectInspector) ignoreReflectedTypes(ssaPkg *ssa.Package) { // At least it's enough to leave the rtype and Value types intact. if ri.pkg.Path() == "reflect" { scope := ri.pkg.Scope() - ri.recursivelyRecordUsedForReflect(scope.Lookup("rtype").Type()) - ri.recursivelyRecordUsedForReflect(scope.Lookup("Value").Type()) + ri.recursivelyRecordUsedForReflect(scope.Lookup("rtype").Type(), nil) + ri.recursivelyRecordUsedForReflect(scope.Lookup("Value").Type(), nil) } for _, memb := range ssaPkg.Members { @@ -135,7 +135,7 @@ func (ri *reflectInspector) checkMethodSignature(reflectParams map[int]bool, sig if ignore { reflectParams[i] = true - ri.recursivelyRecordUsedForReflect(param.Type()) + ri.recursivelyRecordUsedForReflect(param.Type(), nil) } } } @@ -196,7 +196,7 @@ func (ri *reflectInspector) checkFunction(fun *ssa.Function) { case *ssa.ChangeType: obj := typeToObj(inst.X.Type()) if usedForReflect(ri.result, obj) { - ri.recursivelyRecordUsedForReflect(inst.Type()) + ri.recursivelyRecordUsedForReflect(inst.Type(), nil) ri.propagatedInstr[inst] = true } case *ssa.Call: @@ -284,7 +284,7 @@ func (ri *reflectInspector) recordArgReflected(val ssa.Value, visited map[ssa.Va case *ssa.Alloc: /* fmt.Printf("recording val %v \n", *val.Referrers()) */ - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) for _, ref := range *val.Referrers() { if idx, ok := ref.(ssa.Value); ok { @@ -299,11 +299,11 @@ func (ri *reflectInspector) recordArgReflected(val ssa.Value, visited map[ssa.Va return relatedParam(val, visited) case *ssa.ChangeType: - ri.recursivelyRecordUsedForReflect(val.X.Type()) + ri.recursivelyRecordUsedForReflect(val.X.Type(), nil) case *ssa.MakeSlice, *ssa.MakeMap, *ssa.MakeChan, *ssa.Const: - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) case *ssa.Global: - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) // TODO: this might need similar logic to *ssa.Alloc, however // reassigning a function param to a global variable and then reflecting @@ -312,7 +312,7 @@ func (ri *reflectInspector) recordArgReflected(val ssa.Value, visited map[ssa.Va // this only finds the parameters who want to be found, // otherwise relatedParam is used for more in depth analysis - ri.recursivelyRecordUsedForReflect(val.Type()) + ri.recursivelyRecordUsedForReflect(val.Type(), nil) return val } @@ -388,7 +388,7 @@ func relatedParam(val ssa.Value, visited map[ssa.Value]bool) *ssa.Parameter { // Only the names declared in the current package are recorded. This is to ensure // that reflection detection only happens within the package declaring a type. // Detecting it in downstream packages could result in inconsistencies. -func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type) { +func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type, parent *types.Struct) { switch t := t.(type) { case *types.Named: obj := t.Obj() @@ -398,10 +398,10 @@ func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type) { if usedForReflect(ri.result, obj) { return // prevent endless recursion } - ri.recordUsedForReflect(obj) + ri.recordUsedForReflect(obj, parent) // Record the underlying type, too. - ri.recursivelyRecordUsedForReflect(t.Underlying()) + ri.recursivelyRecordUsedForReflect(t.Underlying(), nil) case *types.Struct: for i := range t.NumFields() { @@ -415,19 +415,18 @@ func (ri *reflectInspector) recursivelyRecordUsedForReflect(t types.Type) { } // Record the field itself, too. - ri.recordUsedForReflect(field) + ri.recordUsedForReflect(field, t) - ri.recursivelyRecordUsedForReflect(field.Type()) + ri.recursivelyRecordUsedForReflect(field.Type(), nil) } case interface{ Elem() types.Type }: // Get past pointers, slices, etc. - ri.recursivelyRecordUsedForReflect(t.Elem()) + ri.recursivelyRecordUsedForReflect(t.Elem(), nil) } } -// TODO: consider caching recordedObjectString via a map, -// if that shows an improvement in our benchmark +// TODO: figure out how to move `EmbeddedAliasFields` to reflectedObjectString func recordedObjectString(obj types.Object) objectString { if obj == nil { return "" @@ -465,23 +464,39 @@ func recordedObjectString(obj types.Object) objectString { return pkg.Path() + "." + obj.Name() } +func reflectedObjectString(obj types.Object, parent *types.Struct) string { + if obj == nil { + return "" + } + pkg := obj.Pkg() + if pkg == nil { + return "" + } + + if v, ok := obj.(*types.Var); ok && parent != nil { + return hashWithStruct(parent, v) + } + + lpkg := sharedCache.ListedPackages[obj.Pkg().Path()] + return hashWithPackage(lpkg, obj.Name()) + +} + // recordUsedForReflect records the objects whose names we cannot obfuscate due to reflection. // We currently record named types and fields. -func (ri *reflectInspector) recordUsedForReflect(obj types.Object) { +func (ri *reflectInspector) recordUsedForReflect(obj types.Object, parent *types.Struct) { if obj.Pkg().Path() != ri.pkg.Path() { panic("called recordUsedForReflect with a foreign object") } - objStr := recordedObjectString(obj) + objStr := reflectedObjectString(obj, parent) if objStr == "" { - // If the object can't be described via a qualified string, - // do we need to record it at all? return } - ri.result.ReflectObjects[objStr] = struct{}{} + ri.result.ReflectObjects[objStr] = obj.Name() } func usedForReflect(cache pkgCache, obj types.Object) bool { - objStr := recordedObjectString(obj) + objStr := reflectedObjectString(obj, nil) if objStr == "" { return false } diff --git a/reflect_abi_patch.go b/reflect_abi_patch.go new file mode 100644 index 00000000..036a4413 --- /dev/null +++ b/reflect_abi_patch.go @@ -0,0 +1,117 @@ +package main + +import ( + "bytes" + "fmt" + "maps" + "os" + "slices" + "strings" +) + +func abiNamePatch(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + + find := `return unsafe.String(n.DataChecked(1+i, "non-empty string"), l)` + replace := `name := unsafe.String(n.DataChecked(1+i, "non-empty string"), l) + return _realName(name)` + + str := strings.Replace(string(data), find, replace, 1) + + realname := ` +//go:linkname _realName +func _realName(name string) string +` + + return str + realname, nil +} + +var patchMainFile = "" + +func reflectMainPrePatch(path string) ([]byte, error) { + if patchMainFile != "" { + // already patched another file in main + return nil, nil + } + + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + patchMainFile = path + + nameMap := "\nvar _nameMap = map[string]string{}" + + return append(content, []byte(realNameCode+nameMap)...), nil +} + +func reflectMainPostPatch(file []byte, lpkg *listedPackage) []byte { + obfMapName := hashWithPackage(lpkg, "_nameMap") + nameMap := fmt.Sprintf("%s = map[string]string{", obfMapName) + + values := "" + obfToReal := realForReflected() + + keys := slices.Collect(maps.Keys(obfToReal)) + slices.Sort(keys) + + for _, obf := range keys { + values += fmt.Sprintf(`"%s": "%s",`, obf, obfToReal[obf]) + } + + return bytes.Replace(file, []byte(nameMap), []byte(nameMap+values), 1) +} + +// this may look unnessecary for simpler cases but we need support complex cases like: +// name := "*struct { AQ45rr68K string; ipq5aQSIqN string; hNfiW5O5LVq struct { gPTbGR00hu string } }" +const realNameCode = ` +//go:linkname _realName internal/abi._realName +func _realName(name string) string { + for i := 0; i < len(name); { + remLen := len(name[i:]) + + found := false + for obfName, real := range _nameMap { + keyLen := len(obfName) + if keyLen > remLen { + continue + } + if name[i:i+keyLen] == obfName { + name = name[:i] + real + name[i+keyLen:] + + found = true + i += len(real) + break + } + } + + if !found { + i++ + } + } + + return name +}` + +// map reflected obfuscated names to real names +func realForReflected() map[string]string { + obfToReal := map[string]string{} + + for _, lpkg := range sharedCache.ListedPackages { + pkg, err := loadPkgCache(lpkg) + if err != nil { + // if this happens it's for a package we don't care about + continue + } + + for obfsName, name := range pkg.ReflectObjects { + obfToReal[obfsName] = name + } + } + + return obfToReal +} diff --git a/reverse.go b/reverse.go index ed9fef4d..eb69a8ac 100644 --- a/reverse.go +++ b/reverse.go @@ -69,13 +69,13 @@ One can reverse a captured panic stack trace as follows: continue } addHashedWithPackage := func(str string) { - replaces = append(replaces, hashWithPackage(nil, lpkg, str), str) + replaces = append(replaces, hashWithPackage(lpkg, str), str) } // Package paths are obfuscated, too. addHashedWithPackage(lpkg.ImportPath) - files, err := parseFiles(lpkg.Dir, lpkg.CompiledGoFiles) + files, err := parseFiles(lpkg, lpkg.Dir, lpkg.CompiledGoFiles) if err != nil { return err } @@ -113,7 +113,7 @@ One can reverse a captured panic stack trace as follows: // Reverse position information of call sites. pos := fset.Position(node.Pos()) origPos := fmt.Sprintf("%s:%d", goFile, pos.Offset) - newFilename := hashWithPackage(nil, lpkg, origPos) + ".go" + newFilename := hashWithPackage(lpkg, origPos) + ".go" // Do "obfuscated.go:1", corresponding to the call site's line. // Most common in stack traces. diff --git a/shared.go b/shared.go index 69a878d4..c3b6782c 100644 --- a/shared.go +++ b/shared.go @@ -91,20 +91,23 @@ func loadSharedCache() error { // saveSharedCache creates a temporary directory to share between garble processes. // This directory also includes the gob-encoded cache global. -func saveSharedCache() (string, error) { +func saveSharedCache() (err error) { if sharedCache == nil { panic("saving a missing cache?") } - dir, err := os.MkdirTemp("", "garble-shared") - if err != nil { - return "", err - } - cachePath := filepath.Join(dir, "main-cache.gob") - if err := writeGobExclusive(cachePath, &sharedCache); err != nil { - return "", err + if sharedTempDir == "" { + sharedTempDir, err = os.MkdirTemp("", "garble-shared") + if err != nil { + return err + } + + os.Setenv("GARBLE_SHARED", sharedTempDir) } - return dir, nil + + cachePath := filepath.Join(sharedTempDir, "main-cache.gob") + + return writeGobExclusive(cachePath, &sharedCache) } func createExclusive(name string) (*os.File, error) { @@ -227,7 +230,7 @@ func (p *listedPackage) obfuscatedImportPath() string { if !p.ToObfuscate { return p.ImportPath } - newPath := hashWithPackage(nil, p, p.ImportPath) + newPath := hashWithPackage(p, p.ImportPath) log.Printf("import path %q hashed with %x to %q", p.ImportPath, p.GarbleActionID, newPath) return newPath } diff --git a/testdata/script/reflect.txtar b/testdata/script/reflect.txtar index 2c7e0f84..eaeab593 100644 --- a/testdata/script/reflect.txtar +++ b/testdata/script/reflect.txtar @@ -172,6 +172,8 @@ func main() { mu sync.Mutex extensionMap map[int32]EncodingT }) + + fmt.Println(reflect.TypeOf(Connection{})) } type EmbeddingIndirect struct { @@ -463,6 +465,43 @@ func gobChan() { }, len([]string{})) } +type Connection struct { + MaxLen struct { + Varchar int + } +} + +// NewConnection create a new connection from databaseURL string +func NewConnection() *Connection { + return &Connection{ + MaxLen: struct { + Varchar int + }{ + Varchar: 0x7FFF, + }, + } +} + +func closure() { + type gobAlias struct { + Security func() struct { + Pad bool + } + } + + alias := gobAlias{} + + gob.NewEncoder(os.Stdout).Encode(alias) + + outer := func() func() struct{ Pad bool } { + return func() struct{ Pad bool } { + return struct{ Pad bool }{Pad: true} + } + } + + alias.Security = outer() +} + -- importedpkg/imported.go -- package importedpkg @@ -603,3 +642,4 @@ struct { UnnamedStructField string } TypeOfParent's own name: TypeOfParent TypeOfParent named: TypeOfNamedField NamedReflectionField TypeOfParent embedded: TypeOfEmbeddedField EmbeddedReflectionField +main.Connection