diff mbox series

[dunfell] go: fix CVE-2022-1962 go/parser stack exhaustion in all Parse* functions

Message ID 20221221163216.1758961-1-vkumbhar@gmail.com
State New, archived
Headers show
Series [dunfell] go: fix CVE-2022-1962 go/parser stack exhaustion in all Parse* functions | expand

Commit Message

Vivek Kumbhar Dec. 21, 2022, 4:32 p.m. UTC
From: Vivek Kumbhar <vkumbhar@mvista.com>

Signed-off-by: Vivek Kumbhar <vkumbhar@mvista.com>
---
 meta/recipes-devtools/go/go-1.14.inc          |   1 +
 .../go/go-1.14/CVE-2022-1962.patch            | 421 ++++++++++++++++++
 2 files changed, 422 insertions(+)
 create mode 100644 meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch

Comments

Steve Sakoman Dec. 22, 2022, 3:01 p.m. UTC | #1
On Wed, Dec 21, 2022 at 6:32 AM vkumbhar <vkumbhar@mvista.com> wrote:
>
> From: Vivek Kumbhar <vkumbhar@mvista.com>
>
> Signed-off-by: Vivek Kumbhar <vkumbhar@mvista.com>
> ---
>  meta/recipes-devtools/go/go-1.14.inc          |   1 +
>  .../go/go-1.14/CVE-2022-1962.patch            | 421 ++++++++++++++++++
>  2 files changed, 422 insertions(+)
>  create mode 100644 meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch
>
> diff --git a/meta/recipes-devtools/go/go-1.14.inc b/meta/recipes-devtools/go/go-1.14.inc
> index cec37c1b09..0361bc763b 100644
> --- a/meta/recipes-devtools/go/go-1.14.inc
> +++ b/meta/recipes-devtools/go/go-1.14.inc
> @@ -49,6 +49,7 @@ SRC_URI += "\
>      file://CVE-2022-24921.patch \
>      file://CVE-2022-28131.patch \
>      file://CVE-2022-28327.patch \
> +    file://CVE-2022-1962.patch \

This patch will not apply, perhaps a conflict with "golang:
CVE-2022-41715 regexp/syntax: limit memory used by parsing regexps"
which is in the latest pull request?

ERROR: go-native-1.14.15-r0 do_patch: Applying patch
'CVE-2022-1962.patch' on target directory
'/home/steve/builds/poky-contrib/build/tmp/work/x86_64-linux/go-native/1.14.15-r0/go'
Command Error: 'quilt --quiltrc
/home/steve/builds/poky-contrib/build/tmp/work/x86_64-linux/go-native/1.14.15-r0/recipe-sysroot-native/etc/quiltrc
push' exited with 0  Output:
Applying patch CVE-2022-1962.patch
patching file src/go/parser/interface.go
Hunk #1 succeeded at 92 (offset -5 lines).
Hunk #2 succeeded at 191 (offset -15 lines).
patching file src/go/parser/parser.go
Hunk #1 FAILED at 60.
Hunk #2 succeeded at 236 (offset 126 lines).
Hunk #3 succeeded at 366 with fuzz 1 (offset 126 lines).
Hunk #4 succeeded at 1052 with fuzz 2 (offset -89 lines).
Hunk #5 FAILED at 1555.
Hunk #6 FAILED at 1615.
Hunk #7 FAILED at 1697.
Hunk #8 succeeded at 1923 (offset -63 lines).
Hunk #9 succeeded at 2240 (offset -51 lines).
4 out of 9 hunks FAILED -- rejects in file src/go/parser/parser.go
patching file src/go/parser/parser_test.go
Hunk #1 FAILED at 10.
Hunk #2 succeeded at 569 (offset -8 lines).
1 out of 2 hunks FAILED -- rejects in file src/go/parser/parser_test.go
can't find file to patch at input line 375
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|diff --git a/src/go/parser/resolver.go b/src/go/parser/resolver.go
|index cf92c7e4f5..f55bdb7f17 100644
|--- a/src/go/parser/resolver.go
|+++ b/src/go/parser/resolver.go
--------------------------
No file to patch.  Skipping patch.
5 out of 5 hunks ignored
Patch CVE-2022-1962.patch does not apply (enforce with -f)

Steve

>  "
>
>  SRC_URI_append_libc-musl = " file://0009-ld-replace-glibc-dynamic-linker-with-musl.patch"
> diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch b/meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch
> new file mode 100644
> index 0000000000..ceabc9057a
> --- /dev/null
> +++ b/meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch
> @@ -0,0 +1,421 @@
> +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      |  54 ++++++++++-
> + src/go/parser/parser_test.go | 169 +++++++++++++++++++++++++++++++++++
> + src/go/parser/resolver.go    |   9 ++
> + 4 files changed, 236 insertions(+), 6 deletions(-)
> +
> +diff --git a/src/go/parser/interface.go b/src/go/parser/interface.go
> +index 85486d2f4b..eae429e6ef 100644
> +--- a/src/go/parser/interface.go
> ++++ b/src/go/parser/interface.go
> +@@ -97,8 +97,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)
> +                       }
> +               }
> +
> +@@ -203,8 +206,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 f10c8650af..2c42b9f8cc 100644
> +--- a/src/go/parser/parser.go
> ++++ b/src/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) {
> +@@ -110,6 +114,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
> +@@ -222,8 +244,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 {
> +@@ -1119,6 +1145,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)
> +@@ -1531,7 +1559,13 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
> +       }
> +
> +       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()
> +@@ -1591,6 +1625,8 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
> + }
> +
> + func (p *parser) parseUnaryExpr() ast.Expr {
> ++      defer decNestLev(incNestLev(p))
> ++
> +       if p.trace {
> +               defer un(trace(p, "UnaryExpr"))
> +       }
> +@@ -1673,7 +1709,13 @@ func (p *parser) parseBinaryExpr(prec1 int) ast.Expr {
> +       }
> +
> +       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
> +@@ -1962,6 +2004,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"))
> +       }
> +@@ -2265,6 +2309,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 a4f882d368..1a46c87866 100644
> +--- a/src/go/parser/parser_test.go
> ++++ b/src/go/parser/parser_test.go
> +@@ -10,6 +10,7 @@ import (
> +       "go/ast"
> +       "go/token"
> +       "io/fs"
> ++      "runtime"
> +       "strings"
> +       "testing"
> + )
> +@@ -577,3 +578,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)
> ++                                      }
> ++                              }
> ++                      })
> ++              }
> ++      }
> ++}
> +diff --git a/src/go/parser/resolver.go b/src/go/parser/resolver.go
> +index cf92c7e4f5..f55bdb7f17 100644
> +--- a/src/go/parser/resolver.go
> ++++ b/src/go/parser/resolver.go
> +@@ -25,6 +25,7 @@ func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, str
> +               declErr:  declErr,
> +               topScope: pkgScope,
> +               pkgScope: pkgScope,
> ++              depth:    1,
> +       }
> +
> +       for _, decl := range file.Decls {
> +@@ -53,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)
> +@@ -61,6 +64,7 @@ type resolver struct {
> +       pkgScope   *ast.Scope   // pkgScope.Outer == nil
> +       topScope   *ast.Scope   // top-most scope; may be pkgScope
> +       unresolved []*ast.Ident // unresolved identifiers
> ++      depth      int          // scope depth
> +
> +       // Label scopes
> +       // (maintained by open/close LabelScope)
> +@@ -83,6 +87,10 @@ func (r *resolver) sprintf(format string, args ...interface{}) 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.dump("opening scope @%v", pos)
> +       }
> +@@ -90,6 +98,7 @@ func (r *resolver) openScope(pos token.Pos) {
> + }
> +
> + func (r *resolver) closeScope() {
> ++      r.depth--
> +       if debugResolve {
> +               r.dump("closing scope")
> +       }
> +--
> +2.30.2
> +
> --
> 2.30.2
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#174921): https://lists.openembedded.org/g/openembedded-core/message/174921
> Mute This Topic: https://lists.openembedded.org/mt/95809427/3620601
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [steve@sakoman.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
diff mbox series

Patch

diff --git a/meta/recipes-devtools/go/go-1.14.inc b/meta/recipes-devtools/go/go-1.14.inc
index cec37c1b09..0361bc763b 100644
--- a/meta/recipes-devtools/go/go-1.14.inc
+++ b/meta/recipes-devtools/go/go-1.14.inc
@@ -49,6 +49,7 @@  SRC_URI += "\
     file://CVE-2022-24921.patch \
     file://CVE-2022-28131.patch \
     file://CVE-2022-28327.patch \
+    file://CVE-2022-1962.patch \
 "
 
 SRC_URI_append_libc-musl = " file://0009-ld-replace-glibc-dynamic-linker-with-musl.patch"
diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch b/meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch
new file mode 100644
index 0000000000..ceabc9057a
--- /dev/null
+++ b/meta/recipes-devtools/go/go-1.14/CVE-2022-1962.patch
@@ -0,0 +1,421 @@ 
+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      |  54 ++++++++++-
+ src/go/parser/parser_test.go | 169 +++++++++++++++++++++++++++++++++++
+ src/go/parser/resolver.go    |   9 ++
+ 4 files changed, 236 insertions(+), 6 deletions(-)
+
+diff --git a/src/go/parser/interface.go b/src/go/parser/interface.go
+index 85486d2f4b..eae429e6ef 100644
+--- a/src/go/parser/interface.go
++++ b/src/go/parser/interface.go
+@@ -97,8 +97,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)
+ 			}
+ 		}
+ 
+@@ -203,8 +206,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 f10c8650af..2c42b9f8cc 100644
+--- a/src/go/parser/parser.go
++++ b/src/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) {
+@@ -110,6 +114,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
+@@ -222,8 +244,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 {
+@@ -1119,6 +1145,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)
+@@ -1531,7 +1559,13 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
+ 	}
+ 
+ 	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()
+@@ -1591,6 +1625,8 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
+ }
+ 
+ func (p *parser) parseUnaryExpr() ast.Expr {
++	defer decNestLev(incNestLev(p))
++
+ 	if p.trace {
+ 		defer un(trace(p, "UnaryExpr"))
+ 	}
+@@ -1673,7 +1709,13 @@ func (p *parser) parseBinaryExpr(prec1 int) ast.Expr {
+ 	}
+ 
+ 	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
+@@ -1962,6 +2004,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"))
+ 	}
+@@ -2265,6 +2309,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 a4f882d368..1a46c87866 100644
+--- a/src/go/parser/parser_test.go
++++ b/src/go/parser/parser_test.go
+@@ -10,6 +10,7 @@ import (
+ 	"go/ast"
+ 	"go/token"
+ 	"io/fs"
++	"runtime"
+ 	"strings"
+ 	"testing"
+ )
+@@ -577,3 +578,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)
++					}
++				}
++			})
++		}
++	}
++}
+diff --git a/src/go/parser/resolver.go b/src/go/parser/resolver.go
+index cf92c7e4f5..f55bdb7f17 100644
+--- a/src/go/parser/resolver.go
++++ b/src/go/parser/resolver.go
+@@ -25,6 +25,7 @@ func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, str
+ 		declErr:  declErr,
+ 		topScope: pkgScope,
+ 		pkgScope: pkgScope,
++		depth:    1,
+ 	}
+ 
+ 	for _, decl := range file.Decls {
+@@ -53,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)
+@@ -61,6 +64,7 @@ type resolver struct {
+ 	pkgScope   *ast.Scope   // pkgScope.Outer == nil
+ 	topScope   *ast.Scope   // top-most scope; may be pkgScope
+ 	unresolved []*ast.Ident // unresolved identifiers
++	depth      int          // scope depth
+ 
+ 	// Label scopes
+ 	// (maintained by open/close LabelScope)
+@@ -83,6 +87,10 @@ func (r *resolver) sprintf(format string, args ...interface{}) 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.dump("opening scope @%v", pos)
+ 	}
+@@ -90,6 +98,7 @@ func (r *resolver) openScope(pos token.Pos) {
+ }
+ 
+ func (r *resolver) closeScope() {
++	r.depth--
+ 	if debugResolve {
+ 		r.dump("closing scope")
+ 	}
+-- 
+2.30.2
+