1
0
mirror of https://github.com/golang/go synced 2024-11-25 12:27:57 -07:00

go/build/constraint: add parsing limits

Limit the size of build constraints that we will parse. This prevents a
number of stack exhaustions that can be hit when parsing overly complex
constraints. The imposed limits are unlikely to ever be hit in real
world usage.

Fixes #69141
Fixes CVE-2024-34158

Change-Id: I38b614bf04caa36eefc6a4350d848588c4cef3c4
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/1540
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Russ Cox <rsc@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/611240
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
This commit is contained in:
Roland Shoemaker 2024-06-20 10:45:30 -07:00 committed by Gopher Robot
parent 08c84420bc
commit f22d731976
2 changed files with 89 additions and 4 deletions

View File

@ -16,6 +16,10 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// maxSize is a limit used to control the complexity of expressions, in order
// to prevent stack exhaustion issues due to recursion.
const maxSize = 1000
// An Expr is a build tag constraint expression. // An Expr is a build tag constraint expression.
// The underlying concrete type is *[AndExpr], *[OrExpr], *[NotExpr], or *[TagExpr]. // The underlying concrete type is *[AndExpr], *[OrExpr], *[NotExpr], or *[TagExpr].
type Expr interface { type Expr interface {
@ -151,7 +155,7 @@ func Parse(line string) (Expr, error) {
return parseExpr(text) return parseExpr(text)
} }
if text, ok := splitPlusBuild(line); ok { if text, ok := splitPlusBuild(line); ok {
return parsePlusBuildExpr(text), nil return parsePlusBuildExpr(text)
} }
return nil, errNotConstraint return nil, errNotConstraint
} }
@ -201,6 +205,8 @@ type exprParser struct {
tok string // last token read tok string // last token read
isTag bool isTag bool
pos int // position (start) of last token pos int // position (start) of last token
size int
} }
// parseExpr parses a boolean build tag expression. // parseExpr parses a boolean build tag expression.
@ -249,6 +255,10 @@ func (p *exprParser) and() Expr {
// On entry, the next input token has not yet been lexed. // On entry, the next input token has not yet been lexed.
// On exit, the next input token has been lexed and is in p.tok. // On exit, the next input token has been lexed and is in p.tok.
func (p *exprParser) not() Expr { func (p *exprParser) not() Expr {
p.size++
if p.size > maxSize {
panic(&SyntaxError{Offset: p.pos, Err: "build expression too large"})
}
p.lex() p.lex()
if p.tok == "!" { if p.tok == "!" {
p.lex() p.lex()
@ -388,7 +398,13 @@ func splitPlusBuild(line string) (expr string, ok bool) {
} }
// parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”). // parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”).
func parsePlusBuildExpr(text string) Expr { func parsePlusBuildExpr(text string) (Expr, error) {
// Only allow up to 100 AND/OR operators for "old" syntax.
// This is much less than the limit for "new" syntax,
// but uses of old syntax were always very simple.
const maxOldSize = 100
size := 0
var x Expr var x Expr
for _, clause := range strings.Fields(text) { for _, clause := range strings.Fields(text) {
var y Expr var y Expr
@ -414,19 +430,25 @@ func parsePlusBuildExpr(text string) Expr {
if y == nil { if y == nil {
y = z y = z
} else { } else {
if size++; size > maxOldSize {
return nil, errComplex
}
y = and(y, z) y = and(y, z)
} }
} }
if x == nil { if x == nil {
x = y x = y
} else { } else {
if size++; size > maxOldSize {
return nil, errComplex
}
x = or(x, y) x = or(x, y)
} }
} }
if x == nil { if x == nil {
x = tag("ignore") x = tag("ignore")
} }
return x return x, nil
} }
// isValidTag reports whether the word is a valid build tag. // isValidTag reports whether the word is a valid build tag.

View File

@ -224,7 +224,7 @@ var parsePlusBuildExprTests = []struct {
func TestParsePlusBuildExpr(t *testing.T) { func TestParsePlusBuildExpr(t *testing.T) {
for i, tt := range parsePlusBuildExprTests { for i, tt := range parsePlusBuildExprTests {
t.Run(fmt.Sprint(i), func(t *testing.T) { t.Run(fmt.Sprint(i), func(t *testing.T) {
x := parsePlusBuildExpr(tt.in) x, _ := parsePlusBuildExpr(tt.in)
if x.String() != tt.x.String() { if x.String() != tt.x.String() {
t.Errorf("parsePlusBuildExpr(%q):\nhave %v\nwant %v", tt.in, x, tt.x) t.Errorf("parsePlusBuildExpr(%q):\nhave %v\nwant %v", tt.in, x, tt.x)
} }
@ -321,3 +321,66 @@ func TestPlusBuildLines(t *testing.T) {
}) })
} }
} }
func TestSizeLimits(t *testing.T) {
for _, tc := range []struct {
name string
expr string
}{
{
name: "go:build or limit",
expr: "//go:build " + strings.Repeat("a || ", maxSize+2),
},
{
name: "go:build and limit",
expr: "//go:build " + strings.Repeat("a && ", maxSize+2),
},
{
name: "go:build and depth limit",
expr: "//go:build " + strings.Repeat("(a &&", maxSize+2),
},
{
name: "go:build or depth limit",
expr: "//go:build " + strings.Repeat("(a ||", maxSize+2),
},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := Parse(tc.expr)
if err == nil {
t.Error("expression did not trigger limit")
} else if syntaxErr, ok := err.(*SyntaxError); !ok || syntaxErr.Err != "build expression too large" {
if !ok {
t.Errorf("unexpected error: %v", err)
} else {
t.Errorf("unexpected syntax error: %s", syntaxErr.Err)
}
}
})
}
}
func TestPlusSizeLimits(t *testing.T) {
maxOldSize := 100
for _, tc := range []struct {
name string
expr string
}{
{
name: "+build or limit",
expr: "// +build " + strings.Repeat("a ", maxOldSize+2),
},
{
name: "+build and limit",
expr: "// +build " + strings.Repeat("a,", maxOldSize+2),
},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := Parse(tc.expr)
if err == nil {
t.Error("expression did not trigger limit")
} else if err != errComplex {
t.Errorf("unexpected error: got %q, want %q", err, errComplex)
}
})
}
}