Details
-
Bug
-
Status: Closed (View Workflow)
-
Critical
-
Resolution: Fixed
-
10.11, 11.4, 11.8, 12.3, 13.0
-
Can result in hang or crash
Description
Problem and Use Case
Any user who can log in and run a SELECT can crash the whole server
with a single statement of well under 1 KB.
The statement calls ExtractValue() or UpdateXML() with an XPath
expression built from nested predicate brackets, such as
a[a[a[ ... ]]]. The server process dies with SIGSEGV. Every other
connected client loses its session at the same time, and the server has
to be restarted.
No table is required, no stored data is required, and no privilege
beyond the ability to connect and run a SELECT is required. The XML
document argument is never looked at, so a placeholder such as
'<a/>' is enough.
Both ExtractValue() and UpdateXML() reach the same defect, so
denying one function name does not remove the exposure.
Context
The XPath expression handed to ExtractValue() and UpdateXML() is
parsed in Item_xml_str_func::fix_fields() at
sql/item_xmlfunc.cc:2798, which calls my_xpath_parse() at line
2843. That is the only caller of the XPath parser in the tree, and
Item_func_xml_extractvalue and Item_func_xml_update are the only
two classes that inherit the call. Parsing happens during statement
preparation, before any XML is processed.
sql/item_xmlfunc.cc implements the XPath grammar by recursive
descent. Predicate brackets are parsed by
my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list() at line
1815, which loops over [ tokens and calls
my_xpath_parse_PredicateExpr() at line 1832 for the content of each
bracket. That call re-enters the top of the expression grammar and
closes a cycle of thirteen functions:
my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list 1815, calls at 1832
|
my_xpath_parse_PredicateExpr / my_xpath_parse_Expr macros, 1661 and 1664
|
my_xpath_parse_OrExpr 2214, calls at 2216
|
my_xpath_parse_AndExpr 2246, calls at 2248
|
my_xpath_parse_EqualityExpr 2311, calls at 2314
|
my_xpath_parse_RelationalExpr 2372, calls at 2375
|
my_xpath_parse_AdditiveExpr 2415, calls at 2417
|
my_xpath_parse_MultiplicativeExpr 2467, calls at 2469
|
my_xpath_parse_UnaryExpr 2510, calls at 2513
|
my_xpath_parse_UnionExpr 2096, calls at 2098
|
my_xpath_parse_PathExpr 2173, calls at 2175
|
my_xpath_parse_LocationPath 1701, calls at 1706
|
my_xpath_parse_RelativeLocationPath 1779, calls at 1781
|
my_xpath_parse_Step 1867, calls at 1869
|
back to my_xpath_parse_AxisSpecifier_NodeTest_opt_Predicate_list
|
Nothing in the cycle counts depth. The recursion descends one full pass
through those thirteen functions for every [ in the input, and the
input sets the depth.
sql/item_xmlfunc.cc contains exactly one check_stack_overrun()
call, at line 2006 inside my_xpath_parse_lp_Expr_rp(). That guard
covers the '(' Expr ')' rule. It runs its check before attempting to
match, so every route through my_xpath_parse_PrimaryExpr() at line
2023 is protected. The predicate cycle never reaches it.
my_xpath_parse_PathExpr() at line 2173 tries
my_xpath_parse_LocationPath() first and only falls back to the
branch leading to PrimaryExpr when the location path fails. Input
shaped like a location path always takes the first branch, so the
guarded rule is never entered and the guard never runs.
That guard was added on 2026-07-06 by commit 265bf3a9b10 for
MDEV-39750, "ExtractValue does not control recursion depth", reported by
Chris Anley at NCC Group. MDEV-39750 is public and its fix shipped in
10.6.28, 10.11.19, 11.4.13, 11.8.9, 12.3.3, and 13.0.2. The two vectors
that fix covers are REPEAT('(', 100000) and REPEAT('/n', 100000).
The predicate vector reported here bypasses it. This is an incomplete
fix rather than a regression.
The recursion itself is original code. The predicate loop and its call
back into the expression grammar have been present since commit
5e4c3ce6822c of 2005-12-21, which introduced ExtractValue and
UpdateXML, and no depth limit has ever guarded them.
Affected versions. Every branch from 10.6 through main carries the
unguarded predicate recursion:
- 10.6, 10.11, 11.4, 11.8, 12.3, 13.0, and main: affected. Each carries
theMDEV-39750guard at line 2006, and none of them guards the predicate
cycle. - 12.0, 12.1, and 12.2: affected, and additionally missing the
MDEV-39750guard. Their last commits date from 2025-07-31, 2025-11-13,
and 2026-02-04, so they are no longer receiving merges.
Thresholds. The default thread_stack is 299008 bytes
(DEFAULT_THREAD_STACK in include/my_pthread.h:669, which is
292*1024). The reporter measured the crash on a debug build at depth
312, with depth 311 surviving. Dividing 299008 by 312 gives 958 bytes
per predicate level, or roughly 74 bytes for each of the thirteen
frames. That 958 is a lower bound rather than a measurement, because the
connection thread has already consumed stack before my_xpath_parse()
is entered, so the true per level cost is higher. Sanitizer builds raise
DEFAULT_THREAD_STACK to 2 MiB or 11 MiB and enlarge the frames, so
the crashing depth there is different and has not been measured.
Reachability from stored objects. Item_xml_str_func and its two
subclasses do not override check_vcol_func_processor(). The nearest
override in the inheritance chain is
Item_result_field::check_vcol_func_processor(), which returns false
and therefore permits the function. On that reading ExtractValue()
is accepted in a virtual column, a CHECK constraint, and a
DEFAULT expression, all of which are fixed when the table is opened.
Whether that yields a crash surviving restart is unverified, because the
CREATE TABLE would itself have to survive fixing the same
expression. This is worth settling before the severity is finalized.
Reproduction
The reporter's statement, which crashes a debug build at the default
thread_stack. The XPath is 937 bytes.
SELECT ExtractValue('<a/>', CONCAT(REPEAT('a[',312),'a',REPEAT(']',312))); |
SELECT UpdateXML('<a/>', CONCAT(REPEAT('a[',312),'a',REPEAT(']',312)), '<b/>'); |
A proposed MTR test, mysql-test/main/xml_xpath_predicate_depth.test.
The depth is derived from @@thread_stack rather than hardcoded, so
the test stays past the threshold on builds whose frames differ from the
reporter's.
# Each predicate level costs one pass through the thirteen grammar
|
# functions of the predicate cycle. On a debug build at the default
|
# 292 KiB stack that pass measures at least 958 bytes, which exhausts
|
# the stack near depth 312. Dividing the stack by 64 clears that by
|
# more than a factor of ten, which keeps the depth past the threshold
|
# on optimized builds, where the frames are smaller and more levels
|
# fit before the stack runs out.
|
--let $depth= `SELECT @@thread_stack DIV 64`
|
|
|
--echo #
|
--echo # Test Case 1: A predicate nested past the stack limit is
|
--echo # rejected by ExtractValue rather than crashing the server.
|
--echo #
|
--replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/
|
--error ER_STACK_OVERRUN_NEED_MORE
|
--eval SELECT ExtractValue('<a/>', CONCAT(REPEAT('a[', $depth), 'a', REPEAT(']', $depth)))
|
|
|
--echo #
|
--echo # Test Case 2: The same expression is rejected by UpdateXML,
|
--echo # which parses its XPath argument through the same code.
|
--echo #
|
--replace_regex /overrun: [0-9]* bytes used of a [0-9]* byte stack, and [0-9]* bytes needed/overrun: 'used bytes' used of a 'available' byte stack, and 'X' bytes needed/
|
--error ER_STACK_OVERRUN_NEED_MORE
|
--eval SELECT UpdateXML('<a/>', CONCAT(REPEAT('a[', $depth), 'a', REPEAT(']', $depth)), '<b/>')
|
The test mutates no global state and creates no tables, so it carries no
cleanup block. Appending the two cases to mysql-test/main/xml.test
next to the MDEV-39750 cases is the alternative, and matches how
MDEV-39750 was tested.
Against unfixed code the test is expected to crash the server rather
than report a failed expectation, which is the impact this ticket
claims. That has not been run and stays unverified until it is.
Repoted by: daffainfo
Attachments
Issue Links
- relates to
-
MDEV-39750 ExtractValue does not control recursion depth
-
- Closed
-
- links to