More correct comments transformation (#152)

More correct comments transformation was implemented.

Added processing of //go:linkname localname [importpath.name] directive, now localname is not renamed. This is safe and does not cause a name disclosure because the functions marked //linkname do not have a name in the resulting binary.

Added cgo directives support

Fixed filename leak protection for cgo

Part of #149
pull/153/head
pagran 5 years ago committed by GitHub
parent 991fbb042b
commit ea4a01df87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -16,6 +16,49 @@ import (
// Source: https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/internal/syntax/parser_test.go#229
const PosMin = 1
const buildTagPrefix = "// +build"
// Source: https://go.googlesource.com/go/+/refs/heads/master/src/cmd/compile/internal/gc/noder.go#1493
var nameSpecialDirectives = []string{
"//go:linkname",
"//go:cgo_export_static",
"//go:cgo_export_dynamic",
"//go:cgo_import_static",
"//go:cgo_import_dynamic",
}
var specialDirectives = append([]string{
"//go:cgo_ldflag",
"//go:cgo_dynamic_linker",
}, nameSpecialDirectives...)
func isDirective(text string, directives []string) bool {
for _, prefix := range directives {
if strings.HasPrefix(text, prefix) {
return true
}
}
return false
}
func getLocalName(text string) (string, bool) {
if !isDirective(text, nameSpecialDirectives) {
return "", false
}
parts := strings.Fields(text)
if len(parts) < 2 {
return "", false
}
name := strings.TrimSpace(parts[1])
if len(name) == 0 {
return "", false
}
return name, true
}
func prependComment(group *ast.CommentGroup, comment *ast.Comment) *ast.CommentGroup {
if group == nil {
return &ast.CommentGroup{List: []*ast.Comment{comment}}
@ -32,9 +75,8 @@ func clearCommentGroup(group *ast.CommentGroup) *ast.CommentGroup {
}
var comments []*ast.Comment
for _, comment := range group.List {
if strings.HasPrefix(comment.Text, "//go:") {
if strings.HasPrefix(comment.Text, "//go:") && !isDirective(comment.Text, specialDirectives) {
comments = append(comments, &ast.Comment{Text: comment.Text})
}
}
@ -68,21 +110,46 @@ func clearNodeComments(node ast.Node) {
}
}
func findBuildTags(commentGroups []*ast.CommentGroup) (buildTags []string) {
for _, group := range commentGroups {
for _, comment := range group.List {
if !strings.Contains(comment.Text, "+build") {
// processDetachedDire collects all not attached to declarations comments and build tags
// It returns detached comments and local name blacklist
func processDetachedDirectives(commentGroups []*ast.CommentGroup) (detachedComments, localNameBlacklist []string) {
var buildTags []string
var specialComments []string
for _, commentGroup := range commentGroups {
for _, comment := range commentGroup.List {
if strings.HasPrefix(comment.Text, buildTagPrefix) {
buildTags = append(buildTags, comment.Text)
continue
}
if !isDirective(comment.Text, specialDirectives) {
continue
}
buildTags = append(buildTags, comment.Text)
specialComments = append(specialComments, comment.Text)
if localName, ok := getLocalName(comment.Text); ok {
localNameBlacklist = append(localNameBlacklist, localName)
}
}
}
return buildTags
detachedComments = append(detachedComments, buildTags...)
detachedComments = append(detachedComments, specialComments...)
detachedComments = append(detachedComments, "")
return detachedComments, localNameBlacklist
}
func transformLineInfo(file *ast.File) ([]string, *ast.File) {
// transformLineInfo removes the comment except go directives and build tags. Converts comments to the node view.
// It returns comments not attached to declarations and names of declarations which cannot be renamed.
func transformLineInfo(file *ast.File, cgoFile bool) (detachedComments, localNameBlacklist []string, f *ast.File) {
prefix := ""
if cgoFile {
prefix = "_cgo_"
}
// Save build tags and add file name leak protection
extraComments := append(findBuildTags(file.Comments), "", "//line :1")
detachedComments, localNameBlacklist = processDetachedDirectives(file.Comments)
detachedComments = append(detachedComments, "", "//line "+prefix+":1")
file.Comments = nil
newLines := mathrand.Perm(len(file.Decls))
@ -92,16 +159,20 @@ func transformLineInfo(file *ast.File) ([]string, *ast.File) {
node := cursor.Node()
clearNodeComments(node)
// If tiny mode is active information about line numbers is erased in object files
if envGarbleTiny {
return true
}
funcDecl, ok := node.(*ast.FuncDecl)
if !ok {
return true
}
comment := &ast.Comment{Text: fmt.Sprintf("//line %c.go:%d", nameCharset[mathrand.Intn(len(nameCharset))], PosMin+newLines[funcCounter])}
comment := &ast.Comment{Text: fmt.Sprintf("//line %s%c.go:%d", prefix, nameCharset[mathrand.Intn(len(nameCharset))], PosMin+newLines[funcCounter])}
funcDecl.Doc = prependComment(funcDecl.Doc, comment)
funcCounter++
return true
}
return extraComments, astutil.Apply(file, pre, nil).(*ast.File)
return detachedComments, localNameBlacklist, astutil.Apply(file, pre, nil).(*ast.File)
}

@ -591,11 +591,25 @@ func transformCompile(args []string) ([]string, error) {
privateNameMap := make(map[string]string)
existingNames := collectNames(files)
packageCounter := 0
detachedComments := make([][]string, len(files))
for i, file := range files {
name := filepath.Base(filepath.Clean(paths[i]))
cgoFile := strings.HasPrefix(name, "_cgo_")
fileDetachedComments, localNameBlacklist, file := transformLineInfo(file, cgoFile)
for _, name := range localNameBlacklist {
obj := pkg.Scope().Lookup(name)
if obj != nil {
blacklist[obj] = struct{}{}
}
}
detachedComments[i] = fileDetachedComments
files[i] = file
}
// TODO: randomize the order and names of the files
newPaths := make([]string, 0, len(files))
for i, file := range files {
var extraComments []string
origName := filepath.Base(filepath.Clean(paths[i]))
name := origName
switch {
@ -622,9 +636,6 @@ func transformCompile(args []string) ([]string, error) {
// messy.
name = "_cgo_" + name
default:
if !envGarbleTiny {
extraComments, file = transformLineInfo(file)
}
file = transformGo(file, info, blacklist, privateNameMap, pkgPath, existingNames, &packageCounter)
// Uncomment for some quick debugging. Do not delete.
@ -651,8 +662,9 @@ func transformCompile(args []string) ([]string, error) {
printWriter = io.MultiWriter(tempFile, debugFile)
}
if len(extraComments) > 0 {
for _, comment := range extraComments {
fileDetachedComments := detachedComments[i]
if len(fileDetachedComments) > 0 {
for _, comment := range fileDetachedComments {
if _, err = printWriter.Write([]byte(comment + "\n")); err != nil {
return nil, err
}

@ -1,19 +1,20 @@
env GOPRIVATE='test/main,rsc.io/*'
garble build
garble build -tags directives
exec ./main$exe
cmp stderr main.stderr
! binsubstr main$exe 'localName' 'globalConst' 'globalVar' 'globalType' 'valuable information' 'rsc.io'
! binsubstr main$exe 'localName' 'globalConst' 'globalVar' 'globalType' 'valuable information' 'rsc.io' 'remoteIntReturn' 'intReturn'
binsubstr main$exe 'magicFunc'
[short] stop # no need to verify this with -short
go build
go build -tags directives
exec ./main$exe
cmp stderr main.stderr
binsubstr main$exe 'globalVar' # 'globalType' only matches on go < 1.15
! binsubstr main$exe 'localName' 'globalConst'
! binsubstr main$exe 'localName' 'globalConst' 'remoteIntReturn' 'intReturn'
-- go.mod --
@ -73,6 +74,7 @@ func main() {
scopesTest()
println(quote.Go())
sub.Test()
sub.TestDirectives()
}
-- scopes.go --
@ -122,6 +124,39 @@ func Test() {
func noop(...interface{}) {}
-- sub/directives.go --
// +build directives
package sub
import (
_ "unsafe"
_ "test/main/sub/a"
)
//go:linkname remoteIntReturn a.magicFunc
func remoteIntReturn() int
//go:noinline
func TestDirectives() {
if remoteIntReturn() != 42 {
panic("invalid result")
}
}
-- sub/a/directives.go --
// +build directives
package a
import _ "unsafe"
//go:linkname intReturn a.magicFunc
//go:noinline
func intReturn() int {
return 42
}
-- main.stderr --
nil case
{"Foo":3}

Loading…
Cancel
Save