From: dxbjavid Date: Fri, 12 Jun 2026 11:19:04 +0000 (-0400) Subject: fix backtracking hang in hstore literal parser X-Git-Tag: rel_2_0_51~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=83f2a4f95115390a707f04b257f116497f7d427c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git 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 (cherry picked from commit 0c38dfff3025176324996970dfb0c0f3e05ff28d) --- 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 66ad484257..d0623a9f05 100644 --- a/lib/sqlalchemy/dialects/postgresql/hstore.py +++ b/lib/sqlalchemy/dialects/postgresql/hstore.py @@ -302,12 +302,12 @@ class _HStoreMatrixFunction(sqlfunc.GenericFunction): 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 88324196dc..3d408999e3 100644 --- a/test/dialect/postgresql/test_types.py +++ b/test/dialect/postgresql/test_types.py @@ -3856,6 +3856,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