From 5568403d9bddca450965a2e38def70b56e95927f Mon Sep 17 00:00:00 2001 From: shuvamk Date: Tue, 4 Aug 2026 06:53:43 +0530 Subject: [PATCH] Add recursion checks for parse_interval `SELECT INTERVAL INTERVAL INTERVAL ... 1` (600 levels, ~5 KB) aborts the process with `fatal runtime error: stack overflow` on a 2 MiB stack instead of returning `ParserError::RecursionLimitExceeded`. In Rust a stack overflow is `abort()`, not a panic, so a consumer parsing untrusted SQL cannot contain it. For dialects where `require_interval_qualifier()` is false, `parse_interval` calls `parse_prefix` directly rather than `parse_expr`, so it bypasses the counter that lives in `parse_subexpr`. `parse_prefix` re-enters `parse_interval` on the next INTERVAL keyword, leaving the cycle unguarded. Reproduced on generic, duckdb, snowflake and postgres. Apply the same guards used for the parenthesis gap in #2199: the recursion counter plus `recursive::recursive` under the `recursive-protection` feature. Test in tests/sqlparser_common.rs alongside the other recursion-limit tests. Co-Authored-By: Claude Opus 5 --- src/parser/mod.rs | 3 +++ tests/sqlparser_common.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2d51d4717..d2668d3b7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3368,7 +3368,10 @@ impl<'a> Parser<'a> { /// ``` /// /// Note that we do not currently attempt to parse the quoted value. + #[cfg_attr(feature = "recursive-protection", recursive::recursive)] pub fn parse_interval(&mut self) -> Result { + let _guard = self.recursion_counter.try_decrease()?; + // The SQL standard allows an optional sign before the value string, but // it is not clear if any implementations support that syntax, so we // don't currently try to parse it. (The sign can instead be included diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index bad083602..0800bc41f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11419,6 +11419,20 @@ fn parse_deeply_nested_subquery_expr_hits_recursion_limits() { assert_eq!(res, Err(ParserError::RecursionLimitExceeded)); } +#[test] +fn parse_deeply_nested_interval_hits_recursion_limits() { + let dialect = GenericDialect {}; + + let sql = format!("SELECT {}1", "INTERVAL ".repeat(1000)); + + let res = Parser::new(&dialect) + .try_with_sql(&sql) + .expect("tokenize to work") + .parse_statements(); + + assert_eq!(res, Err(ParserError::RecursionLimitExceeded)); +} + #[test] fn parse_with_recursion_limit() { let dialect = GenericDialect {};