Skip to content

Commit 474a314

Browse files
committed
Change all internal queries to consider CVE-2018-1058
1 parent ea1b25c commit 474a314

4 files changed

Lines changed: 46 additions & 29 deletions

File tree

docs/contents/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
ChangeLog
22
=========
33

4+
Version 5.1.1 (...)
5+
- This version changes internal queries so that they cannot be exploited using
6+
a PostgreSQL security vulnerability described as CVE-2018-1058.
7+
48
Version 5.1 (2019-05-17)
59
------------------------
610
- Changes to the classic PyGreSQL module (pg):

pg.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,12 +1191,14 @@ def __init__(self, db):
11911191
self._query_pg_type = (
11921192
"SELECT oid, typname, typname::text::regtype,"
11931193
" typtype, null as typcategory, typdelim, typrelid"
1194-
" FROM pg_type WHERE oid=%s::regtype")
1194+
" FROM pg_catalog.pg_type"
1195+
" WHERE oid OPERATOR(pg_catalog.=) %s::regtype")
11951196
else:
11961197
self._query_pg_type = (
11971198
"SELECT oid, typname, typname::regtype,"
11981199
" typtype, typcategory, typdelim, typrelid"
1199-
" FROM pg_type WHERE oid=%s::regtype")
1200+
" FROM pg_catalog.pg_type"
1201+
" WHERE oid OPERATOR(pg_catalog.=) %s::regtype")
12001202

12011203
def add(self, oid, pgtype, regtype,
12021204
typtype, category, delim, relid):
@@ -1551,21 +1553,23 @@ def __init__(self, *args, **kw):
15511553
self.adapter = Adapter(self)
15521554
self.dbtypes = DbTypes(self)
15531555
if db.server_version < 80400:
1554-
# support older remote data bases
1556+
# support older remote data bases (not officially supported)
15551557
self._query_attnames = (
15561558
"SELECT a.attname, t.oid, t.typname, t.typname::text::regtype,"
15571559
" t.typtype, null as typcategory, t.typdelim, t.typrelid"
1558-
" FROM pg_attribute a"
1559-
" JOIN pg_type t ON t.oid = a.atttypid"
1560-
" WHERE a.attrelid = %s::regclass AND %s"
1560+
" FROM pg_catalog.pg_attribute a"
1561+
" JOIN pg_catalog.pg_type t"
1562+
" ON t.oid OPERATOR(pg_catalog.=) a.atttypid"
1563+
" WHERE a.attrelid OPERATOR(pg_catalog.=) %s::regclass AND %s"
15611564
" AND NOT a.attisdropped ORDER BY a.attnum")
15621565
else:
15631566
self._query_attnames = (
15641567
"SELECT a.attname, t.oid, t.typname, t.typname::regtype,"
15651568
" t.typtype, t.typcategory, t.typdelim, t.typrelid"
1566-
" FROM pg_attribute a"
1567-
" JOIN pg_type t ON t.oid = a.atttypid"
1568-
" WHERE a.attrelid = %s::regclass AND %s"
1569+
" FROM pg_catalog.pg_attribute a"
1570+
" JOIN pg_catalog.pg_type t"
1571+
" ON t.oid OPERATOR(pg_catalog.=) a.atttypid"
1572+
" WHERE a.attrelid OPERATOR(pg_catalog.=) %s::regclass AND %s"
15691573
" AND NOT a.attisdropped ORDER BY a.attnum")
15701574
db.set_cast_hook(self.dbtypes.typecast)
15711575
self.debug = None # For debugging scripts, this can be set
@@ -1997,11 +2001,13 @@ def pkey(self, table, composite=False, flush=False):
19972001
try: # cache lookup
19982002
pkey = pkeys[table]
19992003
except KeyError: # cache miss, check the database
2000-
q = ("SELECT a.attname, a.attnum, i.indkey FROM pg_index i"
2001-
" JOIN pg_attribute a ON a.attrelid = i.indrelid"
2002-
" AND a.attnum = ANY(i.indkey)"
2004+
q = ("SELECT a.attname, a.attnum, i.indkey"
2005+
" FROM pg_catalog.pg_index i"
2006+
" JOIN pg_catalog.pg_attribute a"
2007+
" ON a.attrelid OPERATOR(pg_catalog.=) i.indrelid"
2008+
" AND a.attnum OPERATOR(pg_catalog.=) ANY(i.indkey)"
20032009
" AND NOT a.attisdropped"
2004-
" WHERE i.indrelid=%s::regclass"
2010+
" WHERE i.indrelid OPERATOR(pg_catalog.=) %s::regclass"
20052011
" AND i.indisprimary ORDER BY a.attnum") % (
20062012
_quote_if_unqualified('$1', table),)
20072013
pkey = self.db.query(q, (table,)).getresult()
@@ -2023,7 +2029,8 @@ def pkey(self, table, composite=False, flush=False):
20232029
def get_databases(self):
20242030
"""Get list of databases in the system."""
20252031
return [s[0] for s in
2026-
self.db.query('SELECT datname FROM pg_database').getresult()]
2032+
self.db.query(
2033+
'SELECT datname FROM pg_catalog.pg_database').getresult()]
20272034

20282035
def get_relations(self, kinds=None, system=False):
20292036
"""Get list of relations in connected database of specified kinds.
@@ -2042,9 +2049,11 @@ def get_relations(self, kinds=None, system=False):
20422049
where.append("s.nspname NOT SIMILAR"
20432050
" TO 'pg/_%|information/_schema' ESCAPE '/'")
20442051
where = " WHERE %s" % ' AND '.join(where) if where else ''
2045-
q = ("SELECT quote_ident(s.nspname)||'.'||quote_ident(r.relname)"
2046-
" FROM pg_class r"
2047-
" JOIN pg_namespace s ON s.oid = r.relnamespace%s"
2052+
q = ("SELECT pg_catalog.quote_ident(s.nspname) OPERATOR(pg_catalog.||)"
2053+
" '.' OPERATOR(pg_catalog.||) pg_catalog.quote_ident(r.relname)"
2054+
" FROM pg_catalog.pg_class r"
2055+
" JOIN pg_catalog.pg_namespace s"
2056+
" ON s.oid OPERATOR(pg_catalog.=) r.relnamespace%s"
20482057
" ORDER BY s.nspname, r.relname") % where
20492058
return [r[0] for r in self.db.query(q).getresult()]
20502059

@@ -2076,9 +2085,9 @@ def get_attnames(self, table, with_oid=True, flush=False):
20762085
try: # cache lookup
20772086
names = attnames[table]
20782087
except KeyError: # cache miss, check the database
2079-
q = "a.attnum > 0"
2088+
q = "a.attnum OPERATOR(pg_catalog.>) 0"
20802089
if with_oid:
2081-
q = "(%s OR a.attname = 'oid')" % q
2090+
q = "(%s OR a.attname OPERATOR(pg_catalog.=) 'oid')" % q
20822091
q = self._query_attnames % (_quote_if_unqualified('$1', table), q)
20832092
names = self.db.query(q, (table,)).getresult()
20842093
types = self.dbtypes
@@ -2113,7 +2122,7 @@ def has_table_privilege(self, table, privilege='select', flush=False):
21132122
try: # ask cache
21142123
ret = privileges[table, privilege]
21152124
except KeyError: # cache miss, ask the database
2116-
q = "SELECT has_table_privilege(%s, $2)" % (
2125+
q = "SELECT pg_catalog.has_table_privilege(%s, $2)" % (
21172126
_quote_if_unqualified('$1', table),)
21182127
q = self.db.query(q, (table, privilege))
21192128
ret = q.getresult()[0][0] == self._make_bool(True)
@@ -2175,7 +2184,7 @@ def get(self, table, row, keyname=None):
21752184
adapt = params.add
21762185
col = self.escape_identifier
21772186
what = 'oid, *' if qoid else '*'
2178-
where = ' AND '.join('%s = %s' % (
2187+
where = ' AND '.join('%s OPERATOR(pg_catalog.=) %s' % (
21792188
col(k), adapt(row[k], attnames[k])) for k in keyname)
21802189
if 'oid' in row:
21812190
if qoid:
@@ -2187,6 +2196,8 @@ def get(self, table, row, keyname=None):
21872196
q = self.db.query(q, params)
21882197
res = q.dictresult()
21892198
if not res:
2199+
# make where clause in error message better readable
2200+
where = where.replace('OPERATOR(pg_catalog.=)', '=')
21902201
raise _db_error('No such record in %s\nwhere %s\nwith %s' % (
21912202
table, where, self._list_params(params)))
21922203
for n, value in res[0].items():
@@ -2276,7 +2287,7 @@ def update(self, table, row=None, **kw):
22762287
params = self.adapter.parameter_list()
22772288
adapt = params.add
22782289
col = self.escape_identifier
2279-
where = ' AND '.join('%s = %s' % (
2290+
where = ' AND '.join('%s OPERATOR(pg_catalog.=) %s' % (
22802291
col(k), adapt(row[k], attnames[k])) for k in keyname)
22812292
if 'oid' in row:
22822293
if qoid:
@@ -2467,7 +2478,7 @@ def delete(self, table, row=None, **kw):
24672478
params = self.adapter.parameter_list()
24682479
adapt = params.add
24692480
col = self.escape_identifier
2470-
where = ' AND '.join('%s = %s' % (
2481+
where = ' AND '.join('%s OPERATOR(pg_catalog.=) %s' % (
24712482
col(k), adapt(row[k], attnames[k])) for k in keyname)
24722483
if 'oid' in row:
24732484
if qoid:

pgdb.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -726,11 +726,11 @@ def __init__(self, cnx):
726726
# older remote databases (not officially supported)
727727
self._query_pg_type = ("SELECT oid, typname,"
728728
" typlen, typtype, null as typcategory, typdelim, typrelid"
729-
" FROM pg_type WHERE oid=%s")
729+
" FROM pg_catalog.pg_type WHERE oid OPERATOR(pg_catalog.=) %s")
730730
else:
731731
self._query_pg_type = ("SELECT oid, typname,"
732732
" typlen, typtype, typcategory, typdelim, typrelid"
733-
" FROM pg_type WHERE oid=%s")
733+
" FROM pg_catalog.pg_type WHERE oid OPERATOR(pg_catalog.=) %s")
734734

735735
def __missing__(self, key):
736736
"""Get the type info from the database if it is not cached."""
@@ -770,7 +770,9 @@ def get_fields(self, typ):
770770
if not typ.relid:
771771
return None # this type is not composite
772772
self._src.execute("SELECT attname, atttypid"
773-
" FROM pg_attribute WHERE attrelid=%s AND attnum>0"
773+
" FROM pg_catalog.pg_attribute"
774+
" WHERE attrelid OPERATOR(pg_catalog.=) %s"
775+
" AND attnum OPERATOR(pg_catalog.>) 0"
774776
" AND NOT attisdropped ORDER BY attnum" % (typ.relid,))
775777
return [FieldInfo(name, self.get(int(oid)))
776778
for name, oid in self._src.fetch(-1)]

tests/test_classic_dbwrapper.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,9 +1641,9 @@ def testGetLittleBobbyTables(self):
16411641
get = self.db.get
16421642
query = self.db.query
16431643
self.createTable('test_students',
1644-
'firstname varchar primary key, nickname varchar, grade char(2)',
1645-
values=[("D'Arcy", 'Darcey', 'A+'), ('Sheldon', 'Moonpie', 'A+'),
1646-
('Robert', 'Little Bobby Tables', 'D-')])
1644+
'firstname varchar primary key, nickname varchar, grade char(2)',
1645+
values=[("D'Arcy", 'Darcey', 'A+'), ('Sheldon', 'Moonpie', 'A+'),
1646+
('Robert', 'Little Bobby Tables', 'D-')])
16471647
r = get('test_students', 'Sheldon')
16481648
self.assertEqual(r, dict(
16491649
firstname="Sheldon", nickname='Moonpie', grade='A+'))

0 commit comments

Comments
 (0)