Skip to content

Commit f22d731

Browse files
rolandshoemakergopherbot
authored andcommitted
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 <[email protected]> Reviewed-by: Russ Cox <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/611240 Reviewed-by: Dmitri Shuralyov <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Auto-Submit: Dmitri Shuralyov <[email protected]>
1 parent 08c8442 commit f22d731

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

src/go/build/constraint/expr.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import (
1616
"unicode/utf8"
1717
)
1818

19+
// maxSize is a limit used to control the complexity of expressions, in order
20+
// to prevent stack exhaustion issues due to recursion.
21+
const maxSize = 1000
22+
1923
// An Expr is a build tag constraint expression.
2024
// The underlying concrete type is *[AndExpr], *[OrExpr], *[NotExpr], or *[TagExpr].
2125
type Expr interface {
@@ -151,7 +155,7 @@ func Parse(line string) (Expr, error) {
151155
return parseExpr(text)
152156
}
153157
if text, ok := splitPlusBuild(line); ok {
154-
return parsePlusBuildExpr(text), nil
158+
return parsePlusBuildExpr(text)
155159
}
156160
return nil, errNotConstraint
157161
}
@@ -201,6 +205,8 @@ type exprParser struct {
201205
tok string // last token read
202206
isTag bool
203207
pos int // position (start) of last token
208+
209+
size int
204210
}
205211

206212
// parseExpr parses a boolean build tag expression.
@@ -249,6 +255,10 @@ func (p *exprParser) and() Expr {
249255
// On entry, the next input token has not yet been lexed.
250256
// On exit, the next input token has been lexed and is in p.tok.
251257
func (p *exprParser) not() Expr {
258+
p.size++
259+
if p.size > maxSize {
260+
panic(&SyntaxError{Offset: p.pos, Err: "build expression too large"})
261+
}
252262
p.lex()
253263
if p.tok == "!" {
254264
p.lex()
@@ -388,7 +398,13 @@ func splitPlusBuild(line string) (expr string, ok bool) {
388398
}
389399

390400
// parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”).
391-
func parsePlusBuildExpr(text string) Expr {
401+
func parsePlusBuildExpr(text string) (Expr, error) {
402+
// Only allow up to 100 AND/OR operators for "old" syntax.
403+
// This is much less than the limit for "new" syntax,
404+
// but uses of old syntax were always very simple.
405+
const maxOldSize = 100
406+
size := 0
407+
392408
var x Expr
393409
for _, clause := range strings.Fields(text) {
394410
var y Expr
@@ -414,19 +430,25 @@ func parsePlusBuildExpr(text string) Expr {
414430
if y == nil {
415431
y = z
416432
} else {
433+
if size++; size > maxOldSize {
434+
return nil, errComplex
435+
}
417436
y = and(y, z)
418437
}
419438
}
420439
if x == nil {
421440
x = y
422441
} else {
442+
if size++; size > maxOldSize {
443+
return nil, errComplex
444+
}
423445
x = or(x, y)
424446
}
425447
}
426448
if x == nil {
427449
x = tag("ignore")
428450
}
429-
return x
451+
return x, nil
430452
}
431453

432454
// isValidTag reports whether the word is a valid build tag.

src/go/build/constraint/expr_test.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ var parsePlusBuildExprTests = []struct {
224224
func TestParsePlusBuildExpr(t *testing.T) {
225225
for i, tt := range parsePlusBuildExprTests {
226226
t.Run(fmt.Sprint(i), func(t *testing.T) {
227-
x := parsePlusBuildExpr(tt.in)
227+
x, _ := parsePlusBuildExpr(tt.in)
228228
if x.String() != tt.x.String() {
229229
t.Errorf("parsePlusBuildExpr(%q):\nhave %v\nwant %v", tt.in, x, tt.x)
230230
}
@@ -321,3 +321,66 @@ func TestPlusBuildLines(t *testing.T) {
321321
})
322322
}
323323
}
324+
325+
func TestSizeLimits(t *testing.T) {
326+
for _, tc := range []struct {
327+
name string
328+
expr string
329+
}{
330+
{
331+
name: "go:build or limit",
332+
expr: "//go:build " + strings.Repeat("a || ", maxSize+2),
333+
},
334+
{
335+
name: "go:build and limit",
336+
expr: "//go:build " + strings.Repeat("a && ", maxSize+2),
337+
},
338+
{
339+
name: "go:build and depth limit",
340+
expr: "//go:build " + strings.Repeat("(a &&", maxSize+2),
341+
},
342+
{
343+
name: "go:build or depth limit",
344+
expr: "//go:build " + strings.Repeat("(a ||", maxSize+2),
345+
},
346+
} {
347+
t.Run(tc.name, func(t *testing.T) {
348+
_, err := Parse(tc.expr)
349+
if err == nil {
350+
t.Error("expression did not trigger limit")
351+
} else if syntaxErr, ok := err.(*SyntaxError); !ok || syntaxErr.Err != "build expression too large" {
352+
if !ok {
353+
t.Errorf("unexpected error: %v", err)
354+
} else {
355+
t.Errorf("unexpected syntax error: %s", syntaxErr.Err)
356+
}
357+
}
358+
})
359+
}
360+
}
361+
362+
func TestPlusSizeLimits(t *testing.T) {
363+
maxOldSize := 100
364+
for _, tc := range []struct {
365+
name string
366+
expr string
367+
}{
368+
{
369+
name: "+build or limit",
370+
expr: "// +build " + strings.Repeat("a ", maxOldSize+2),
371+
},
372+
{
373+
name: "+build and limit",
374+
expr: "// +build " + strings.Repeat("a,", maxOldSize+2),
375+
},
376+
} {
377+
t.Run(tc.name, func(t *testing.T) {
378+
_, err := Parse(tc.expr)
379+
if err == nil {
380+
t.Error("expression did not trigger limit")
381+
} else if err != errComplex {
382+
t.Errorf("unexpected error: got %q, want %q", err, errComplex)
383+
}
384+
})
385+
}
386+
}

0 commit comments

Comments
 (0)