-
Notifications
You must be signed in to change notification settings - Fork 92
Description
Package: github.com/antchfx/xpath v1.3.5
File: query.go — logicalQuery.Select()
CVSS 3.1: 7.5 High — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-835: Loop with Unreachable Exit Condition
Summary
Any boolean XPath expression that evaluates to true - such as 0<1,
1=1, 1>0, 0<=1, 1!=2, not(false()), true() - causes an
infinite loop when used as a top-level node selector, consuming 100% of
a CPU core until the process is killed.
Impact
Any application that accepts user-controlled input as an XPath expression
and passes it to Find(), QueryAll(), or QuerySelectorAll() in any
of the downstream antchfx/*query packages is vulnerable to an
unbounded CPU denial-of-service. A single request with a trivial
expression like 1=1 is sufficient to permanently consume a goroutine
and peg a CPU core until the process is restarted.
Root Cause
logicalQuery.Select() in query.go has no "done" sentinel. When the
boolean expression evaluates to true, it returns t.Current().Copy()
on every call and never returns nil:
go
func (l *logicalQuery) Select(t iterator) NodeNavigator {
node := t.Current().Copy()
val := l.Evaluate(t)
switch val.(type) {
case bool:
if val.(bool) == true {
return node // ← returns same node on every call, never nil
}
}
return nil
}
MoveNext() in NodeIterator loops until Select() returns nil:
func (t *NodeIterator) MoveNext() bool {
n := t.query.Select(t)
if n == nil { return false } // ← never reached
...
return true
}
Since expressions like 0<1 are always true, Select() never returns
nil, MoveNext() never returns false, and the calling loop in
QuerySelectorAll runs forever.
Compare to the Correct Pattern
contextQuery.Select() in the same file correctly tracks a count and
returns nil after the first result:
func (c *contextQuery) Select(t iterator) NodeNavigator {
if c.count > 0 {
return nil // terminates correctly ✓
}
c.count++
return t.Current().Copy()
}
logicalQuery is missing the equivalent guard entirely.
Affected Expressions
Any XPath expression that compiles to a top-level logicalQuery and
evaluates to true:
┌──────────────┬──────────────────────────┐
│ Expression │ Result │
├──────────────┼──────────────────────────┤
│ 0<1 │ ∞ loop │
├──────────────┼──────────────────────────┤
│ 1=1 │ ∞ loop │
├──────────────┼──────────────────────────┤
│ 1>0 │ ∞ loop │
├──────────────┼──────────────────────────┤
│ 0<=1 │ ∞ loop │
├──────────────┼──────────────────────────┤
│ 1!=2 │ ∞ loop │
├──────────────┼──────────────────────────┤
│ true() │ ∞ loop │
├──────────────┼──────────────────────────┤
│ not(false()) │ ∞ loop │
├──────────────┼──────────────────────────┤
│ 0>1 │ returns normally (false) │
├──────────────┼──────────────────────────┤
│ 1=2 │ returns normally (false) │
└──────────────┴──────────────────────────┘
Suggested Fix
Add a done bool field to logicalQuery, set it on first return, and
reset it in Evaluate:
type logicalQuery struct {
Left, Right query
Do func(iterator, interface{}, interface{}) interface{}
done bool
}
func (l *logicalQuery) Select(t iterator) NodeNavigator {
if l.done {
return nil
}
node := t.Current().Copy()
val := l.Evaluate(t)
if v, ok := val.(bool); ok && v {
l.done = true
return node
}
return nil
}
func (l *logicalQuery) Evaluate(t iterator) interface{} {
l.done = false
m := l.Left.Evaluate(t)
n := l.Right.Evaluate(t)
return l.Do(t, m, n)
}
func (l *logicalQuery) Clone() query {
return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do}
}
Reference
https://securityinfinity.com/research/infinite-loop-dos-in-antchfx-xpath-logicalquery-select/