summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch
blob: b2ab5d0669f3d97c1e1bee99a4484a84f211dbda (plain)
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
From ba8788ebcead55e99e631c6a1157ad7b35535d11 Mon Sep 17 00:00:00 2001
From: Roland Shoemaker <bracewell@google.com>
Date: Wed, 15 Jun 2022 10:43:05 -0700
Subject: [PATCH] [release-branch.go1.17] go/parser: limit recursion depth

Limit nested parsing to 100,000, which prevents stack exhaustion when
parsing deeply nested statements, types, and expressions. Also limit
the scope depth to 1,000 during object resolution.

Thanks to Juho Nurminen of Mattermost for reporting this issue.

Fixes #53707
Updates #53616
Fixes CVE-2022-1962

Change-Id: I4d7b86c1d75d0bf3c7af1fdea91582aa74272c64
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1491025
Reviewed-by: Russ Cox <rsc@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
(cherry picked from commit 6a856f08d58e4b6705c0c337d461c540c1235c83)
Reviewed-on: https://go-review.googlesource.com/c/go/+/417070
Reviewed-by: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Knyszek <mknyszek@google.com>

Upstream-Status: Backport [https://github.com/golang/go/commit/ba8788ebcead55e99e631c6a1157ad7b35535d11]
CVE: CVE-2022-1962
Signed-off-by: Vivek Kumbhar <vkumbhar@mvista.com>
---
 src/go/parser/interface.go   |  10 ++-
 src/go/parser/parser.go      |  48 ++++++++--
 src/go/parser/parser_test.go | 169 +++++++++++++++++++++++++++++++++++
 3 files changed, 220 insertions(+), 7 deletions(-)

diff --git a/src/go/parser/interface.go b/src/go/parser/interface.go
index 54f9d7b..537b327 100644
--- a/src/go/parser/interface.go
+++ b/src/go/parser/interface.go
@@ -92,8 +92,11 @@ func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode)
 	defer func() {
 		if e := recover(); e != nil {
 			// resume same panic if it's not a bailout
-			if _, ok := e.(bailout); !ok {
+			bail, ok := e.(bailout)
+			if !ok {
 				panic(e)
+			} else if bail.msg != "" {
+				p.errors.Add(p.file.Position(bail.pos), bail.msg)
 			}
 		}
 
@@ -188,8 +191,11 @@ func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode M
 	defer func() {
 		if e := recover(); e != nil {
 			// resume same panic if it's not a bailout
-			if _, ok := e.(bailout); !ok {
+			bail, ok := e.(bailout)
+			if !ok {
 				panic(e)
+			} else if bail.msg != "" {
+				p.errors.Add(p.file.Position(bail.pos), bail.msg)
 			}
 		}
 		p.errors.Sort()
diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go
index 31a7398..586fe90 100644
--- a/src/go/parser/parser.go
+++ b/src/go/parser/parser.go
@@ -64,6 +64,10 @@ type parser struct {
 	unresolved []*ast.Ident      // unresolved identifiers
 	imports    []*ast.ImportSpec // list of imports
 
+	// nestLev is used to track and limit the recursion depth
+	// during parsing.
+	nestLev int
+	
 	// Label scopes
 	// (maintained by open/close LabelScope)
 	labelScope  *ast.Scope     // label scope for current function
@@ -236,6 +240,24 @@ func un(p *parser) {
 	p.printTrace(")")
 }
 
+// maxNestLev is the deepest we're willing to recurse during parsing
+const maxNestLev int = 1e5
+
+func incNestLev(p *parser) *parser {
+	p.nestLev++
+	if p.nestLev > maxNestLev {
+		p.error(p.pos, "exceeded max nesting depth")
+		panic(bailout{})
+	}
+	return p
+}
+
+// decNestLev is used to track nesting depth during parsing to prevent stack exhaustion.
+// It is used along with incNestLev in a similar fashion to how un and trace are used.
+func decNestLev(p *parser) {
+	p.nestLev--
+}
+
 // Advance to the next token.
 func (p *parser) next0() {
 	// Because of one-token look-ahead, print the previous token
@@ -348,8 +370,12 @@ func (p *parser) next() {
 	}
 }
 
-// A bailout panic is raised to indicate early termination.
-type bailout struct{}
+// A bailout panic is raised to indicate early termination. pos and msg are
+// only populated when bailing out of object resolution.
+type bailout struct {
+	pos token.Pos
+	msg string
+}
 
 func (p *parser) error(pos token.Pos, msg string) {
 	epos := p.file.Position(pos)
@@ -1030,6 +1056,8 @@ func (p *parser) parseChanType() *ast.ChanType {
 
 // If the result is an identifier, it is not resolved.
 func (p *parser) tryIdentOrType() ast.Expr {
+	defer decNestLev(incNestLev(p))
+
 	switch p.tok {
 	case token.IDENT:
 		return p.parseTypeName()
@@ -1609,7 +1637,13 @@ func (p *parser) parseBinaryExpr(lhs bool, prec1 int) ast.Expr {
 	}
 
 	x := p.parseUnaryExpr(lhs)
-	for {
+	// We track the nesting here rather than at the entry for the function,
+	// since it can iteratively produce a nested output, and we want to
+	// limit how deep a structure we generate.
+	var n int
+	defer func() { p.nestLev -= n }()
+	for n = 1; ; n++ {
+		incNestLev(p)
 		op, oprec := p.tokPrec()
 		if oprec < prec1 {
 			return x
@@ -1628,7 +1662,7 @@ func (p *parser) parseBinaryExpr(lhs bool, prec1 int) ast.Expr {
 // The result may be a type or even a raw type ([...]int). Callers must
 // check the result (using checkExpr or checkExprOrType), depending on
 // context.
-func (p *parser) parseExpr(lhs bool) ast.Expr {
+func (p *parser) parseExpr(lhs bool) ast.Expr {    
 	if p.trace {
 		defer un(trace(p, "Expression"))
 	}
@@ -1899,6 +1933,8 @@ func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) {
 }
 
 func (p *parser) parseIfStmt() *ast.IfStmt {
+	defer decNestLev(incNestLev(p))
+
 	if p.trace {
 		defer un(trace(p, "IfStmt"))
 	}
@@ -2214,6 +2250,8 @@ func (p *parser) parseForStmt() ast.Stmt {
 }
 
 func (p *parser) parseStmt() (s ast.Stmt) {
+	defer decNestLev(incNestLev(p))
+
 	if p.trace {
 		defer un(trace(p, "Statement"))
 	}
diff --git a/src/go/parser/parser_test.go b/src/go/parser/parser_test.go
index 25a374e..37a6a2b 100644
--- a/src/go/parser/parser_test.go
+++ b/src/go/parser/parser_test.go
@@ -10,6 +10,7 @@ import (
 	"go/ast"
 	"go/token"
 	"os"
+	"runtime"
 	"strings"
 	"testing"
 )
@@ -569,3 +570,171 @@ type x int // comment
 		t.Errorf("got %q, want %q", comment, "// comment")
 	}
 }
+
+var parseDepthTests = []struct {
+	name   string
+	format string
+	// multipler is used when a single statement may result in more than one
+	// change in the depth level, for instance "1+(..." produces a BinaryExpr
+	// followed by a UnaryExpr, which increments the depth twice. The test
+	// case comment explains which nodes are triggering the multiple depth
+	// changes.
+	parseMultiplier int
+	// scope is true if we should also test the statement for the resolver scope
+	// depth limit.
+	scope bool
+	// scopeMultiplier does the same as parseMultiplier, but for the scope
+	// depths.
+	scopeMultiplier int
+}{
+	// The format expands the part inside « » many times.
+	// A second set of brackets nested inside the first stops the repetition,
+	// so that for example «(«1»)» expands to (((...((((1))))...))).
+	{name: "array", format: "package main; var x «[1]»int"},
+	{name: "slice", format: "package main; var x «[]»int"},
+	{name: "struct", format: "package main; var x «struct { X «int» }»", scope: true},
+	{name: "pointer", format: "package main; var x «*»int"},
+	{name: "func", format: "package main; var x «func()»int", scope: true},
+	{name: "chan", format: "package main; var x «chan »int"},
+	{name: "chan2", format: "package main; var x «<-chan »int"},
+	{name: "interface", format: "package main; var x «interface { M() «int» }»", scope: true, scopeMultiplier: 2}, // Scopes: InterfaceType, FuncType
+	{name: "map", format: "package main; var x «map[int]»int"},
+	{name: "slicelit", format: "package main; var x = «[]any{«»}»", parseMultiplier: 2},             // Parser nodes: UnaryExpr, CompositeLit
+	{name: "arraylit", format: "package main; var x = «[1]any{«nil»}»", parseMultiplier: 2},         // Parser nodes: UnaryExpr, CompositeLit
+	{name: "structlit", format: "package main; var x = «struct{x any}{«nil»}»", parseMultiplier: 2}, // Parser nodes: UnaryExpr, CompositeLit
+	{name: "maplit", format: "package main; var x = «map[int]any{1:«nil»}»", parseMultiplier: 2},    // Parser nodes: CompositeLit, KeyValueExpr
+	{name: "dot", format: "package main; var x = «x.»x"},
+	{name: "index", format: "package main; var x = x«[1]»"},
+	{name: "slice", format: "package main; var x = x«[1:2]»"},
+	{name: "slice3", format: "package main; var x = x«[1:2:3]»"},
+	{name: "dottype", format: "package main; var x = x«.(any)»"},
+	{name: "callseq", format: "package main; var x = x«()»"},
+	{name: "methseq", format: "package main; var x = x«.m()»", parseMultiplier: 2}, // Parser nodes: SelectorExpr, CallExpr
+	{name: "binary", format: "package main; var x = «1+»1"},
+	{name: "binaryparen", format: "package main; var x = «1+(«1»)»", parseMultiplier: 2}, // Parser nodes: BinaryExpr, ParenExpr
+	{name: "unary", format: "package main; var x = «^»1"},
+	{name: "addr", format: "package main; var x = «& »x"},
+	{name: "star", format: "package main; var x = «*»x"},
+	{name: "recv", format: "package main; var x = «<-»x"},
+	{name: "call", format: "package main; var x = «f(«1»)»", parseMultiplier: 2},    // Parser nodes: Ident, CallExpr
+	{name: "conv", format: "package main; var x = «(*T)(«1»)»", parseMultiplier: 2}, // Parser nodes: ParenExpr, CallExpr
+	{name: "label", format: "package main; func main() { «Label:» }"},
+	{name: "if", format: "package main; func main() { «if true { «» }»}", parseMultiplier: 2, scope: true, scopeMultiplier: 2}, // Parser nodes: IfStmt, BlockStmt. Scopes: IfStmt, BlockStmt
+	{name: "ifelse", format: "package main; func main() { «if true {} else » {} }", scope: true},
+	{name: "switch", format: "package main; func main() { «switch { default: «» }»}", scope: true, scopeMultiplier: 2},               // Scopes: TypeSwitchStmt, CaseClause
+	{name: "typeswitch", format: "package main; func main() { «switch x.(type) { default: «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: TypeSwitchStmt, CaseClause
+	{name: "for0", format: "package main; func main() { «for { «» }» }", scope: true, scopeMultiplier: 2},                            // Scopes: ForStmt, BlockStmt
+	{name: "for1", format: "package main; func main() { «for x { «» }» }", scope: true, scopeMultiplier: 2},                          // Scopes: ForStmt, BlockStmt
+	{name: "for3", format: "package main; func main() { «for f(); g(); h() { «» }» }", scope: true, scopeMultiplier: 2},              // Scopes: ForStmt, BlockStmt
+	{name: "forrange0", format: "package main; func main() { «for range x { «» }» }", scope: true, scopeMultiplier: 2},               // Scopes: RangeStmt, BlockStmt
+	{name: "forrange1", format: "package main; func main() { «for x = range z { «» }» }", scope: true, scopeMultiplier: 2},           // Scopes: RangeStmt, BlockStmt
+	{name: "forrange2", format: "package main; func main() { «for x, y = range z { «» }» }", scope: true, scopeMultiplier: 2},        // Scopes: RangeStmt, BlockStmt
+	{name: "go", format: "package main; func main() { «go func() { «» }()» }", parseMultiplier: 2, scope: true},                      // Parser nodes: GoStmt, FuncLit
+	{name: "defer", format: "package main; func main() { «defer func() { «» }()» }", parseMultiplier: 2, scope: true},                // Parser nodes: DeferStmt, FuncLit
+	{name: "select", format: "package main; func main() { «select { default: «» }» }", scope: true},
+}
+
+// split splits pre«mid»post into pre, mid, post.
+// If the string does not have that form, split returns x, "", "".
+func split(x string) (pre, mid, post string) {
+	start, end := strings.Index(x, "«"), strings.LastIndex(x, "»")
+	if start < 0 || end < 0 {
+		return x, "", ""
+	}
+	return x[:start], x[start+len("«") : end], x[end+len("»"):]
+}
+
+func TestParseDepthLimit(t *testing.T) {
+	if runtime.GOARCH == "wasm" {
+		t.Skip("causes call stack exhaustion on js/wasm")
+	}
+	for _, tt := range parseDepthTests {
+		for _, size := range []string{"small", "big"} {
+			t.Run(tt.name+"/"+size, func(t *testing.T) {
+				n := maxNestLev + 1
+				if tt.parseMultiplier > 0 {
+					n /= tt.parseMultiplier
+				}
+				if size == "small" {
+					// Decrease the number of statements by 10, in order to check
+					// that we do not fail when under the limit. 10 is used to
+					// provide some wiggle room for cases where the surrounding
+					// scaffolding syntax adds some noise to the depth that changes
+					// on a per testcase basis.
+					n -= 10
+				}
+
+				pre, mid, post := split(tt.format)
+				if strings.Contains(mid, "«") {
+					left, base, right := split(mid)
+					mid = strings.Repeat(left, n) + base + strings.Repeat(right, n)
+				} else {
+					mid = strings.Repeat(mid, n)
+				}
+				input := pre + mid + post
+
+				fset := token.NewFileSet()
+				_, err := ParseFile(fset, "", input, ParseComments|SkipObjectResolution)
+				if size == "small" {
+					if err != nil {
+						t.Errorf("ParseFile(...): %v (want success)", err)
+					}
+				} else {
+					expected := "exceeded max nesting depth"
+					if err == nil || !strings.HasSuffix(err.Error(), expected) {
+						t.Errorf("ParseFile(...) = _, %v, want %q", err, expected)
+					}
+				}
+			})
+		}
+	}
+}
+
+func TestScopeDepthLimit(t *testing.T) {
+	if runtime.GOARCH == "wasm" {
+		t.Skip("causes call stack exhaustion on js/wasm")
+	}
+	for _, tt := range parseDepthTests {
+		if !tt.scope {
+			continue
+		}
+		for _, size := range []string{"small", "big"} {
+			t.Run(tt.name+"/"+size, func(t *testing.T) {
+				n := maxScopeDepth + 1
+				if tt.scopeMultiplier > 0 {
+					n /= tt.scopeMultiplier
+				}
+				if size == "small" {
+					// Decrease the number of statements by 10, in order to check
+					// that we do not fail when under the limit. 10 is used to
+					// provide some wiggle room for cases where the surrounding
+					// scaffolding syntax adds some noise to the depth that changes
+					// on a per testcase basis.
+					n -= 10
+				}
+
+				pre, mid, post := split(tt.format)
+				if strings.Contains(mid, "«") {
+					left, base, right := split(mid)
+					mid = strings.Repeat(left, n) + base + strings.Repeat(right, n)
+				} else {
+					mid = strings.Repeat(mid, n)
+				}
+				input := pre + mid + post
+
+				fset := token.NewFileSet()
+				_, err := ParseFile(fset, "", input, DeclarationErrors)
+				if size == "small" {
+					if err != nil {
+						t.Errorf("ParseFile(...): %v (want success)", err)
+					}
+				} else {
+					expected := "exceeded max scope depth during object resolution"
+					if err == nil || !strings.HasSuffix(err.Error(), expected) {
+						t.Errorf("ParseFile(...) = _, %v, want %q", err, expected)
+					}
+				}
+			})
+		}
+	}
+}
-- 
2.30.2