Source: sqlalchemy Version: 1.2.18+ds1 Followup-For: Bug #922669 I've confirmed that 1.2.18+ds1 is affected despite the description at [1]. Upstream has a patch for the 1.2 series at [2].
A debdiff including the patch is attached. It builds and the tests pass. However, the fix requires removing previously supported behavior. Consumers that depend on this have been found [3], so I'm not sure if this should be shipped in buster. Ross [1] https://github.com/sqlalchemy/sqlalchemy/issues/4481#issue-405370167 [2] https://gerrit.sqlalchemy.org/#/c/sqlalchemy/sqlalchemy/+/1184/ [3] https://github.com/sqlalchemy/sqlalchemy/issues/4538
diff -Nru sqlalchemy-1.2.18+ds1/debian/changelog sqlalchemy-1.2.18+ds1/debian/changelog --- sqlalchemy-1.2.18+ds1/debian/changelog 2019-02-24 15:01:50.000000000 -0800 +++ sqlalchemy-1.2.18+ds1/debian/changelog 2019-05-05 19:46:35.000000000 -0700 @@ -1,3 +1,10 @@ +sqlalchemy (1.2.18+ds1-1.1) UNRELEASED; urgency=medium + + * Non-maintainer upload. + * Add upstream patch for CVE-2019-7164, CVE-2019-7548 + + -- Ross Vandegrift <rvandegr...@debian.org> Sun, 05 May 2019 19:46:35 -0700 + sqlalchemy (1.2.18+ds1-1) unstable; urgency=medium * New upstream release diff -Nru sqlalchemy-1.2.18+ds1/debian/patches/0002-dla-1718-1.patch sqlalchemy-1.2.18+ds1/debian/patches/0002-dla-1718-1.patch --- sqlalchemy-1.2.18+ds1/debian/patches/0002-dla-1718-1.patch 1969-12-31 16:00:00.000000000 -0800 +++ sqlalchemy-1.2.18+ds1/debian/patches/0002-dla-1718-1.patch 2019-05-05 19:45:46.000000000 -0700 @@ -0,0 +1,332 @@ +From 82b4dcdeb0505f2dfcece5f76045b28b0edda03d Mon Sep 17 00:00:00 2001 +From: Mike Bayer <mike...@zzzcomputing.com> +Date: Mon, 08 Apr 2019 22:07:35 -0400 +Subject: [PATCH] Illustrate fix for #4481 in terms of a 1.2 patch + +Release 1.2 has decided (so far) not to backport 1.3's fix for #4481 as it is +backwards-incompatible with code that relied upon the feature of automatic text +coercion in SQL statements. However, for the specific case of order_by() and +group_by(), we present a patch that backports the specific change in compiler +to have 1.3's behavior for order_by/group_by specifically. This is much more +targeted than the 0.9 version of the patch as it takes advantage 1.0's +architecture which runs all order_by() / group_by() through a label lookup that +only warns if the label can't be matched. + +For an example of an application that was actually impacted by 1.3's change +and how they had to change it, see: + +https://github.com/ctxis/CAPE/commit/be0482294f5eb30026fe97a967ee5a768d032278 + +Basically, in the uncommon case an application is actually using the text +coercion feature which was generally little-known, within the order_by() +and group_by() an error is now raised instead of a warning; the application +must instead ensure the SQL fragment is passed within a text() construct. +The above application has also been seeing a warning about this since 1.0 +which apparently remained unattended. + +The patch includes adjustments to the tests that were testing for the +warning to now test that an exception is raised. Any distro that wants +to patch the specific CVE issue resolved in #4481 to SQLAlchemy 1.0, 1.1 +or 1.2 can use this patch. + +Change-Id: I3363b21428f1ad8797394b63197375a2e56a0bd7 +References: #4481 +--- + +diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py +index 5a11ed1..4780bab 100644 +--- a/lib/sqlalchemy/sql/compiler.py ++++ b/lib/sqlalchemy/sql/compiler.py +@@ -757,12 +757,11 @@ + else: + col = with_cols[element.element] + except KeyError: +- # treat it like text() +- util.warn_limited( +- "Can't resolve label reference %r; converting to text()", +- util.ellipses_string(element.element), ++ elements._no_text_coercion( ++ element.element, ++ exc.CompileError, ++ "Can't resolve label reference for ORDER BY / GROUP BY.", + ) +- return self.process(element._text_clause) + else: + kwargs["render_label_as_label"] = col + return self.process( +diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py +index 299fcad..ff86deb 100644 +--- a/lib/sqlalchemy/sql/elements.py ++++ b/lib/sqlalchemy/sql/elements.py +@@ -4432,6 +4432,17 @@ + ) + + ++def _no_text_coercion(element, exc_cls=exc.ArgumentError, extra=None): ++ raise exc_cls( ++ "%(extra)sTextual SQL expression %(expr)r should be " ++ "explicitly declared as text(%(expr)r)" ++ % { ++ "expr": util.ellipses_string(element), ++ "extra": "%s " % extra if extra else "", ++ } ++ ) ++ ++ + def _no_literals(element): + if hasattr(element, "__clause_element__"): + return element.__clause_element__() +diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py +index abcb597..fc9531d 100644 +--- a/test/orm/test_eager_relations.py ++++ b/test/orm/test_eager_relations.py +@@ -32,7 +32,6 @@ + from sqlalchemy.testing import assert_raises + from sqlalchemy.testing import assert_raises_message + from sqlalchemy.testing import eq_ +-from sqlalchemy.testing import expect_warnings + from sqlalchemy.testing import fixtures + from sqlalchemy.testing import in_ + from sqlalchemy.testing import is_ +@@ -343,16 +342,11 @@ + .order_by("email_address") + ) + +- with expect_warnings("Can't resolve label reference 'email_address'"): +- self.assert_compile( +- q, +- "SELECT users.id AS users_id, users.name AS users_name, " +- "addresses_1.id AS addresses_1_id, addresses_1.user_id AS " +- "addresses_1_user_id, addresses_1.email_address AS " +- "addresses_1_email_address FROM users LEFT OUTER JOIN " +- "addresses AS addresses_1 ON users.id = addresses_1.user_id " +- "ORDER BY email_address", +- ) ++ assert_raises_message( ++ sa.exc.CompileError, ++ "Can't resolve label reference for ORDER BY / GROUP BY.", ++ q.all, ++ ) + + def test_deferred_fk_col(self): + users, Dingaling, User, dingalings, Address, addresses = ( +diff --git a/test/orm/test_query.py b/test/orm/test_query.py +index 04ce8b5..0710507 100644 +--- a/test/orm/test_query.py ++++ b/test/orm/test_query.py +@@ -51,7 +51,6 @@ + from sqlalchemy.orm.util import with_parent + from sqlalchemy.sql import expression + from sqlalchemy.sql import operators +-from sqlalchemy.testing import assert_warnings + from sqlalchemy.testing import AssertsCompiledSQL + from sqlalchemy.testing import fixtures + from sqlalchemy.testing import is_ +@@ -2139,18 +2138,10 @@ + ua = aliased(User) + q = s.query(ua).order_by("email_ad") + +- def go(): +- self.assert_compile( +- q, +- "SELECT (SELECT max(addresses.email_address) AS max_1 " +- "FROM addresses WHERE addresses.user_id = users_1.id) " +- "AS anon_1, users_1.id AS users_1_id, " +- "users_1.name AS users_1_name FROM users AS users_1 " +- "ORDER BY email_ad", +- ) +- +- assert_warnings( +- go, ["Can't resolve label reference 'email_ad'"], regex=True ++ assert_raises_message( ++ sa.exc.CompileError, ++ "Can't resolve label reference for ORDER BY / GROUP BY", ++ q.with_labels().statement.compile, + ) + + def test_order_by_column_labeled_prop_attr_aliased_one(self): +@@ -4143,47 +4134,33 @@ + # the queries here are again "invalid" from a SQL perspective, as the + # "name" field isn't matched up to anything. + # +- with expect_warnings("Can't resolve label reference 'name';"): +- self.assert_compile( +- s.query(User) +- .options(joinedload("addresses")) +- .order_by(desc("name")) +- .limit(1), +- "SELECT anon_1.users_id AS anon_1_users_id, " +- "anon_1.users_name AS anon_1_users_name, " +- "addresses_1.id AS addresses_1_id, " +- "addresses_1.user_id AS addresses_1_user_id, " +- "addresses_1.email_address AS addresses_1_email_address " +- "FROM (SELECT users.id AS users_id, users.name AS users_name " +- "FROM users ORDER BY users.name " +- "DESC LIMIT :param_1) AS anon_1 " +- "LEFT OUTER JOIN addresses AS addresses_1 " +- "ON anon_1.users_id = addresses_1.user_id " +- "ORDER BY name DESC, addresses_1.id", +- ) ++ q = ( ++ s.query(User) ++ .options(joinedload("addresses")) ++ .order_by(desc("name")) ++ .limit(1) ++ ) ++ assert_raises_message( ++ sa_exc.CompileError, ++ "Can't resolve label reference for ORDER BY / GROUP BY.", ++ q.with_labels().statement.compile, ++ ) + + def test_order_by_w_eager_two(self): + User = self.classes.User + s = create_session() + +- with expect_warnings("Can't resolve label reference 'name';"): +- self.assert_compile( +- s.query(User) +- .options(joinedload("addresses")) +- .order_by("name") +- .limit(1), +- "SELECT anon_1.users_id AS anon_1_users_id, " +- "anon_1.users_name AS anon_1_users_name, " +- "addresses_1.id AS addresses_1_id, " +- "addresses_1.user_id AS addresses_1_user_id, " +- "addresses_1.email_address AS addresses_1_email_address " +- "FROM (SELECT users.id AS users_id, users.name AS users_name " +- "FROM users ORDER BY users.name " +- "LIMIT :param_1) AS anon_1 " +- "LEFT OUTER JOIN addresses AS addresses_1 " +- "ON anon_1.users_id = addresses_1.user_id " +- "ORDER BY name, addresses_1.id", +- ) ++ q = ( ++ s.query(User) ++ .options(joinedload("addresses")) ++ .order_by("name") ++ .limit(1) ++ ) ++ assert_raises_message( ++ sa_exc.CompileError, ++ "Can't resolve label reference for ORDER BY / GROUP BY.", ++ q.with_labels().statement.compile, ++ ) + + def test_order_by_w_eager_three(self): + User = self.classes.User +@@ -4268,22 +4245,11 @@ + .limit(1) + .offset(0) + ) +- with expect_warnings( +- "Can't resolve label reference 'email_address desc'" +- ): +- eq_( +- [ +- ( +- User( +- id=7, +- orders=[Order(id=1), Order(id=3), Order(id=5)], +- addresses=[Address(id=1)], +- ), +- "j...@bean.com", +- ) +- ], +- result.all(), +- ) ++ assert_raises_message( ++ sa_exc.CompileError, ++ "Can't resolve label reference for ORDER BY / GROUP BY", ++ result.all, ++ ) + + + class TextWarningTest(QueryTest, AssertsCompiledSQL): +diff --git a/test/sql/test_text.py b/test/sql/test_text.py +index 317fc61..75f1d6f 100644 +--- a/test/sql/test_text.py ++++ b/test/sql/test_text.py +@@ -22,7 +22,6 @@ + from sqlalchemy.sql import table + from sqlalchemy.sql import util as sql_util + from sqlalchemy.testing import assert_raises_message +-from sqlalchemy.testing import assert_warnings + from sqlalchemy.testing import AssertsCompiledSQL + from sqlalchemy.testing import eq_ + from sqlalchemy.testing import expect_warnings +@@ -633,15 +632,13 @@ + class OrderByLabelResolutionTest(fixtures.TestBase, AssertsCompiledSQL): + __dialect__ = "default" + +- def _test_warning(self, stmt, offending_clause, expected): +- with expect_warnings( +- "Can't resolve label reference %r;" % offending_clause +- ): +- self.assert_compile(stmt, expected) ++ def _test_exception(self, stmt, offending_clause): + assert_raises_message( +- exc.SAWarning, +- "Can't resolve label reference %r; converting to text" +- % offending_clause, ++ exc.CompileError, ++ r"Can't resolve label reference for ORDER BY / GROUP BY. " ++ "Textual SQL " ++ "expression %r should be explicitly " ++ r"declared as text\(%r\)" % (offending_clause, offending_clause), + stmt.compile, + ) + +@@ -691,9 +688,7 @@ + + def test_unresolvable_warning_order_by(self): + stmt = select([table1.c.myid]).order_by("foobar") +- self._test_warning( +- stmt, "foobar", "SELECT mytable.myid FROM mytable ORDER BY foobar" +- ) ++ self._test_exception(stmt, "foobar") + + def test_group_by_label(self): + stmt = select([table1.c.myid.label("foo")]).group_by("foo") +@@ -709,9 +704,7 @@ + + def test_unresolvable_warning_group_by(self): + stmt = select([table1.c.myid]).group_by("foobar") +- self._test_warning( +- stmt, "foobar", "SELECT mytable.myid FROM mytable GROUP BY foobar" +- ) ++ self._test_exception(stmt, "foobar") + + def test_asc(self): + stmt = select([table1.c.myid]).order_by(asc("name"), "description") +@@ -810,23 +803,13 @@ + .order_by("myid", "t1name", "x") + ) + +- def go(): +- # the labels here are anonymized, so label naming +- # can't catch these. +- self.assert_compile( +- s1, +- "SELECT mytable_1.myid AS mytable_1_myid, " +- "mytable_1.name AS name_1, foo(:foo_2) AS foo_1 " +- "FROM mytable AS mytable_1 ORDER BY mytable_1.myid, t1name, x", +- ) +- +- assert_warnings( +- go, +- [ +- "Can't resolve label reference 't1name'", +- "Can't resolve label reference 'x'", +- ], +- regex=True, ++ assert_raises_message( ++ exc.CompileError, ++ r"Can't resolve label reference for ORDER BY / GROUP BY. " ++ "Textual SQL " ++ "expression 't1name' should be explicitly " ++ r"declared as text\('t1name'\)", ++ s1.compile, + ) + + def test_columnadapter_non_anonymized(self): diff -Nru sqlalchemy-1.2.18+ds1/debian/patches/series sqlalchemy-1.2.18+ds1/debian/patches/series --- sqlalchemy-1.2.18+ds1/debian/patches/series 2018-12-30 03:30:19.000000000 -0800 +++ sqlalchemy-1.2.18+ds1/debian/patches/series 2019-05-05 19:45:53.000000000 -0700 @@ -1 +1,2 @@ 0001-drop_notfound_page_from_docs.patch +0002-dla-1718-1.patch