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.
		
		
		
		
		
			
		
			
				
	
	
		
			500 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Plaintext
		
	
			
		
		
	
	
			500 lines
		
	
	
		
			12 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.20
 | 
						|
-- garble_main.go --
 | 
						|
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/ecdsa"
 | 
						|
	"crypto/elliptic"
 | 
						|
	"crypto/rand"
 | 
						|
	"crypto/x509"
 | 
						|
	"crypto/x509/pkix"
 | 
						|
	"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 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
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
-- 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
 |