You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
garble/testdata/script/reflect.txtar

606 lines
14 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.22
-- 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"
"unsafe"
"strings"
"sync"
"text/template"
"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.
// 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
})
}
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 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{}))
}
-- 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.
{"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