Skip to content

Commit fdf5050

Browse files
miss-islingtonsir-sigurd
authored andcommitted
bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) (GH-10952)
(cherry picked from commit 5b25f1d) (cherry picked from commit 1de91a0) Co-authored-by: Sergey Fedoseev <fedoseev.sergey@gmail.com>
1 parent b2e0649 commit fdf5050

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
@@ -836,18 +836,17 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec
836836
return NULL;
837837
}
838838

839+
if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) {
840+
return NULL;
841+
}
839842
rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL);
840843

841844
if (rc != SQLITE_OK) {
842845
/* Workaround for SQLite bug: no error code or string is available here */
843846
PyErr_SetString(pysqlite_OperationalError, "Error creating function");
844847
return NULL;
845-
} else {
846-
if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1)
847-
return NULL;
848-
849-
Py_RETURN_NONE;
850848
}
849+
Py_RETURN_NONE;
851850
}
852851

853852
PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
@@ -868,17 +867,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje
868867
return NULL;
869868
}
870869

870+
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) {
871+
return NULL;
872+
}
871873
rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback);
872874
if (rc != SQLITE_OK) {
873875
/* Workaround for SQLite bug: no error code or string is available here */
874876
PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate");
875877
return NULL;
876-
} else {
877-
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1)
878-
return NULL;
879-
880-
Py_RETURN_NONE;
881878
}
879+
Py_RETURN_NONE;
882880
}
883881

884882
static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source)
@@ -1003,17 +1001,15 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P
10031001
return NULL;
10041002
}
10051003

1004+
if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) {
1005+
return NULL;
1006+
}
10061007
rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb);
1007-
10081008
if (rc != SQLITE_OK) {
10091009
PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback");
10101010
return NULL;
1011-
} else {
1012-
if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1)
1013-
return NULL;
1014-
1015-
Py_RETURN_NONE;
10161011
}
1012+
Py_RETURN_NONE;
10171013
}
10181014

10191015
static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
@@ -1036,9 +1032,9 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s
10361032
/* None clears the progress handler previously set */
10371033
sqlite3_progress_handler(self->db, 0, 0, (void*)0);
10381034
} else {
1039-
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
10401035
if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1)
10411036
return NULL;
1037+
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
10421038
}
10431039

10441040
Py_RETURN_NONE;

0 commit comments

Comments
 (0)