Skip to content

Commit fff8fab

Browse files
[2.7] bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) (GH-10955)
(cherry picked from commit 5b25f1d) (cherry picked from commit 1de91a0) Co-authored-by: Sergey Fedoseev <[email protected]>.
1 parent b2742ba commit fff8fab

File tree

3 files changed

+85
-39
lines changed

3 files changed

+85
-39
lines changed

‎Lib/sqlite3/test/regression.py‎

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -246,24 +246,6 @@ def CheckPragmaAutocommit(self):
246246
cur.execute("pragma page_size")
247247
row = cur.fetchone()
248248

249-
def CheckSetDict(self):
250-
"""
251-
See http://bugs.python.org/issue7478
252-
253-
It was possible to successfully register callbacks that could not be
254-
hashed. Return codes of PyDict_SetItem were not checked properly.
255-
"""
256-
class NotHashable:
257-
def __call__(self, *args, **kw):
258-
pass
259-
def __hash__(self):
260-
raise TypeError()
261-
var = NotHashable()
262-
self.assertRaises(TypeError, self.con.create_function, var)
263-
self.assertRaises(TypeError, self.con.create_aggregate, var)
264-
self.assertRaises(TypeError, self.con.set_authorizer, var)
265-
self.assertRaises(TypeError, self.con.set_progress_handler, var)
266-
267249
def CheckConnectionCall(self):
268250
"""
269251
Call a connection with a non-string SQL request: check error handling
@@ -380,9 +362,73 @@ def callback(*args):
380362
support.gc_collect()
381363

382364

365+
class UnhashableFunc:
366+
def __hash__(self):
367+
raise TypeError('unhashable type')
368+
369+
def __init__(self, return_value=None):
370+
self.calls = 0
371+
self.return_value = return_value
372+
373+
def __call__(self, *args, **kwargs):
374+
self.calls += 1
375+
return self.return_value
376+
377+
378+
class UnhashableCallbacksTestCase(unittest.TestCase):
379+
"""
380+
https://bugs.python.org/issue34052
381+
382+
Registering unhashable callbacks raises TypeError, callbacks are not
383+
registered in SQLite after such registration attempt.
384+
"""
385+
def setUp(self):
386+
self.con = sqlite.connect(':memory:')
387+
388+
def tearDown(self):
389+
self.con.close()
390+
391+
def test_progress_handler(self):
392+
f = UnhashableFunc(return_value=0)
393+
with self.assertRaisesRegexp(TypeError, 'unhashable type'):
394+
self.con.set_progress_handler(f, 1)
395+
self.con.execute('SELECT 1')
396+
self.assertFalse(f.calls)
397+
398+
def test_func(self):
399+
func_name = 'func_name'
400+
f = UnhashableFunc()
401+
with self.assertRaisesRegexp(TypeError, 'unhashable type'):
402+
self.con.create_function(func_name, 0, f)
403+
msg = 'no such function: %s' % func_name
404+
with self.assertRaisesRegexp(sqlite.OperationalError, msg):
405+
self.con.execute('SELECT %s()' % func_name)
406+
self.assertFalse(f.calls)
407+
408+
def test_authorizer(self):
409+
f = UnhashableFunc(return_value=sqlite.SQLITE_DENY)
410+
with self.assertRaisesRegexp(TypeError, 'unhashable type'):
411+
self.con.set_authorizer(f)
412+
self.con.execute('SELECT 1')
413+
self.assertFalse(f.calls)
414+
415+
def test_aggr(self):
416+
class UnhashableType(type):
417+
__hash__ = None
418+
aggr_name = 'aggr_name'
419+
with self.assertRaisesRegexp(TypeError, 'unhashable type'):
420+
self.con.create_aggregate(aggr_name, 0, UnhashableType('Aggr', (), {}))
421+
msg = 'no such function: %s' % aggr_name
422+
with self.assertRaisesRegexp(sqlite.OperationalError, msg):
423+
self.con.execute('SELECT %s()' % aggr_name)
424+
425+
383426
def suite():
384427
regression_suite = unittest.makeSuite(RegressionTests, "Check")
385-
return unittest.TestSuite((regression_suite,))
428+
return unittest.TestSuite((
429+
regression_suite,
430+
unittest.makeSuite(UnhashableCallbacksTestCase),
431+
))
386432

387433
def test():
388434
runner = unittest.TextTestRunner()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:meth:`sqlite3.Connection.create_aggregate`,
2+
:meth:`sqlite3.Connection.create_function`,
3+
:meth:`sqlite3.Connection.set_authorizer`,
4+
:meth:`sqlite3.Connection.set_progress_handler` methods raises TypeError
5+
when unhashable objects are passed as callable. These methods now don't pass
6+
such objects to SQLite API. Previous behavior could lead to segfaults. Patch
7+
by Sergey Fedoseev.

‎Modules/_sqlite/connection.c‎

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -877,19 +877,17 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec
877877
return NULL;
878878
}
879879

880+
if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) {
881+
return NULL;
882+
}
880883
rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL);
881884

882885
if (rc != SQLITE_OK) {
883886
/* Workaround for SQLite bug: no error code or string is available here */
884887
PyErr_SetString(pysqlite_OperationalError, "Error creating function");
885888
return NULL;
886-
} else {
887-
if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1)
888-
return NULL;
889-
890-
Py_INCREF(Py_None);
891-
return Py_None;
892889
}
890+
Py_RETURN_NONE;
893891
}
894892

895893
PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
@@ -910,18 +908,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje
910908
return NULL;
911909
}
912910

911+
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) {
912+
return NULL;
913+
}
913914
rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback);
914915
if (rc != SQLITE_OK) {
915916
/* Workaround for SQLite bug: no error code or string is available here */
916917
PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate");
917918
return NULL;
918-
} else {
919-
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1)
920-
return NULL;
921-
922-
Py_INCREF(Py_None);
923-
return Py_None;
924919
}
920+
Py_RETURN_NONE;
925921
}
926922

927923
static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source)
@@ -1007,18 +1003,15 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P
10071003
return NULL;
10081004
}
10091005

1006+
if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) {
1007+
return NULL;
1008+
}
10101009
rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb);
1011-
10121010
if (rc != SQLITE_OK) {
10131011
PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback");
10141012
return NULL;
1015-
} else {
1016-
if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1)
1017-
return NULL;
1018-
1019-
Py_INCREF(Py_None);
1020-
return Py_None;
10211013
}
1014+
Py_RETURN_NONE;
10221015
}
10231016

10241017
static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
@@ -1041,9 +1034,9 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s
10411034
/* None clears the progress handler previously set */
10421035
sqlite3_progress_handler(self->db, 0, 0, (void*)0);
10431036
} else {
1044-
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
10451037
if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1)
10461038
return NULL;
1039+
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
10471040
}
10481041

10491042
Py_INCREF(Py_None);

0 commit comments

Comments
 (0)