Skip to content

Commit 1de91a0

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

File tree

3 files changed

+84
-36
lines changed

3 files changed

+84
-36
lines changed

‎Lib/sqlite3/test/regression.py‎

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -256,24 +256,6 @@ def CheckPragmaAutocommit(self):
256256
cur.execute("pragma page_size")
257257
row = cur.fetchone()
258258

259-
def CheckSetDict(self):
260-
"""
261-
See http://bugs.python.org/issue7478
262-
263-
It was possible to successfully register callbacks that could not be
264-
hashed. Return codes of PyDict_SetItem were not checked properly.
265-
"""
266-
class NotHashable:
267-
def __call__(self, *args, **kw):
268-
pass
269-
def __hash__(self):
270-
raise TypeError()
271-
var = NotHashable()
272-
self.assertRaises(TypeError, self.con.create_function, var)
273-
self.assertRaises(TypeError, self.con.create_aggregate, var)
274-
self.assertRaises(TypeError, self.con.set_authorizer, var)
275-
self.assertRaises(TypeError, self.con.set_progress_handler, var)
276-
277259
def CheckConnectionCall(self):
278260
"""
279261
Call a connection with a non-string SQL request: check error handling
@@ -398,9 +380,72 @@ def callback(*args):
398380
support.gc_collect()
399381

400382

383+
class UnhashableFunc:
384+
__hash__ = None
385+
386+
def __init__(self, return_value=None):
387+
self.calls = 0
388+
self.return_value = return_value
389+
390+
def __call__(self, *args, **kwargs):
391+
self.calls += 1
392+
return self.return_value
393+
394+
395+
class UnhashableCallbacksTestCase(unittest.TestCase):
396+
"""
397+
https://bugs.python.org/issue34052
398+
399+
Registering unhashable callbacks raises TypeError, callbacks are not
400+
registered in SQLite after such registration attempt.
401+
"""
402+
def setUp(self):
403+
self.con = sqlite.connect(':memory:')
404+
405+
def tearDown(self):
406+
self.con.close()
407+
408+
def test_progress_handler(self):
409+
f = UnhashableFunc(return_value=0)
410+
with self.assertRaisesRegex(TypeError, 'unhashable type'):
411+
self.con.set_progress_handler(f, 1)
412+
self.con.execute('SELECT 1')
413+
self.assertFalse(f.calls)
414+
415+
def test_func(self):
416+
func_name = 'func_name'
417+
f = UnhashableFunc()
418+
with self.assertRaisesRegex(TypeError, 'unhashable type'):
419+
self.con.create_function(func_name, 0, f)
420+
msg = 'no such function: %s' % func_name
421+
with self.assertRaisesRegex(sqlite.OperationalError, msg):
422+
self.con.execute('SELECT %s()' % func_name)
423+
self.assertFalse(f.calls)
424+
425+
def test_authorizer(self):
426+
f = UnhashableFunc(return_value=sqlite.SQLITE_DENY)
427+
with self.assertRaisesRegex(TypeError, 'unhashable type'):
428+
self.con.set_authorizer(f)
429+
self.con.execute('SELECT 1')
430+
self.assertFalse(f.calls)
431+
432+
def test_aggr(self):
433+
class UnhashableType(type):
434+
__hash__ = None
435+
aggr_name = 'aggr_name'
436+
with self.assertRaisesRegex(TypeError, 'unhashable type'):
437+
self.con.create_aggregate(aggr_name, 0, UnhashableType('Aggr', (), {}))
438+
msg = 'no such function: %s' % aggr_name
439+
with self.assertRaisesRegex(sqlite.OperationalError, msg):
440+
self.con.execute('SELECT %s()' % aggr_name)
441+
442+
401443
def suite():
402444
regression_suite = unittest.makeSuite(RegressionTests, "Check")
403-
return unittest.TestSuite((regression_suite,))
445+
return unittest.TestSuite((
446+
regression_suite,
447+
unittest.makeSuite(UnhashableCallbacksTestCase),
448+
))
404449

405450
def test():
406451
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 & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -827,18 +827,17 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec
827827
return NULL;
828828
}
829829

830+
if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) {
831+
return NULL;
832+
}
830833
rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL);
831834

832835
if (rc != SQLITE_OK) {
833836
/* Workaround for SQLite bug: no error code or string is available here */
834837
PyErr_SetString(pysqlite_OperationalError, "Error creating function");
835838
return NULL;
836-
} else {
837-
if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1)
838-
return NULL;
839-
840-
Py_RETURN_NONE;
841839
}
840+
Py_RETURN_NONE;
842841
}
843842

844843
PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
@@ -859,17 +858,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje
859858
return NULL;
860859
}
861860

861+
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) {
862+
return NULL;
863+
}
862864
rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback);
863865
if (rc != SQLITE_OK) {
864866
/* Workaround for SQLite bug: no error code or string is available here */
865867
PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate");
866868
return NULL;
867-
} else {
868-
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1)
869-
return NULL;
870-
871-
Py_RETURN_NONE;
872869
}
870+
Py_RETURN_NONE;
873871
}
874872

875873
static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source)
@@ -982,17 +980,15 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P
982980
return NULL;
983981
}
984982

983+
if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) {
984+
return NULL;
985+
}
985986
rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb);
986-
987987
if (rc != SQLITE_OK) {
988988
PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback");
989989
return NULL;
990-
} else {
991-
if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1)
992-
return NULL;
993-
994-
Py_RETURN_NONE;
995990
}
991+
Py_RETURN_NONE;
996992
}
997993

998994
static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
@@ -1015,9 +1011,9 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s
10151011
/* None clears the progress handler previously set */
10161012
sqlite3_progress_handler(self->db, 0, 0, (void*)0);
10171013
} else {
1018-
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
10191014
if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1)
10201015
return NULL;
1016+
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
10211017
}
10221018

10231019
Py_RETURN_NONE;

0 commit comments

Comments
 (0)