From 85bc529d2bcb720aca843d0d6d8c1ccfa46a6793 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Mon, 1 Jun 2026 15:54:07 +0200 Subject: [PATCH] sql: Fix coordinator panic parsing Unicode-numeric durations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Duration::parse` located the end of the leading number with `chars().position(|c| \!char::is_numeric(c))`, which returns a *character* index, and then sliced the string with it as a *byte* offset. `char::is_numeric` also matches multi-byte Unicode numerics (e.g. '²', '½'), so for such input the character index and byte offset diverge and the slice can land mid-character, panicking the coordinator thread: SET statement_timeout = '²'; SET idle_in_transaction_session_timeout = '1²ms'; Use `str::find`, which returns a byte index suitable for slicing, and restrict the leading run to ASCII digits — exactly what the subsequent `u64::parse` accepts. Inputs containing Unicode numerics now cleanly return a parse error instead of panicking. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/sql/src/session/vars/value.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/sql/src/session/vars/value.rs b/src/sql/src/session/vars/value.rs index a5e6277b9a1aa..ce98f8462e795 100644 --- a/src/sql/src/session/vars/value.rs +++ b/src/sql/src/session/vars/value.rs @@ -291,11 +291,12 @@ impl Value for Duration { { let s = extract_single_value(input)?; let s = s.trim(); - // Take all numeric values from [0..] - let split_pos = s - .chars() - .position(|p| !char::is_numeric(p)) - .unwrap_or_else(|| s.chars().count()); + // Find where the leading run of ASCII digits ends. `str::find` returns a + // byte index, so it is safe to slice with directly. We restrict to ASCII + // digits (rather than `char::is_numeric`) because that is exactly what + // `u64::parse` accepts; matching broader Unicode numerics such as '²' + // would yield a byte offset that can land mid-character and panic. + let split_pos = s.find(|p: char| !p.is_ascii_digit()).unwrap_or(s.len()); // Error if the numeric values don't parse, i.e. there aren't any. let d = s[..split_pos] @@ -1240,6 +1241,13 @@ mod tests { errs("x"); errs("s"); errs("18446744073709551615 min"); + // Unicode numerics such as '²' (U+00B2) are multi-byte and must not be + // treated as parseable digits: their char index differs from their byte + // offset, so slicing on them would land mid-character and panic. + errs("²"); + errs("1²ms"); + errs("½"); + errs("1ms"); } #[mz_ore::test]