Decrease binary size for -literals

Only string literals over 8 characters in length are now being
obfuscated. This leads to around 20% smaller binaries when building with
-literals.

Fixes #618
pull/645/head
lu4p 1 year ago
parent 5dd6b2dc43
commit 5b2193351f

@ -15,18 +15,20 @@ import (
ah "mvdan.cc/garble/internal/asthelper"
)
// maxSizeBytes is the limit, in bytes, of the size of string-like literals
// minSize is the lower bound limit, of the size of string-like literals
// which we will obfuscate. This is needed in order for binary size to stay relatively
// moderate, this also decreases the likelihood for performance slowdowns.
const minSize = 8
// maxSize is the upper bound limit, of the size of string-like literals
// which we will obfuscate. This is important, because otherwise garble can take
// a very long time to obfuscate huge code-generated literals, such as those
// corresponding to large assets.
//
// Note that this is the size of the literal in source code. For example, "\xab"
// counts as four bytes.
//
// If someone truly wants to obfuscate those, they should do that when they
// generate the code, not at build time. Plus, with Go 1.16 that technique
// should largely stop being used.
const maxSizeBytes = 2 << 10 // KiB
const maxSize = 2 << 10 // KiB
// Obfuscate replaces literals with obfuscated anonymous functions.
func Obfuscate(obfRand *mathrand.Rand, file *ast.File, info *types.Info, linkStrings map[*types.Var]string) *ast.File {
@ -63,7 +65,7 @@ func Obfuscate(obfRand *mathrand.Rand, file *ast.File, info *types.Info, linkStr
if typeAndValue.Type == types.Typ[types.String] && typeAndValue.Value != nil {
value := constant.StringVal(typeAndValue.Value)
if len(value) == 0 || len(value) > maxSizeBytes {
if len(value) < minSize || len(value) > maxSize {
return true
}
@ -119,10 +121,9 @@ func Obfuscate(obfRand *mathrand.Rand, file *ast.File, info *types.Info, linkStr
// calls the appropriate obfuscation method, returning a new node that should
// be used to replace it.
//
// If the input is not a byte slice or array, the node is returned as-is and
// the second return value will be false.
// If the input node cannot be obfuscated nil is returned.
func handleCompositeLiteral(obfRand *mathrand.Rand, isPointer bool, node *ast.CompositeLit, info *types.Info) ast.Node {
if len(node.Elts) == 0 || len(node.Elts) > maxSizeBytes {
if len(node.Elts) < minSize || len(node.Elts) > maxSize {
return nil
}

@ -2,8 +2,8 @@ garble -literals build
exec ./main$exe
cmp stderr main.stderr
binsubstr main$exe 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const'
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'obfuscated with shadowed builtins' '1: literal in' 'an array' '2: literal in' 'a slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this'
binsubstr main$exe 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const' 'sz<min'
! binsubstr main$exe 'garbleDecrypt' 'Lorem Ipsum' 'dolor sit amet' 'first assign' 'second assign' 'First Line' 'Second Line' 'secret map value' 'obfuscated with shadowed builtins' '1: literal in' 'an secret array' '2: literal in' 'a secret slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this'
[short] stop # checking that the build is reproducible is slow
@ -17,7 +17,7 @@ bincmp main$exe main_old$exe
go build
exec ./main$exe
cmp stderr main.stderr
binsubstr main$exe 'Lorem' 'dolor' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String'
binsubstr main$exe 'Lorem Ipsum' 'dolor sit amet' 'second assign' 'First Line' 'Second Line' 'secret map value' 'obfuscated with shadowed builtins' '1: literal in' 'an secret array' '2: literal in' 'a secret slice' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'testMap1 key' 'Obfuscate this block' 'also obfuscate this'
# Generate and write random literals into a separate file.
# Some of them will be huge; assuming that we don't try to obfuscate them, the
@ -70,7 +70,7 @@ type structTest struct {
}
const (
cnst string = "Lorem"
cnst string = "Lorem Ipsum"
multiline string = `First Line
Second Line`
)
@ -102,19 +102,21 @@ type typeAlias [arrayLen]byte
func main() {
empty := ""
tooSmall := "sz<min"
localVar := "dolor"
localVar := "dolor sit amet"
reassign := "first assign"
reassign = "second assign"
add := "total" + " string"
add := "totally long" + "unnecessarily added string"
println(cnst, boolean)
println(multiline, add)
println(localVar)
println(reassign)
println(empty)
println(tooSmall)
x := structTest{
field: "to obfuscate",
@ -122,15 +124,15 @@ func main() {
}
lambda := func() string {
return "😅 😅"
return "happy faces 😅 😅"
}()
println(lambda)
println(x.field, x.anotherField)
testMap := map[string]string{"map key": "map value"}
testMap["map key"] = "new value"
println(testMap["map key"])
testMap := map[string]string{"secret map key": "secret map value"}
testMap["secret map key"] = "secret new value"
println(testMap["secret map key"])
println("another literal")
println(mixedBlock, iotaBlock)
println(i, foo, bar)
@ -139,9 +141,9 @@ func main() {
byteTest()
shadowTest()
strArray := [2]string{"1: literal in", "an array"}
strArray := [2]string{"1: literal in", "an secret array"}
println(strArray[0], strArray[1])
strSlice := []string{"2: literal in", "a slice"}
strSlice := []string{"2: literal in", "a secret slice"}
println(strSlice[0], strSlice[1])
emptyStrSlice := []string{""}
print(emptyStrSlice[0])
@ -192,32 +194,32 @@ func typedTest() {
// constantTest tests that string constants which need to be constant are skipped
func constantTest() {
const a = "foo" // skip
const a = "foo bar bar" // skip
const length = len(a)
const b = "bar" // skip
const b = "bar foo foo" // skip
type T [len(b)]byte
const c = "foo" // skip
const c = "foo bar bar" // skip
var _ [len(c)]byte
const d = "foo" // skip
var arr = [5]string{len(d): "foo"}
const d = "foo bar bar" // skip
var arr = [12]string{len(d): "foo bar bar"}
for _, elm := range arr {
if elm != "" {
println(elm)
}
}
const e = "foo" // skip
var slice = []string{len(e): "foo"}
const e = "foo bar bar" // skip
var slice = []string{len(e): "foo bar bar"}
for _, elm := range slice {
if elm != "" {
println(elm)
}
}
const f = "foo" // skip
const f = "foo bar bar" // skip
const i = length + len(f)
println(length, i)
@ -234,57 +236,57 @@ func constantTest() {
// We should figure out a way to test for the byte sequences.
// For now, we manually tested these when they got added.
func byteTest() {
a := []byte{12, 13}
a := []byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
for _, elm := range a {
print(elm, ",")
}
println()
var b = []byte{12, 13}
var b = []byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
for _, elm := range b {
print(elm, ",")
}
println()
var c = [2]byte{12, 13}
var c = [10]byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
for _, elm := range c {
print(elm, ",")
}
println()
d := func() [4]byte {
return [4]byte{12, 13}
d := func() [12]byte {
return [12]byte{12, 13, 12, 13, 12, 13, 12, 13, 12, 13}
}()
for _, elm := range d {
print(elm, ",")
}
println()
e := []byte{0x43, 11_1, 0b01101101, 'p', 'l', 'e', 'x'}
e := []byte{0x43, 11_1, 0b01101101, 'p', 'l', 'e', 'x', ' ', 'l', 'i', 't'}
println(string(e))
// Testing for issue #520.
func(s []byte) {
print(string(s))
}([]byte("chungus"))
}([]byte("big chungus"))
println()
func(s *[]byte) {
print(string(*s))
}(&[]byte{99, 104, 117, 110, 103, 117, 115})
}(&[]byte{99, 104, 117, 110, 103, 117, 115, '!', '!'})
println()
func(s [7]byte) {
func(s [9]byte) {
for _, elm := range s {
print(elm, ",")
}
}([7]byte{99, 104, 117, 110, 103, 117, 115})
}([9]byte{99, 104, 117, 110, 103, 117, 115, 117, 115})
println()
func(s *[7]byte) {
func(s *[9]byte) {
for _, elm := range s {
print(elm, ",")
}
}(&[7]byte{99, 104, 117, 110, 103, 117, 115})
}(&[9]byte{99, 104, 117, 110, 103, 117, 115, 117, 115})
println()
}
@ -330,57 +332,58 @@ type ImportedType int
package main
//go:noinline
func str0() { println("foo") }
func str0() { println("foo bar bar") }
//go:noinline
func str1() { println("foo") }
func str1() { println("foo bar bar") }
//go:noinline
func str2() { println("foo") }
func str2() { println("foo bar bar") }
//go:noinline
func str3() { println("foo") }
func str3() { println("foo bar bar") }
//go:noinline
func str4() { println("foo") }
func str4() { println("foo bar bar") }
//go:noinline
func arr0() { println(len([...]byte{0, 1, 2})) }
func arr0() { println(len([...]byte{0, 1, 2, 3, 4, 5, 6, 7, 8})) }
//go:noinline
func arr1() { println(len([...]byte{0, 1, 2})) }
func arr1() { println(len([...]byte{0, 1, 2, 3, 4, 5, 6, 7, 8})) }
//go:noinline
func slc0() { println([]byte{0, 1, 2}) }
func slc0() { println([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8}) }
//go:noinline
func slc1() { println([]byte{0, 1, 2}) }
func slc1() { println([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8}) }
//go:noinline
func str5() { println("foo") }
func str5() { println("foo bar bar") }
//go:noinline
func str6() { println("foo") }
func str6() { println("foo bar bar") }
//go:noinline
func str7() { println("foo") }
func str7() { println("foo bar bar") }
//go:noinline
func str8() { println("foo") }
func str8() { println("foo bar bar") }
//go:noinline
func str9() { println("foo") }
func str9() { println("foo bar bar") }
-- main.stderr --
Lorem true
Lorem Ipsum true
First Line
Second Line total string
dolor
Second Line totally longunnecessarily added string
dolor sit amet
second assign
😅 😅
sz<min
happy faces 😅 😅
to obfuscate also obfuscate
new value
secret new value
another literal
Obfuscate this block also obfuscate this
1 0 1
@ -390,19 +393,19 @@ stringTypeField String stringTypeField strType
stringType lambda func return
stringType func param
stringType return
foo
foo
3 6
12,13,
12,13,
12,13,
12,13,0,0,
Complex
chungus
chungus
99,104,117,110,103,117,115,
99,104,117,110,103,117,115,
foo bar bar
foo bar bar
11 22
12,13,12,13,12,13,12,13,12,13,
12,13,12,13,12,13,12,13,12,13,
12,13,12,13,12,13,12,13,12,13,
12,13,12,13,12,13,12,13,12,13,0,0,
Complex lit
big chungus
chungus!!
99,104,117,110,103,117,115,117,115,
99,104,117,110,103,117,115,117,115,
obfuscated with shadowed builtins (vars)
obfuscated with shadowed builtins (types)
1: literal in an array
2: literal in a slice
1: literal in an secret array
2: literal in a secret slice

Loading…
Cancel
Save