From 80538f19c79fa384a8a2abb748a69ff7a4b7de90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 29 May 2020 15:28:24 +0100 Subject: [PATCH] blacklist struct fields with reflection too In the added test, the unexported field used to be garbled. Reflection can only reach exported methods, exported fields, and unexported fields. Exported methods and fields are currently never garbled, so unexported fields was the only missing piece. --- main.go | 50 ++++++++++++++++++++++++------------ testdata/scripts/imports.txt | 25 ++++++++++++++---- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/main.go b/main.go index cd828d1..d9df8d0 100644 --- a/main.go +++ b/main.go @@ -452,12 +452,29 @@ func hashWith(salt, value string) string { // used with reflect.TypeOf or reflect.ValueOf. Since we obfuscate one package // at a time, we only detect those if the type definition and the reflect usage // are both in the same package. -func buildBlacklist(files []*ast.File, info *types.Info, pkg *types.Package) (blacklist []types.Object) { +// +// The blacklist mainly contains named types and their field declarations. +func buildBlacklist(files []*ast.File, info *types.Info, pkg *types.Package) map[types.Object]struct{} { // Keep track of the current syntax tree level. If reflectCallLevel is // non-negative, we are under a reflect call. level := 0 reflectCallLevel := -1 + blacklist := make(map[types.Object]struct{}) + addToBlacklist := func(named *types.Named) { + obj := named.Obj() + if obj == nil || obj.Pkg() != pkg { + return + } + blacklist[obj] = struct{}{} + + strct, _ := named.Underlying().(*types.Struct) + if strct != nil { + for i := 0; i < strct.NumFields(); i++ { + blacklist[strct.Field(i)] = struct{}{} + } + } + } visit := func(node ast.Node) bool { if node == nil { if level == reflectCallLevel { @@ -468,8 +485,9 @@ func buildBlacklist(files []*ast.File, info *types.Info, pkg *types.Package) (bl } if reflectCallLevel >= 0 && level >= reflectCallLevel { expr, _ := node.(ast.Expr) - if obj := objOf(info.TypeOf(expr)); obj != nil && obj.Pkg() == pkg { - blacklist = append(blacklist, obj) + named := namedType(info.TypeOf(expr)) + if named != nil { + addToBlacklist(named) } } level++ @@ -495,7 +513,7 @@ func buildBlacklist(files []*ast.File, info *types.Info, pkg *types.Package) (bl } // transformGo garbles the provided Go syntax node. -func transformGo(file *ast.File, info *types.Info, blacklist []types.Object) *ast.File { +func transformGo(file *ast.File, info *types.Info, blacklist map[types.Object]struct{}) *ast.File { // Remove all comments, minus the "//go:" compiler directives. // The final binary should still not contain comment text, but removing // it helps ensure that (and makes position info less predictable). @@ -538,7 +556,7 @@ func transformGo(file *ast.File, info *types.Info, blacklist []types.Object) *as if vr, ok := obj.(*types.Var); ok && vr.Embedded() { // ObjectOf returns the field for embedded struct // fields, not the type it uses. Use the type. - obj = objOf(obj.Type()) + obj = namedType(obj.Type()).Obj() pkg = obj.Pkg() } @@ -548,11 +566,9 @@ func transformGo(file *ast.File, info *types.Info, blacklist []types.Object) *as return true // could be a Go plugin API } - // TODO: also do this for method receivers - for _, item := range blacklist { - if obj == item { - return true - } + // The object itself is blacklisted, e.g. a type definition. + if _, ok := blacklist[obj]; ok { + return true } // log.Printf("%#v %T", node, obj) @@ -620,15 +636,15 @@ func implementedOutsideGo(obj *types.Func) bool { (obj.Scope() != nil && obj.Scope().Pos() == token.NoPos) } -// objOf tries to obtain the object behind a *types.Named, even if it's behind a -// pointer type. This is useful to obtain "testing.T" from "*testing.T", or to -// obtain the type declaration object from an embedded field. -func objOf(t types.Type) types.Object { +// named tries to obtain the *types.Named behind a type, if there is one. +// This is useful to obtain "testing.T" from "*testing.T", or to obtain the type +// declaration object from an embedded field. +func namedType(t types.Type) *types.Named { switch t := t.(type) { case *types.Named: - return t.Obj() + return t case interface{ Elem() types.Type }: - return objOf(t.Elem()) + return namedType(t.Elem()) default: return nil } @@ -643,7 +659,7 @@ func isTestSignature(sign *types.Signature) bool { if params.Len() != 1 { return false } - obj := objOf(params.At(0).Type()) + obj := namedType(params.At(0).Type()).Obj() return obj != nil && obj.Pkg().Path() == "testing" && obj.Name() == "T" } diff --git a/testdata/scripts/imports.txt b/testdata/scripts/imports.txt index b551dfc..63b800c 100644 --- a/testdata/scripts/imports.txt +++ b/testdata/scripts/imports.txt @@ -23,6 +23,7 @@ package main import ( "fmt" + "reflect" _ "unsafe" "test/main/imported" @@ -36,12 +37,20 @@ func linkedPrintln(a ...interface{}) (n int, err error) func main() { fmt.Println(imported.ImportedVar) fmt.Println(imported.ImportedConst) - imported.ImportedFunc('x') + fmt.Println(imported.ImportedFunc('x')) fmt.Println(imported.ImportedType(3)) fmt.Printf("%T\n", imported.ReflectTypeOf(2)) fmt.Printf("%T\n", imported.ReflectTypeOfIndirect(4)) - fmt.Printf("%#v\n", imported.ReflectValueOfVar) + + v := imported.ReflectValueOfVar + fmt.Printf("%#v\n", v) + method := reflect.ValueOf(&v).MethodByName("ExportedMethodName") + if method.IsValid() { + fmt.Println(method.Call(nil)) + } else { + fmt.Println("method not found") + } linkedPrintln(nil) fmt.Println(quote.Go()) @@ -68,10 +77,14 @@ type ReflectTypeOfIndirect int var _ = reflect.TypeOf(new([]*ReflectTypeOfIndirect)) type ReflectValueOf struct { - Foo int `bar:"baz"` + ExportedField string + + unexportedField string } -var ReflectValueOfVar = ReflectValueOf{Foo: 3} +func (r *ReflectValueOf) ExportedMethodName() string { return "method: "+r.ExportedField } + +var ReflectValueOfVar = ReflectValueOf{ExportedField: "abc"} var _ = reflect.TypeOf(ReflectValueOfVar) @@ -81,9 +94,11 @@ type ImportedType int -- main.stdout -- imported var value imported const value +x 3 imported.ReflectTypeOf imported.ReflectTypeOfIndirect -imported.ReflectValueOf{Foo:3} +imported.ReflectValueOf{ExportedField:"abc", unexportedField:""} +[method: abc] Don't communicate by sharing memory, share memory by communicating.