From f55d694e21bbe17be37070a094d99b3e5601e7e0 Mon Sep 17 00:00:00 2001 From: dxbjavid Date: Fri, 12 Jun 2026 07:19:04 -0400 Subject: [PATCH] fix backtracking hang in hstore literal parser Fixed regular expression in the pure Python hstore result processor, used when ``use_native_hstore=False`` is set, which could hang on malformed hstore text containing unterminated quoted segments with backslashes. Pull request courtesy dxbjavid. Fixes: #13370 Closes: #13371 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13371 Pull-request-sha: f5eddae11c435c78f326b70a15357cbaf6d09337 Change-Id: I0d2d7565dc88f56a73b41e2ad20ca1c5a6f738bb --- doc/build/changelog/unreleased_20/13370.rst | 8 ++++++++ lib/sqlalchemy/dialects/postgresql/hstore.py | 4 ++-- test/dialect/postgresql/test_types.py | 10 ++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 doc/build/changelog/unreleased_20/13370.rst diff --git a/doc/build/changelog/unreleased_20/13370.rst b/doc/build/changelog/unreleased_20/13370.rst new file mode 100644 index 0000000000..26e8945b64 --- /dev/null +++ b/doc/build/changelog/unreleased_20/13370.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, postgresql + :tickets: 13370 + + Fixed regular expression in the pure Python hstore result processor, + used when ``use_native_hstore=False`` is set, which could hang on + malformed hstore text containing unterminated quoted segments with + backslashes. Pull request courtesy dxbjavid. diff --git a/lib/sqlalchemy/dialects/postgresql/hstore.py b/lib/sqlalchemy/dialects/postgresql/hstore.py index 738c27814a..0a25f1fc9b 100644 --- a/lib/sqlalchemy/dialects/postgresql/hstore.py +++ b/lib/sqlalchemy/dialects/postgresql/hstore.py @@ -319,12 +319,12 @@ class _HStoreMatrixFunction(sqlfunc.GenericFunction[Any]): HSTORE_PAIR_RE = re.compile( r""" ( - "(?P (\\ . | [^"])* )" # Quoted key + "(?P (\\ . | [^"\\])* )" # Quoted key ) [ ]* => [ ]* # Pair operator, optional adjoining whitespace ( (?P NULL ) # NULL value - | "(?P (\\ . | [^"])* )" # Quoted value + | "(?P (\\ . | [^"\\])* )" # Quoted value ) """, re.VERBOSE, diff --git a/test/dialect/postgresql/test_types.py b/test/dialect/postgresql/test_types.py index 2f5df25d87..ce1994fa69 100644 --- a/test/dialect/postgresql/test_types.py +++ b/test/dialect/postgresql/test_types.py @@ -4152,6 +4152,16 @@ class HStoreTest(AssertsCompiledSQL, fixtures.TestBase): ) eq_(proc('"\\\\\\"a"=>"\\\\\\"1"'), {'\\"a': '\\"1'}) + def test_result_deserialize_malformed_no_backtracking(self): + # a quoted segment with a long run of backslashes and no closing + # quote used to make HSTORE_PAIR_RE backtrack catastrophically; the + # parser should reject it promptly instead of hanging + dialect = postgresql.dialect(use_native_hstore=False) + proc = self.test_table.c.hash.type._cached_result_processor( + dialect, None + ) + assert_raises(ValueError, proc, '"' + "\\" * 40 + "!") + def test_bind_serialize_psycopg2(self): from sqlalchemy.dialects.postgresql import psycopg2 -- 2.47.3