Note that due to the default stack size limitations on Solaris, tests were
removed from this patch as they won't pass by default. The fix was manually
verified outside of the main test suite.

From 0d1615b23f9a558aa0a1957b4c81596220eb8ec4 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.18] 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 #53708
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/+/417056
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
---
 src/go/parser/interface.go   |  10 ++-
 src/go/parser/parser.go      |  54 ++++++++++-
 src/go/parser/parser_test.go | 169 +++++++++++++++++++++++++++++++++++
 src/go/parser/resolver.go    |   9 +-
 4 files changed, 234 insertions(+), 8 deletions(-)

diff --git a/src/go/parser/interface.go b/src/go/parser/interface.go
index e4f8c281ea734..3eea729e05034 100644
--- a/libgo/go/go/parser/interface.go
+++ b/libgo/go/go/parser/interface.go
@@ -97,8 +97,11 @@ func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast
 	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)
 			}
 		}
 
@@ -203,8 +206,11 @@ func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ex
 	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 51a3c3e67f9d8..c34cceab0f03a 100644
--- a/libgo/go/go/parser/parser.go
+++ b/libgo/go/go/parser/parser.go
@@ -60,6 +60,10 @@ type parser struct {
 	inRhs   bool // if set, the parser is parsing a rhs expression
 
 	imports []*ast.ImportSpec // list of imports
+
+	// nestLev is used to track and limit the recursion depth
+	// during parsing.
+	nestLev int
 }
 
 func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) {
@@ -109,6 +113,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
@@ -221,8 +243,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) {
 	if p.trace {
@@ -1252,6 +1278,8 @@ func (p *parser) parseTypeInstance(typ ast.Expr) ast.Expr {
 }
 
 func (p *parser) tryIdentOrType() ast.Expr {
+	defer decNestLev(incNestLev(p))
+
 	switch p.tok {
 	case token.IDENT:
 		typ := p.parseTypeName(nil)
@@ -1664,7 +1692,13 @@ func (p *parser) parsePrimaryExpr(x ast.Expr) ast.Expr {
 	if x == nil {
 		x = p.parseOperand()
 	}
-	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)
 		switch p.tok {
 		case token.PERIOD:
 			p.next()
@@ -1724,6 +1758,8 @@ func (p *parser) parsePrimaryExpr(x ast.Expr) ast.Expr {
 }
 
 func (p *parser) parseUnaryExpr() ast.Expr {
+	defer decNestLev(incNestLev(p))
+
 	if p.trace {
 		defer un(trace(p, "UnaryExpr"))
 	}
@@ -1813,7 +1849,13 @@ func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int, check bool) ast.Expr {
 	if x == nil {
 		x = p.parseUnaryExpr()
 	}
-	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
@@ -2123,6 +2165,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"))
 	}
@@ -2426,6 +2470,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/resolver.go b/src/go/parser/resolver.go
index d66a194c12bb0..82ba48cd96962 100644
--- a/libgo/go/go/parser/resolver.go
+++ b/libgo/go/go/parser/resolver.go
@@ -54,6 +54,8 @@ func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, str
 	file.Unresolved = r.unresolved[0:i]
 }
 
+const maxScopeDepth int = 1e3
+
 type resolver struct {
 	handle  *token.File
 	declErr func(token.Pos, string)
@@ -85,16 +87,19 @@ func (r *resolver) sprintf(format string, args ...any) string {
 }
 
 func (r *resolver) openScope(pos token.Pos) {
+	r.depth++
+	if r.depth > maxScopeDepth {
+		panic(bailout{pos: pos, msg: "exceeded max scope depth during object resolution"})
+	}
 	if debugResolve {
 		r.trace("opening scope @%v", pos)
-		r.depth++
 	}
 	r.topScope = ast.NewScope(r.topScope)
 }
 
 func (r *resolver) closeScope() {
+	r.depth--
 	if debugResolve {
-		r.depth--
 		r.trace("closing scope")
 	}
 	r.topScope = r.topScope.Outer
