-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhardening_test.go
More file actions
167 lines (156 loc) · 6.11 KB
/
Copy pathhardening_test.go
File metadata and controls
167 lines (156 loc) · 6.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Embedded hardening boundary regression -- scripts must not trigger a host OOM crash
// via stdlib (12 §4.9: keeping the host process alive takes priority over byte identity).
// These inputs OOM-crash the process under PUC 5.1.5 / gopher-lua; wangshu proactively
// fail-fasts and returns a Lua error.
package wangshu_test
import (
"strings"
"testing"
"github.com/Liam0205/wangshu"
)
func TestHardening_StringRepOverflow(t *testing.T) {
st := wangshu.NewState(wangshu.Options{})
// The case here used to be string.rep("k", 1e14), on the premise that it
// "would allocate 100T bytes". That premise was wrong: PUC reads the count
// with luaL_checkint, so 1e14 narrows to 276447232 and real lua5.1 returns a
// 276 MB string successfully -- the guard was firing on something PUC
// completes, which is a divergence rather than hardening.
//
// A request that survives narrowing is the real hazard: a 1000-byte string
// repeated 2^31-1 times is 2 TB, and lua5.1 does hang on it. That is what
// this now asserts.
prog, _ := wangshu.Compile([]byte(
`return pcall(string.rep, string.rep("k", 1000), 2147483647)`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Bool() != false {
t.Errorf("pcall ok=%s, want false (should fail-fast)", r[0].Display())
}
if !strings.Contains(r[1].Str(), "string length overflow") {
t.Errorf("err = %q, want 'string length overflow'", r[1].Str())
}
}
func TestHardening_StringRepWithinLimit(t *testing.T) {
// within a reasonable range it works normally
st := wangshu.NewState(wangshu.Options{})
prog, _ := wangshu.Compile([]byte(`return string.rep("ab", 3)`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Str() != "ababab" {
t.Errorf("got %q", r[0].Str())
}
}
func TestHardening_StringFormatWidthOverflow(t *testing.T) {
st := wangshu.NewState(wangshu.Options{})
// %.99999999999d would make fmt.Sprintf allocate a huge number of bytes
prog, _ := wangshu.Compile([]byte(`return pcall(string.format, "%.99999999999d", 1)`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Bool() != false {
t.Errorf("pcall ok=%s, want false", r[0].Display())
}
if !strings.Contains(r[1].Str(), "precision") && !strings.Contains(r[1].Str(), "width") {
t.Errorf("err = %q, want width/precision overflow", r[1].Str())
}
}
func TestHardening_StringFormatNormalWidth(t *testing.T) {
// normal width/precision works
st := wangshu.NewState(wangshu.Options{})
prog, _ := wangshu.Compile([]byte(`return string.format("%5.2f", 3.14159)`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Str() != " 3.14" {
t.Errorf("got %q", r[0].Str())
}
}
func TestHardening_TableConcatRangeOverflow(t *testing.T) {
st := wangshu.NewState(wangshu.Options{})
// This test used to assert a "range too large" guard on j, with the premise
// that "j = 1e14 makes the concat loop exhaust memory". The premise was
// wrong: concat stops at the first element that is not a string or number,
// and a 3-element table has nil at index 4, so the walk ends there whatever
// j says. PUC reports "invalid value (nil) at index 4" in microseconds, and
// the guard was replacing that matching error with a different one -- three
// audit rounds in a row flagged shapes it rejected that PUC completes
// instantly.
//
// So the assertion is now the real behaviour: the DATA bounds the walk, and
// the error names the first non-concatenable index.
prog, _ := wangshu.Compile([]byte(`return pcall(table.concat, {1,2,3}, ",", 1, 100000000000000)`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Bool() != false {
t.Errorf("pcall ok=%s, want false", r[0].Display())
}
if !strings.Contains(r[1].Str(), "invalid value (nil) at index 4") {
t.Errorf("err = %q, want the first-nil-index error", r[1].Str())
}
}
func TestHardening_TableConcatNaNRange(t *testing.T) {
// NaN range: NaN-X=NaN bypasses the range check, and Go int(NaN)=MIN_INT64 differs
// from PUC int(NaN)=0. After normalizing NaN→0 it takes the normal "invalid value at
// index" path (matching PUC behavior), without crashing or bypassing.
st := wangshu.NewState(wangshu.Options{})
prog, _ := wangshu.Compile([]byte(`return pcall(table.concat, {1,2,3}, ",", 0/0)`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
// NaN→0, t[0] is nil → invalid value (matching PUC); should not be OOM or range too large
if r[0].Bool() != false {
t.Errorf("pcall ok=%s, want false", r[0].Display())
}
if !strings.Contains(r[1].Str(), "invalid value") {
t.Errorf("err = %q, want 'invalid value' (NaN→0 path)", r[1].Str())
}
}
func TestHardening_TableConcatNormal(t *testing.T) {
st := wangshu.NewState(wangshu.Options{})
prog, _ := wangshu.Compile([]byte(`return table.concat({"a","b","c"}, "-")`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Str() != "a-b-c" {
t.Errorf("got %q", r[0].Str())
}
}
func TestHardening_TableLargeIntKey(t *testing.T) {
// fuzz corpus testdata/fuzz/FuzzCompileRun/5095a0fd13d76273:
// `t={} t[3333170000]=""` triggers rehash → the `for (1<<b) < u` loop in countIntKey
// spins forever (uint32(1)<<32=0, so b stays < u). Looks like OOM but is actually a CPU
// infinite loop.
// Fix: add a b<31 guard to the loop + cap bestASize at 1<<24 (consistent with the
// mainline hardening threshold).
st := wangshu.NewState(wangshu.Options{})
prog, _ := wangshu.Compile([]byte(`local t={} t[3333170000]="" return "ok"`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Str() != "ok" {
t.Errorf("got %q, want 'ok' (大整数 key 应正常落 hash 段)", r[0].Str())
}
}
func TestHardening_TableUint32MaxKey(t *testing.T) {
// uint32 boundary value 4294967295 = 2^32-1, confirming the b=31 guard does not leak + no infinite loop.
st := wangshu.NewState(wangshu.Options{})
prog, _ := wangshu.Compile([]byte(`local t={} t[4294967295]="x" return t[4294967295]`), "h")
r, err := prog.Run(st)
if err != nil {
t.Fatalf("run: %v", err)
}
if r[0].Str() != "x" {
t.Errorf("got %q, want 'x'", r[0].Str())
}
}