diff --git a/main.go b/main.go index 7e3860b..119f120 100644 --- a/main.go +++ b/main.go @@ -888,9 +888,7 @@ func transformCompile(args []string) ([]string, error) { } // Uncomment for some quick debugging. Do not delete. - // if curPkg.ToObfuscate { - // fmt.Fprintf(os.Stderr, "\n-- %s/%s --\n%s", curPkg.ImportPath, filename, src) - // } + // fmt.Fprintf(os.Stderr, "\n-- %s/%s --\n%s", curPkg.ImportPath, filename, src) if path, err := writeTemp(filename, src); err != nil { return nil, err @@ -933,62 +931,80 @@ func (tf *transformer) handleDirectives(comments []*ast.CommentGroup) { if !strings.HasPrefix(comment.Text, "//go:linkname ") { continue } + + // We can have either just one argument: + // + // //go:linkname localName + // + // Or two arguments, where the second may refer to a name in a + // different package: + // + // //go:linkname localName newName + // //go:linkname localName pkg.newName fields := strings.Fields(comment.Text) - if len(fields) != 3 { - // TODO: the 2nd argument is optional, handle when it's not present - continue + localName := fields[1] + newName := "" + if len(fields) == 3 { + newName = fields[2] } - // This directive has two arguments: "go:linkname localName newName" - // obfuscate the local name, if the current package is obfuscated - if curPkg.ToObfuscate { - fields[1] = hashWithPackage(curPkg, fields[1]) + localName, newName = tf.transformLinkname(localName, newName) + fields[1] = localName + if len(fields) == 3 { + fields[2] = newName } - // If the new name is of the form "pkgpath.Name", and - // we've obfuscated "Name" in that package, rewrite the - // directive to use the obfuscated name. - newName := fields[2] - dotCnt := strings.Count(newName, ".") - if dotCnt < 1 { - // cgo-generated code uses linknames to made up symbol names, - // which do not have a package path at all. - // Replace the comment in case the local name was obfuscated. - comment.Text = strings.Join(fields, " ") - continue - } - switch newName { - case "main.main", "main..inittask", "runtime..inittask": - // The runtime uses some special symbols with "..". - // We aren't touching those at the moment. - continue + if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed + log.Printf("linkname %q changed to %q", comment.Text, strings.Join(fields, " ")) } + comment.Text = strings.Join(fields, " ") + } + } +} - // If the package path has multiple dots, split on the - // last one. - lastDotIdx := strings.LastIndex(newName, ".") - pkgPath, name := newName[:lastDotIdx], newName[lastDotIdx+1:] +func (tf *transformer) transformLinkname(localName, newName string) (string, string) { + // obfuscate the local name, if the current package is obfuscated + if curPkg.ToObfuscate { + localName = hashWithPackage(curPkg, localName) + } + if newName == "" { + return localName, "" + } + // If the new name is of the form "pkgpath.Name", and we've obfuscated + // "Name" in that package, rewrite the directive to use the obfuscated name. + dotCnt := strings.Count(newName, ".") + if dotCnt < 1 { + // cgo-generated code uses linknames to made up symbol names, + // which do not have a package path at all. + // Replace the comment in case the local name was obfuscated. + return localName, newName + } + switch newName { + case "main.main", "main..inittask", "runtime..inittask": + // The runtime uses some special symbols with "..". + // We aren't touching those at the moment. + return localName, newName + } - lpkg, err := listPackage(pkgPath) - if err != nil { - // Probably a made up name like above, but with a dot. - comment.Text = strings.Join(fields, " ") - continue - } - if lpkg.ToObfuscate { - // The name exists and was obfuscated; obfuscate - // the new name. - newName := hashWithPackage(lpkg, name) - newPkgPath := pkgPath - if pkgPath != "main" { - newPkgPath = lpkg.obfuscatedImportPath() - } - fields[2] = newPkgPath + "." + newName - } + // If the package path has multiple dots, split on the last one. + lastDotIdx := strings.LastIndex(newName, ".") + pkgPath, foreignName := newName[:lastDotIdx], newName[lastDotIdx+1:] - comment.Text = strings.Join(fields, " ") + lpkg, err := listPackage(pkgPath) + if err != nil { + // Probably a made up name like above, but with a dot. + return localName, newName + } + if lpkg.ToObfuscate { + // The name exists and was obfuscated; obfuscate the new name. + newForeignName := hashWithPackage(lpkg, foreignName) + newPkgPath := pkgPath + if pkgPath != "main" { + newPkgPath = lpkg.obfuscatedImportPath() } + newName = newPkgPath + "." + newForeignName } + return localName, newName } // processImportCfg parses the importcfg file passed to a compile or link step. diff --git a/shared.go b/shared.go index 9a4b78d..1635260 100644 --- a/shared.go +++ b/shared.go @@ -289,9 +289,6 @@ func appendListedPackages(packages []string, withDeps bool) error { // // TODO: investigate and resolve each one of these var cannotObfuscate = map[string]bool{ - // "//go:linkname must refer to declared function or variable" - "syscall": true, - // "unknown pc" crashes on windows in the cgo test otherwise "runtime/cgo": true, @@ -333,7 +330,8 @@ func listPackage(path string) (*listedPackage, error) { // This is due to how it linkname-implements std packages, // such as sync/atomic or reflect, without importing them in any way. // If ListedPackages lacks such a package we fill it with "std". - if curPkg.ImportPath == "runtime" { + // Note that this is also allowed for runtime sub-packages. + if curPkg.ImportPath == "runtime" || strings.HasPrefix(curPkg.ImportPath, "runtime/") { if ok { return pkg, nil } diff --git a/testdata/script/gogarble.txtar b/testdata/script/gogarble.txtar index 640c18f..5c6cd4c 100644 --- a/testdata/script/gogarble.txtar +++ b/testdata/script/gogarble.txtar @@ -36,7 +36,13 @@ garble build std # Link a binary importing net/http, which will catch whether or not we # support ImportMap when linking. +# Also ensure we are obfuscating low-level std packages. garble build -o=out ./stdimporter +! binsubstr out 'http.ListenAndServe' 'debug.WriteHeapDump' 'time.Now' 'syscall.Listen' + +# The same low-level std packages appear in plain sight in regular builds. +go build -o=out_regular ./stdimporter +binsubstr out_regular 'http.ListenAndServe' 'debug.WriteHeapDump' 'time.Now' 'syscall.Listen' # Also check that a full rebuild is reproducible, via a new GOCACHE. # This is slow, but necessary to uncover bugs hidden by the build cache. @@ -73,6 +79,8 @@ package main import ( "net/http" "runtime/debug" + "time" + "syscall" ) func main() { @@ -80,4 +88,6 @@ func main() { // debug.WriteHeapDump is particularly interesting, // as it is implemented by runtime via a linkname. debug.WriteHeapDump(1) + time.Now() + syscall.Listen(0, 1) }