propagate "uses reflection" through SSA stores

Up until now, the new SSA reflection detection relied on call sites
to propagate which objects (named types, struct fields) used reflection.
For example, given the code:

    json.Marshal(new(T))

we would first record that json.Marshal calls reflect.TypeOf,
and then that the user's code called json.Marshal with the type *T.

However, this would not catch a slight variation on the above:

    var t T
    reflect.TypeOf(t)
    t.foo = struct{bar int}{}

Here, type T's fields such as "foo" and "bar" are not obfuscated,
since our logic sees the call site and marks the type T recursively.
However, the unnamed `struct{bar int}` type was still obfuscated,
causing errors such as:

    cannot use struct{uKGvcJvD24 int}{} (value of type struct{uKGvcJvD24 int}) as struct{bar int} value in assignment

The solution is to teach the analysis about *ssa.Store instructions
in a similar way to how it already knows about *ssa.Call instructions.
If we see a store where the destination type is marked for reflection,
then we mark the source type as well, fixing the bug above.

This fixes obfuscating github.com/gogo/protobuf/proto.
A number of other Go modules fail with similar errors,
and they look like very similar bugs,
but this particular fix doesn't apply to them.
Future incremental fixes will try to deal with those extra cases.

Fixes #685.
literalRecheck
Daniel Martí 11 months ago committed by lu4p
parent afe1aad916
commit 23c8641855

@ -1539,9 +1539,10 @@ func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Packa
// Fill the reflect info from SSA, which builds on top of the syntax tree and type info. // Fill the reflect info from SSA, which builds on top of the syntax tree and type info.
inspector := reflectInspector{ inspector := reflectInspector{
pkg: pkg, pkg: pkg,
checkedAPIs: make(map[string]bool), checkedAPIs: make(map[string]bool),
result: computed, // append the results propagatedStores: map[*ssa.Store]bool{},
result: computed, // append the results
} }
if ssaPkg == nil { if ssaPkg == nil {
ssaPkg = ssaBuildPkg(pkg, files, info) ssaPkg = ssaBuildPkg(pkg, files, info)

@ -15,6 +15,8 @@ type reflectInspector struct {
checkedAPIs map[string]bool checkedAPIs map[string]bool
propagatedStores map[*ssa.Store]bool
result pkgCache result pkgCache
} }
@ -24,7 +26,7 @@ func (ri *reflectInspector) recordReflection(ssaPkg *ssa.Package) {
return return
} }
lenPrevReflectAPIs := len(ri.result.ReflectAPIs) prevDone := len(ri.result.ReflectAPIs) + len(ri.propagatedStores)
// find all unchecked APIs to add them to checkedAPIs after the pass // find all unchecked APIs to add them to checkedAPIs after the pass
notCheckedAPIs := make(map[string]bool) notCheckedAPIs := make(map[string]bool)
@ -41,8 +43,9 @@ func (ri *reflectInspector) recordReflection(ssaPkg *ssa.Package) {
maps.Copy(ri.checkedAPIs, notCheckedAPIs) maps.Copy(ri.checkedAPIs, notCheckedAPIs)
// if a new reflectAPI is found we need to Re-evaluate all functions which might be using that API // if a new reflectAPI is found we need to Re-evaluate all functions which might be using that API
if len(ri.result.ReflectAPIs) > lenPrevReflectAPIs { newDone := len(ri.result.ReflectAPIs) + len(ri.propagatedStores)
ri.recordReflection(ssaPkg) if newDone > prevDone {
ri.recordReflection(ssaPkg) // TODO: avoid recursing
} }
} }
@ -157,10 +160,10 @@ func (ri *reflectInspector) checkInterfaceMethod(m *types.Func) {
// Checks all callsites in a function declaration for use of reflection. // Checks all callsites in a function declaration for use of reflection.
func (ri *reflectInspector) checkFunction(fun *ssa.Function) { func (ri *reflectInspector) checkFunction(fun *ssa.Function) {
/* if fun != nil && fun.Synthetic != "loaded from gc object file" { // if fun != nil && fun.Synthetic != "loaded from gc object file" {
// fun.WriteTo crashes otherwise // // fun.WriteTo crashes otherwise
fun.WriteTo(os.Stdout) // fun.WriteTo(os.Stdout)
} */ // }
f, _ := fun.Object().(*types.Func) f, _ := fun.Object().(*types.Func)
@ -173,54 +176,59 @@ func (ri *reflectInspector) checkFunction(fun *ssa.Function) {
} }
} }
/* fmt.Printf("f: %v\n", f) // fmt.Printf("f: %v\n", f)
fmt.Printf("fun: %v\n", fun) */ // fmt.Printf("fun: %v\n", fun)
for _, block := range fun.Blocks { for _, block := range fun.Blocks {
for _, inst := range block.Instrs { for _, inst := range block.Instrs {
/* fmt.Printf("inst: %v, t: %T\n", inst, inst) */ // fmt.Printf("inst: %v, t: %T\n", inst, inst)
call, ok := inst.(*ssa.Call) switch inst := inst.(type) {
if !ok { case *ssa.Store:
continue if ri.propagatedStores[inst] {
} break // already done
}
callName := call.Call.Value.String() if storeAddrUsedForReflect(ri.result, inst.Addr.Type()) {
if m := call.Call.Method; m != nil { ri.recordArgReflected(inst.Val, make(map[ssa.Value]bool))
callName = call.Call.Method.FullName() ri.propagatedStores[inst] = true
} }
case *ssa.Call:
if ri.checkedAPIs[callName] { callName := inst.Call.Value.String()
// only check apis which were not already checked if m := inst.Call.Method; m != nil {
continue callName = inst.Call.Method.FullName()
} }
/* fmt.Printf("callName: %v\n", callName) */
// record each call argument passed to a function parameter which is used in reflection if ri.checkedAPIs[callName] {
knownParams := ri.result.ReflectAPIs[callName] // only check apis which were not already checked
for knownParam := range knownParams {
if len(call.Call.Args) <= knownParam {
continue continue
} }
arg := call.Call.Args[knownParam] /* fmt.Printf("callName: %v\n", callName) */
/* fmt.Printf("flagging arg: %v\n", arg) */ // record each call argument passed to a function parameter which is used in reflection
knownParams := ri.result.ReflectAPIs[callName]
for knownParam := range knownParams {
if len(inst.Call.Args) <= knownParam {
continue
}
visited := make(map[ssa.Value]bool) arg := inst.Call.Args[knownParam]
reflectedParam := ri.recordArgReflected(arg, visited)
if reflectedParam == nil {
continue
}
pos := slices.Index(fun.Params, reflectedParam) /* fmt.Printf("flagging arg: %v\n", arg) */
if pos < 0 {
continue
}
/* fmt.Printf("recorded param: %v func: %v\n", pos, fun) */ reflectedParam := ri.recordArgReflected(arg, make(map[ssa.Value]bool))
if reflectedParam == nil {
continue
}
reflectParams[pos] = true pos := slices.Index(fun.Params, reflectedParam)
if pos < 0 {
continue
}
/* fmt.Printf("recorded param: %v func: %v\n", pos, fun) */
reflectParams[pos] = true
}
} }
} }
} }
@ -459,3 +467,18 @@ func usedForReflect(cache pkgCache, obj types.Object) bool {
_, ok := cache.ReflectObjects[objStr] _, ok := cache.ReflectObjects[objStr]
return ok return ok
} }
// storeAddrUsedForReflect is only used in reflectInspector
// to see if a [ssa.Store.Addr] has been marked as used by reflection.
// We only mark named objects, so this function looks for a type's first struct field.
func storeAddrUsedForReflect(cache pkgCache, typ types.Type) bool {
switch typ := typ.(type) {
case *types.Struct:
if typ.NumFields() > 0 {
return usedForReflect(cache, typ.Field(0))
}
case interface{ Elem() types.Type }:
return storeAddrUsedForReflect(cache, typ.Elem())
}
return false
}

@ -31,6 +31,7 @@ import (
"reflect" "reflect"
"unsafe" "unsafe"
"strings" "strings"
"sync"
"text/template" "text/template"
"test/main/importedpkg" "test/main/importedpkg"
@ -164,6 +165,12 @@ func main() {
embedFieldField.Name, embedFieldField.Name,
) )
} }
y := UnnamedStructFields{}
y.unexportedGoGoProto = new(struct {
mu sync.Mutex
extensionMap map[int32]EncodingT
})
} }
type EmbeddingIndirect struct { type EmbeddingIndirect struct {
@ -334,6 +341,21 @@ type UnnamedStructInterface interface {
UnnamedStructMethod(struct{ UnnamedStructField string }) UnnamedStructMethod(struct{ UnnamedStructField string })
} }
// Some projects declare types with unnamed struct fields,
// and the entire type is used via reflection and cannot be obfuscated.
// However, when assigning to these fields, the use of inline anonymous struct types
// confused garble, and it did not obfuscate those inline structs as well.
// That resulted in "cannot use X as Y value in assignment" build errors.
var _ = reflect.TypeOf(UnnamedStructFields{})
type UnnamedStructFields struct {
// As seen in github.com/gogo/protobuf/proto.
unexportedGoGoProto *struct {
mu sync.Mutex
extensionMap map[int32]EncodingT
}
}
-- importedpkg/imported.go -- -- importedpkg/imported.go --
package importedpkg package importedpkg

Loading…
Cancel
Save