mirror of
https://github.com/burrowers/garble.git
synced 2026-04-22 23:57:14 +08:00
1b6cc14d93
Ensure reflected arguments keep field names when passing through generic helpers by propagating through ssa.ChangeType and normalizing generic API call handling. Also merge dependency reflection metadata correctly from cache hits. fixes #912
666 lines
15 KiB
Plaintext
666 lines
15 KiB
Plaintext
exec garble build
|
|
exec ./main
|
|
cmp stdout main.stdout
|
|
|
|
! binsubstr main$exe 'garble_main.go' 'test/main' 'importedpkg.' 'DownstreamObfuscated' 'SiblingObfuscated' 'IndirectObfuscated' 'IndirectNamedWithoutReflect' 'AliasIndirectNamedWithReflect' 'AliasIndirectNamedWithoutReflect' 'FmtTypeField' 'LocalObfuscated'
|
|
binsubstr main$exe 'ReflectInDefined' 'ExportedField2' 'unexportedField2' 'IndirectUnobfuscated' 'IndirectNamedWithReflect'
|
|
|
|
[short] stop # no need to verify this with -short
|
|
|
|
# Check that the program works as expected without garble.
|
|
go build
|
|
exec ./main
|
|
cmp stdout main.stdout
|
|
-- go.mod --
|
|
module test/main
|
|
|
|
go 1.23
|
|
-- garble_main.go --
|
|
package main
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/gob"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"unsafe"
|
|
|
|
"test/main/importedpkg"
|
|
"test/main/importedpkg2"
|
|
)
|
|
|
|
var Sink interface{}
|
|
|
|
func main() {
|
|
// Fields still work fine when they are not obfuscated.
|
|
fmt.Println(importedpkg.ReflectInDefinedVar.ExportedField2)
|
|
fmt.Println(importedpkg.ReflectInDefined{ExportedField2: 5})
|
|
|
|
// Type names are not obfuscated either, when reflection is used.
|
|
printfWithoutPackage("%T\n", importedpkg.ReflectTypeOf(2))
|
|
printfWithoutPackage("%T\n", importedpkg.ReflectTypeOfIndirect(4))
|
|
|
|
// More complex use of reflect.
|
|
v := importedpkg.ReflectValueOfVar
|
|
printfWithoutPackage("%#v\n", v)
|
|
method := reflect.ValueOf(&v).MethodByName("ExportedMethodName")
|
|
if method.IsValid() {
|
|
fmt.Println(method.Call(nil))
|
|
} else {
|
|
fmt.Println("method not found")
|
|
}
|
|
|
|
// Use of a common library using reflect, encoding/json.
|
|
enc, _ := json.Marshal(EncodingT{Foo: 3})
|
|
fmt.Println(string(enc))
|
|
|
|
// Another common library, text/template.
|
|
tmpl := template.Must(template.New("").Parse("Hello {{.Name}}."))
|
|
_ = tmpl.Execute(os.Stdout, struct{ Name string }{Name: "Dave"})
|
|
fmt.Println() // Always print a newline.
|
|
_ = genericRenderTemplate("name: {{.Name}} value: {{.Value}} port: {{.Port}}", GenericTemplateData{
|
|
Name: "test",
|
|
Value: "example",
|
|
Port: 8080,
|
|
})
|
|
fmt.Println() // Always print a newline.
|
|
|
|
// Another complex case, involving embedding and another package.
|
|
outer := &importedpkg.EmbeddingOuter{}
|
|
outer.InnerField = 3
|
|
enc, _ = json.Marshal(outer)
|
|
fmt.Println(string(enc))
|
|
|
|
// An edge case; the struct type is defined in a different package.
|
|
// Note that the struct type is unnamed, but it still has named fields.
|
|
// We only use reflection on it here, not the declaring package.
|
|
// As such, we should obfuscate the field name.
|
|
// Simply using the field name here used to cause build failures.
|
|
_ = reflect.TypeOf(importedpkg.UnnamedWithDownstreamReflect{})
|
|
fmt.Printf("%v\n", importedpkg.UnnamedWithDownstreamReflect{
|
|
DownstreamObfuscated: "downstream",
|
|
})
|
|
|
|
// An edge case; the struct type is defined in package importedpkg2.
|
|
// importedpkg2 does not use reflection on it, so it's not obfuscated there.
|
|
// importedpkg uses reflection on a type containing ReflectInSiblingImport.
|
|
// If our logic is incorrect, we might inconsistently obfuscate the type.
|
|
// We should not obfuscate it when building any package.
|
|
fmt.Printf("%v\n", importedpkg2.ReflectInSiblingImport{
|
|
SiblingObfuscated: "sibling",
|
|
})
|
|
|
|
// Using type aliases as both regular fields, and embedded fields.
|
|
var emb EmbeddingIndirect
|
|
emb.With.IndirectUnobfuscated = "indirect-with"
|
|
emb.With.DuplicateFieldName = 3
|
|
emb.Without.IndirectObfuscated = "indirect-without"
|
|
emb.Without.DuplicateFieldName = 4
|
|
fmt.Printf("%v\n", emb)
|
|
printfWithoutPackage("%#v\n", emb.With)
|
|
|
|
// TODO: don't obfuscate the embedded field name here
|
|
// printfWithoutPackage("%#v\n", importedpkg.ReflectEmbeddingAlias{})
|
|
|
|
indirectReflection(IndirectReflection{})
|
|
fmt.Println(FmtType{})
|
|
|
|
// Variadic functions are a bit tricky as the number of parameters is variable.
|
|
// We want to notice indirect uses of reflection via all variadic arguments.
|
|
_ = importedpkg.VariadicReflect(0, 1, 2, 3)
|
|
_ = importedpkg.VariadicReflect(0)
|
|
variadic := VariadicReflection{ReflectionField: "variadic"}
|
|
_ = importedpkg.VariadicReflect("foo", 1, variadic, false)
|
|
printfWithoutPackage("%#v\n", variadic)
|
|
|
|
testx509()
|
|
testGoSpew()
|
|
|
|
// Very complex reflection used by gorm
|
|
user := StatUser{}
|
|
find(&user)
|
|
|
|
// Similar to gorm with composite literals instead of direct assignments
|
|
userComp := StatCompUser{}
|
|
findComp(&userComp)
|
|
|
|
x := UnnamedStructInterface(importedpkg.ReflectUnnamedStruct(0))
|
|
x.UnnamedStructMethod(struct{ UnnamedStructField string }{UnnamedStructField: "field value"})
|
|
|
|
// Local names not used in reflection should not be in the final binary,
|
|
// even if they are embedded in a struct and become a field name.
|
|
type unexportedLocalObfuscated struct{ LocalObfuscatedA int }
|
|
type ExportedLocalObfuscated struct{ LocalObfuscatedB int }
|
|
type EmbeddingObfuscated struct {
|
|
unexportedLocalObfuscated
|
|
ExportedLocalObfuscated
|
|
}
|
|
// Ensure the types are kept in the binary. Use an anonymous type too.
|
|
_ = fmt.Sprintf("%#v", EmbeddingObfuscated{})
|
|
_ = fmt.Sprintf("%#v", struct{ ExportedLocalObfuscated }{})
|
|
|
|
// reflection can see all type names, even local ones, so they cannot be obfuscated.
|
|
{
|
|
type TypeOfNamedField struct{ NamedReflectionField int }
|
|
type TypeOfEmbeddedField struct{ EmbeddedReflectionField int }
|
|
type TypeOfParent struct {
|
|
ReflectionField TypeOfNamedField
|
|
TypeOfEmbeddedField
|
|
}
|
|
t := reflect.TypeOf(TypeOfParent{})
|
|
fmt.Println("TypeOfParent's own name:", t.Name())
|
|
namedField, _ := t.FieldByName("ReflectionField")
|
|
namedFieldField, _ := namedField.Type.FieldByName("NamedReflectionField")
|
|
fmt.Println("TypeOfParent named:",
|
|
namedField.Type.Name(),
|
|
namedFieldField.Name,
|
|
)
|
|
embedField, _ := t.FieldByName("TypeOfEmbeddedField")
|
|
embedFieldField, _ := embedField.Type.FieldByName("EmbeddedReflectionField")
|
|
fmt.Println("TypeOfParent embedded:",
|
|
embedField.Type.Name(),
|
|
embedFieldField.Name,
|
|
)
|
|
}
|
|
|
|
y := UnnamedStructFields{}
|
|
y.unexportedGoGoProto = new(struct {
|
|
mu sync.Mutex
|
|
extensionMap map[int32]EncodingT
|
|
})
|
|
|
|
fmt.Println(reflect.TypeOf(Connection{}))
|
|
}
|
|
|
|
type EmbeddingIndirect struct {
|
|
// With field names, to test selectors above.
|
|
With importedpkg.AliasIndirectNamedWithReflect
|
|
Without importedpkg.AliasIndirectNamedWithoutReflect
|
|
|
|
// Embedding used to crash garble, too.
|
|
importedpkg.AliasIndirectNamedWithReflect
|
|
}
|
|
|
|
func printfWithoutPackage(format string, v any) {
|
|
s := fmt.Sprintf(format, v)
|
|
if _, without, found := strings.Cut(s, "."); found {
|
|
s = without
|
|
}
|
|
fmt.Print(s)
|
|
}
|
|
|
|
type EncodingT struct {
|
|
Foo int
|
|
}
|
|
|
|
type RecursiveStruct struct {
|
|
*RecursiveStruct
|
|
list []RecursiveStruct
|
|
}
|
|
|
|
// This could crash or hang if we don't deal with loops.
|
|
var _ = reflect.TypeOf(RecursiveStruct{})
|
|
|
|
type GenericTemplateData struct {
|
|
Name string
|
|
Value string
|
|
Port int
|
|
}
|
|
|
|
func genericRenderTemplate[T any](src string, data T) error {
|
|
tmpl, err := template.New("").Parse(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return tmpl.Execute(os.Stdout, data)
|
|
}
|
|
|
|
type IndirectReflection struct {
|
|
ReflectionField string
|
|
}
|
|
|
|
func indirectReflection(v any) {
|
|
fmt.Println(reflect.TypeOf(v).Field(0).Name)
|
|
}
|
|
|
|
type VariadicReflection struct {
|
|
ReflectionField string
|
|
}
|
|
|
|
type FmtType struct {
|
|
FmtTypeField int
|
|
}
|
|
|
|
// copied from github.com/davecgh/go-spew, which reaches into reflect's internals
|
|
func testGoSpew() {
|
|
flagValOffset := func() uintptr {
|
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
|
if !ok {
|
|
panic("reflect.Value has no flag field")
|
|
}
|
|
return field.Offset
|
|
}()
|
|
|
|
type flag uintptr
|
|
flagField := func(v *reflect.Value) *flag {
|
|
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
|
}
|
|
|
|
type t0 int
|
|
var t struct {
|
|
A t0
|
|
t0
|
|
a t0
|
|
}
|
|
vA := reflect.ValueOf(t).FieldByName("A")
|
|
va := reflect.ValueOf(t).FieldByName("a")
|
|
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
|
flagvA := *flagField(&vA)
|
|
flagva := *flagField(&va)
|
|
flagvt0 := *flagField(&vt0)
|
|
|
|
if flagvA&flagva&flagvt0 == 0 {
|
|
panic("reflect.Value read-only flag has changed semantics")
|
|
}
|
|
|
|
type T0 int
|
|
var T struct {
|
|
A T0
|
|
T0
|
|
a T0
|
|
}
|
|
vA = reflect.ValueOf(T).FieldByName("A")
|
|
va = reflect.ValueOf(T).FieldByName("a")
|
|
vt0 = reflect.ValueOf(T).FieldByName("T0")
|
|
flagvA = *flagField(&vA)
|
|
flagva = *flagField(&va)
|
|
flagvt0 = *flagField(&vt0)
|
|
|
|
if flagvA&flagva&flagvt0 == 0 {
|
|
panic("reflect.Value read-only flag has changed semantics")
|
|
}
|
|
}
|
|
|
|
// encoding/x509 uses encoding/asn1, which uses reflect.
|
|
// In one place it depends on field names; that used to be broken by garble.
|
|
func testx509() {
|
|
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{Organization: []string{"Acme Co"}},
|
|
}
|
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = x509.ParseCertificate(derBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
type pingMsg struct {
|
|
Data string `sshtype:"192"`
|
|
}
|
|
|
|
type pongMsg struct {
|
|
Data string `sshtype:"193"`
|
|
}
|
|
|
|
// golang.org/x/crypto/ssh converts a reflected type to another type
|
|
func testSSH() {
|
|
var msg = pingMsg{
|
|
Data: "data",
|
|
}
|
|
json.Marshal(msg)
|
|
|
|
_ = pongMsg(msg)
|
|
}
|
|
|
|
// variations similar to ssh
|
|
type reflectedMsg struct {
|
|
Data string
|
|
}
|
|
|
|
type convertedMsg struct {
|
|
Data string
|
|
}
|
|
|
|
func reflectConvert() {
|
|
msg := reflectedMsg(convertedMsg{})
|
|
json.Marshal(msg)
|
|
}
|
|
|
|
type reflectedMsg2 struct {
|
|
Data string
|
|
}
|
|
|
|
type convertedMsg2 struct {
|
|
Data string
|
|
}
|
|
|
|
func unrelatedConvert() {
|
|
// only discoverable by rechecking the package
|
|
_ = convertedMsg2(reflectedMsg2{})
|
|
}
|
|
|
|
func reflectUnrelatedConv() {
|
|
var msg = reflectedMsg2{
|
|
Data: "data",
|
|
}
|
|
json.Marshal(msg)
|
|
|
|
}
|
|
|
|
type StatUser struct {
|
|
Id int64 `gorm:"primaryKey"`
|
|
User_Id int64
|
|
}
|
|
|
|
type StatCompUser struct {
|
|
Id int64 `gorm:"primaryKey"`
|
|
User_Id int64
|
|
}
|
|
|
|
type Transaction struct {
|
|
Statement Statement
|
|
}
|
|
|
|
type Statement struct {
|
|
Dest interface{}
|
|
Model string
|
|
}
|
|
|
|
func find(dest interface{}) {
|
|
tx := Transaction{}
|
|
|
|
tx.Statement.Dest = dest
|
|
execute(tx)
|
|
}
|
|
|
|
func findComp(dest interface{}) {
|
|
tx := Transaction{
|
|
Statement: Statement{
|
|
Dest: dest,
|
|
},
|
|
}
|
|
|
|
execute(tx)
|
|
}
|
|
|
|
func execute(db Transaction) {
|
|
stmt := db.Statement
|
|
|
|
v := reflect.TypeOf(stmt.Dest)
|
|
|
|
fmt.Println(v)
|
|
}
|
|
|
|
type UnnamedStructInterface interface {
|
|
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
|
|
}
|
|
}
|
|
|
|
func gobStruct() {
|
|
type gobAlias struct {
|
|
Security []map[string]struct {
|
|
List []string
|
|
Pad bool
|
|
}
|
|
}
|
|
alias := gobAlias{}
|
|
|
|
gob.NewEncoder(os.Stdout).Encode(alias)
|
|
|
|
alias.Security = make([]map[string]struct {
|
|
List []string
|
|
Pad bool
|
|
}, 0, len([]string{}))
|
|
}
|
|
|
|
func gobMap() {
|
|
type gobAlias struct {
|
|
Security map[string]struct {
|
|
List []string
|
|
Pad bool
|
|
}
|
|
}
|
|
alias := gobAlias{}
|
|
|
|
gob.NewEncoder(os.Stdout).Encode(alias)
|
|
|
|
alias.Security = make(map[string]struct {
|
|
List []string
|
|
Pad bool
|
|
}, len([]string{}))
|
|
}
|
|
|
|
func gobChan() {
|
|
type gobAlias struct {
|
|
Security chan struct {
|
|
List []string
|
|
Pad bool
|
|
}
|
|
}
|
|
|
|
alias := gobAlias{}
|
|
|
|
gob.NewEncoder(os.Stdout).Encode(alias)
|
|
|
|
alias.Security = make(chan struct {
|
|
List []string
|
|
Pad bool
|
|
}, len([]string{}))
|
|
}
|
|
|
|
type Connection struct {
|
|
MaxLen struct {
|
|
Varchar int
|
|
}
|
|
}
|
|
|
|
// NewConnection create a new connection from databaseURL string
|
|
func NewConnection() *Connection {
|
|
return &Connection{
|
|
MaxLen: struct {
|
|
Varchar int
|
|
}{
|
|
Varchar: 0x7FFF,
|
|
},
|
|
}
|
|
}
|
|
|
|
func closure() {
|
|
type gobAlias struct {
|
|
Security func() struct {
|
|
Pad bool
|
|
}
|
|
}
|
|
|
|
alias := gobAlias{}
|
|
|
|
gob.NewEncoder(os.Stdout).Encode(alias)
|
|
|
|
outer := func() func() struct{ Pad bool } {
|
|
return func() struct{ Pad bool } {
|
|
return struct{ Pad bool }{Pad: true}
|
|
}
|
|
}
|
|
|
|
alias.Security = outer()
|
|
}
|
|
|
|
-- importedpkg/imported.go --
|
|
package importedpkg
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"test/main/importedpkg/indirect"
|
|
"test/main/importedpkg2"
|
|
)
|
|
|
|
type ReflectTypeOf int
|
|
|
|
var _ = reflect.TypeOf(ReflectTypeOf(0))
|
|
|
|
type ReflectTypeOfIndirect int
|
|
|
|
var _ = reflect.TypeOf(new([]*ReflectTypeOfIndirect))
|
|
|
|
type ReflectValueOf struct {
|
|
ExportedField string
|
|
|
|
unexportedField string
|
|
}
|
|
|
|
func (r *ReflectValueOf) ExportedMethodName() string { return "method: " + r.ExportedField }
|
|
|
|
var ReflectValueOfVar = ReflectValueOf{ExportedField: "abc"}
|
|
|
|
var _ = reflect.TypeOf(ReflectValueOfVar)
|
|
|
|
type ReflectInDefined struct {
|
|
ExportedField2 int
|
|
|
|
unexportedField2 int
|
|
|
|
importedpkg2.ReflectInSiblingImport
|
|
}
|
|
|
|
var ReflectInDefinedVar = ReflectInDefined{ExportedField2: 9000}
|
|
|
|
var _ = reflect.TypeOf(ReflectInDefinedVar)
|
|
|
|
var _ = reflect.TypeOf([]*struct{ EmbeddingOuter }{})
|
|
|
|
type EmbeddingOuter struct {
|
|
EmbeddingInner
|
|
Anon struct {
|
|
AnonField int
|
|
}
|
|
}
|
|
|
|
type EmbeddingInner struct {
|
|
InnerField int
|
|
}
|
|
|
|
type UnnamedWithDownstreamReflect = struct {
|
|
DownstreamObfuscated string
|
|
}
|
|
|
|
type (
|
|
AliasIndirectNamedWithReflect = indirect.IndirectNamedWithReflect
|
|
AliasIndirectNamedWithoutReflect = indirect.IndirectNamedWithoutReflect
|
|
)
|
|
|
|
var _ = reflect.TypeOf(ReflectEmbeddingAlias{})
|
|
|
|
type ReflectEmbeddingAlias struct {
|
|
ReflectEmbeddedAlias
|
|
}
|
|
|
|
type ReflectEmbeddedAlias = ReflectEmbeddingNamed
|
|
|
|
type ReflectEmbeddingNamed struct{}
|
|
|
|
func VariadicReflect(x any, ys ...any) int {
|
|
_ = reflect.TypeOf(x)
|
|
|
|
for _, y := range ys {
|
|
_ = reflect.TypeOf(y)
|
|
}
|
|
|
|
return len(ys)
|
|
}
|
|
|
|
type ReflectUnnamedStruct int
|
|
|
|
func (ReflectUnnamedStruct) UnnamedStructMethod(s struct{ UnnamedStructField string }) {
|
|
fmt.Println(reflect.TypeOf(s))
|
|
}
|
|
|
|
-- importedpkg2/imported2.go --
|
|
package importedpkg2
|
|
|
|
type ReflectInSiblingImport struct {
|
|
SiblingObfuscated string
|
|
}
|
|
|
|
-- importedpkg/indirect/indirect.go --
|
|
package indirect
|
|
|
|
import "reflect"
|
|
|
|
var _ = reflect.TypeOf(IndirectNamedWithReflect{})
|
|
|
|
type IndirectNamedWithReflect struct {
|
|
IndirectUnobfuscated string
|
|
|
|
DuplicateFieldName int
|
|
}
|
|
|
|
type IndirectNamedWithoutReflect struct {
|
|
IndirectObfuscated string
|
|
|
|
DuplicateFieldName int
|
|
}
|
|
|
|
-- main.stdout --
|
|
9000
|
|
{5 0 {}}
|
|
ReflectTypeOf
|
|
ReflectTypeOfIndirect
|
|
ReflectValueOf{ExportedField:"abc", unexportedField:""}
|
|
[method: abc]
|
|
{"Foo":3}
|
|
Hello Dave.
|
|
name: test value: example port: 8080
|
|
{"InnerField":3,"Anon":{"AnonField":0}}
|
|
{downstream}
|
|
{sibling}
|
|
{{indirect-with 3} {indirect-without 4} { 0}}
|
|
IndirectNamedWithReflect{IndirectUnobfuscated:"indirect-with", DuplicateFieldName:3}
|
|
ReflectionField
|
|
{0}
|
|
VariadicReflection{ReflectionField:"variadic"}
|
|
*main.StatUser
|
|
*main.StatCompUser
|
|
struct { UnnamedStructField string }
|
|
TypeOfParent's own name: TypeOfParent
|
|
TypeOfParent named: TypeOfNamedField NamedReflectionField
|
|
TypeOfParent embedded: TypeOfEmbeddedField EmbeddedReflectionField
|
|
main.Connection
|