support obfuscating the syscall package

One more package that further unblocks obfuscating the runtime.
The issue was the TODO we already had about go:linkname directives with
just one argument, which are used in the syscall package.

While here, factor out the obfuscation of linkname directives into
transformLinkname, as it was starting to get a bit complex.
We now support debug logging as well, while still being able to use
"early returns" for some cases where we bail out.

We also need listPackage to treat all runtime sub-packages like it does
runtime itself, as `runtime/internal/syscall` linknames into `syscall`
without it being a dependency as well.

Finally, add a regression test that, without the fix,
properly spots that the syscall package was not obfuscated:

	FAIL: testdata/script/gogarble.txtar:41: unexpected match for ["syscall.RawSyscall6"] in out

Updates #193.
pull/598/head
Daniel Martí 2 years ago
parent e71cb69dd8
commit 7c2866356f

@ -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.

@ -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
}

@ -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)
}

Loading…
Cancel
Save