@@ -18,11 +18,12 @@ SPDX-License-Identifier: Apache-2.0
1818package boolpolicy
1919
2020import (
21- "errors"
2221 "fmt"
2322 "strconv"
2423 "strings"
2524 "unicode"
25+
26+ "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
2627)
2728
2829const (
@@ -36,6 +37,17 @@ const (
3637 // Go call stack; capping at 64 prevents goroutine stack exhaustion from
3738 // attacker-supplied deeply-nested expressions.
3839 maxParseDepth = 64
40+
41+ // maxParseNodes is the maximum number of AST nodes (RefNode/AndNode/OrNode)
42+ // that Parse will construct for a single expression. The depth cap bounds
43+ // the shape of the tree but not its size: a flat, unparenthesised chain such
44+ // as "$0 OR $1 OR ..." stays at depth 0 while producing one node per token.
45+ // Capping the total node count bounds the memory footprint of the AST — and
46+ // the work any later traversal performs — regardless of expression shape.
47+ // 1024 nodes comfortably covers real policies (hundreds of component
48+ // identities) while staying well under the ~2000-node worst case the
49+ // maxPolicyLen byte limit alone would otherwise permit.
50+ maxParseNodes = 1024
3951)
4052
4153// ---------------------------------------------------------------------------
@@ -140,11 +152,11 @@ func (l *lexer) next() (lexToken, error) {
140152 l .pos ++
141153 }
142154 if l .pos == start {
143- return lexToken {}, fmt .Errorf ("expected digit after '$' at position %d" , l .pos )
155+ return lexToken {}, errors .Errorf ("expected digit after '$' at position %d" , l .pos )
144156 }
145157 idx , err := strconv .Atoi (string (l .runes [start :l .pos ]))
146158 if err != nil {
147- return lexToken {}, fmt . Errorf ( "invalid index at position %d: %w " , start , err )
159+ return lexToken {}, errors . Wrapf ( err , "invalid index at position %d" , start )
148160 }
149161
150162 return lexToken {kind : tokRef , index : idx }, nil
@@ -161,11 +173,11 @@ func (l *lexer) next() (lexToken, error) {
161173 case "OR" :
162174 return lexToken {kind : tokOr }, nil
163175 default :
164- return lexToken {}, fmt .Errorf ("unknown keyword %q at position %d" , word , start )
176+ return lexToken {}, errors .Errorf ("unknown keyword %q at position %d" , word , start )
165177 }
166178
167179 default :
168- return lexToken {}, fmt .Errorf ("unexpected character %q at position %d" , string (ch ), l .pos )
180+ return lexToken {}, errors .Errorf ("unexpected character %q at position %d" , string (ch ), l .pos )
169181 }
170182}
171183
@@ -178,19 +190,40 @@ type parser struct {
178190 lex * lexer
179191 current lexToken
180192 err error
193+ nodes int // number of AST nodes constructed so far
194+ }
195+
196+ // countNode records the construction of one AST node and returns whether the
197+ // parser is still within the maxParseNodes budget. On overflow it latches an
198+ // error (if one is not already set) so the caller can bail out immediately.
199+ func (p * parser ) countNode () bool {
200+ p .nodes ++
201+ if p .nodes > maxParseNodes {
202+ if p .err == nil {
203+ p .err = errors .Errorf ("policy expression exceeds maximum node count of %d" , maxParseNodes )
204+ }
205+
206+ return false
207+ }
208+
209+ return true
181210}
182211
183212// Parse parses a boolean expression string and returns the root AST node.
184213// It returns an error for any lexical or syntactic problems.
185214//
186- // Parse enforces two hard limits to prevent resource exhaustion:
215+ // Parse enforces three hard limits to prevent resource exhaustion:
187216// - input longer than maxPolicyLen bytes is rejected immediately.
188217// - parenthesis nesting deeper than maxParseDepth levels is rejected;
189218// this bounds the Go call-stack depth of the recursive descent and
190219// prevents goroutine stack exhaustion from attacker-supplied input.
220+ // - expressions producing more than maxParseNodes AST nodes are rejected;
221+ // this bounds the size of the AST (and the cost of any later traversal)
222+ // independently of its shape, since the depth cap alone does not limit a
223+ // flat chain such as "$0 OR $1 OR ...".
191224func Parse (input string ) (Node , error ) {
192225 if len (input ) > maxPolicyLen {
193- return nil , fmt .Errorf ("policy expression exceeds maximum length of %d bytes (got %d)" , maxPolicyLen , len (input ))
226+ return nil , errors .Errorf ("policy expression exceeds maximum length of %d bytes (got %d)" , maxPolicyLen , len (input ))
194227 }
195228
196229 p := & parser {lex : newLexer (input )}
@@ -224,6 +257,9 @@ func (p *parser) parseOr(depth int) Node {
224257 for p .err == nil && p .current .kind == tokOr {
225258 p .advance ()
226259 right := p .parseAnd (depth )
260+ if ! p .countNode () {
261+ return nil
262+ }
227263 left = & OrNode {Left : left , Right : right }
228264 }
229265
@@ -237,6 +273,9 @@ func (p *parser) parseAnd(depth int) Node {
237273 for p .err == nil && p .current .kind == tokAnd {
238274 p .advance ()
239275 right := p .parsePrimary (depth )
276+ if ! p .countNode () {
277+ return nil
278+ }
240279 left = & AndNode {Left : left , Right : right }
241280 }
242281
@@ -250,14 +289,17 @@ func (p *parser) parsePrimary(depth int) Node {
250289 }
251290 switch p .current .kind {
252291 case tokRef :
292+ if ! p .countNode () {
293+ return nil
294+ }
253295 node := & RefNode {Index : p .current .index }
254296 p .advance ()
255297
256298 return node
257299
258300 case tokLParen :
259301 if depth >= maxParseDepth {
260- p .err = fmt .Errorf ("policy expression exceeds maximum nesting depth of %d" , maxParseDepth )
302+ p .err = errors .Errorf ("policy expression exceeds maximum nesting depth of %d" , maxParseDepth )
261303
262304 return nil
263305 }
@@ -267,7 +309,7 @@ func (p *parser) parsePrimary(depth int) Node {
267309 return nil
268310 }
269311 if p .current .kind != tokRParen {
270- p .err = fmt .Errorf ("expected ')' but got token kind %v" , p .current .kind )
312+ p .err = errors .Errorf ("expected ')' but got token kind %v" , p .current .kind )
271313
272314 return nil
273315 }
@@ -276,7 +318,7 @@ func (p *parser) parsePrimary(depth int) Node {
276318 return node
277319
278320 default :
279- p .err = fmt .Errorf ("expected '$N' or '(' at position %d" , p .lex .pos )
321+ p .err = errors .Errorf ("expected '$N' or '(' at position %d" , p .lex .pos )
280322
281323 return nil
282324 }
0 commit comments