From 7244c0060dc3ef909f34b0791c3e7490b0340d5e Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Tue, 27 Apr 2021 01:16:46 +0200 Subject: [PATCH] bpo-43762: Add audit events for loading of sqlite3 extensions (GH-25246) --- Doc/library/sqlite3.rst | 14 +++++++++++ Doc/whatsnew/3.10.rst | 8 +++++++ Lib/test/audit-tests.py | 21 +++++++++++++++++ Lib/test/test_audit.py | 23 +++++++++++++++++++ .../2021-04-07-12-57-41.bpo-43762.7lMtpT.rst | 3 +++ Modules/_sqlite/connection.c | 9 ++++++++ Modules/_sqlite/module.c | 8 +++++++ 7 files changed, 86 insertions(+) create mode 100644 Misc/NEWS.d/next/Security/2021-04-07-12-57-41.bpo-43762.7lMtpT.rst diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 6bdf4ed0d81..d0f28db12fd 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -225,6 +225,7 @@ Module functions and constants be found in the `SQLite URI documentation `_. .. audit-event:: sqlite3.connect database sqlite3.connect + .. audit-event:: sqlite3.connect/handle connection_handle sqlite3.connect .. versionchanged:: 3.4 Added the *uri* parameter. @@ -232,6 +233,9 @@ Module functions and constants .. versionchanged:: 3.7 *database* can now also be a :term:`path-like object`, not only a string. + .. versionchanged:: 3.10 + Added the ``sqlite3.connect/handle`` auditing event. + .. function:: register_converter(typename, callable) @@ -467,8 +471,13 @@ Connection Objects Loadable extensions are disabled by default. See [#f1]_. + .. audit-event:: sqlite3.enable_load_extension connection,enabled sqlite3.enable_load_extension + .. versionadded:: 3.2 + .. versionchanged:: 3.10 + Added the ``sqlite3.enable_load_extension`` auditing event. + .. literalinclude:: ../includes/sqlite3/load_extension.py .. method:: load_extension(path) @@ -479,8 +488,13 @@ Connection Objects Loadable extensions are disabled by default. See [#f1]_. + .. audit-event:: sqlite3.load_extension connection,path sqlite3.load_extension + .. versionadded:: 3.2 + .. versionchanged:: 3.10 + Added the ``sqlite3.load_extension`` auditing event. + .. attribute:: row_factory You can change this attribute to a callable that accepts the cursor and the diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 1d9c03c439f..91ad8ec1adb 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -1080,6 +1080,14 @@ ssl Add a *timeout* parameter to the :func:`ssl.get_server_certificate` function. (Contributed by Zackery Spytz in :issue:`31870`.) +sqlite3 +------- + +Add audit events for :func:`~sqlite3.connect/handle`, +:meth:`~sqlite3.Connection.enable_load_extension`, and +:meth:`~sqlite3.Connection.load_extension`. +(Contributed by Erlend E. Aasland in :issue:`43762`.) + sys --- diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index 2addf9762ea..ed42451b8f0 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -359,6 +359,27 @@ def test_http_client(): conn.close() +def test_sqlite3(): + import sqlite3 + + def hook(event, *args): + if event.startswith("sqlite3."): + print(event, *args) + + sys.addaudithook(hook) + cx = sqlite3.connect(":memory:") + + # Configured without --enable-loadable-sqlite-extensions + if hasattr(sqlite3.Connection, "enable_load_extension"): + cx.enable_load_extension(False) + try: + cx.load_extension("test") + except sqlite3.OperationalError: + pass + else: + raise RuntimeError("Expected sqlite3.load_extension to fail") + + if __name__ == "__main__": from test.support import suppress_msvcrt_asserts diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index 456a5daceb9..4ba62c40852 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -130,6 +130,7 @@ class AuditTest(unittest.TestCase): ["gc.get_objects", "gc.get_referrers", "gc.get_referents"] ) + def test_http(self): import_helper.import_module("http.client") returncode, events, stderr = self.run_python("test_http_client") @@ -145,5 +146,27 @@ class AuditTest(unittest.TestCase): self.assertIn('HTTP', events[1][2]) + def test_sqlite3(self): + try: + import sqlite3 + except ImportError: + return + returncode, events, stderr = self.run_python("test_sqlite3") + if returncode: + self.fail(stderr) + + if support.verbose: + print(*events, sep='\n') + actual = [ev[0] for ev in events] + expected = ["sqlite3.connect", "sqlite3.connect/handle"] + + if hasattr(sqlite3.Connection, "enable_load_extension"): + expected += [ + "sqlite3.enable_load_extension", + "sqlite3.load_extension", + ] + self.assertEqual(actual, expected) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Security/2021-04-07-12-57-41.bpo-43762.7lMtpT.rst b/Misc/NEWS.d/next/Security/2021-04-07-12-57-41.bpo-43762.7lMtpT.rst new file mode 100644 index 00000000000..aa392656807 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2021-04-07-12-57-41.bpo-43762.7lMtpT.rst @@ -0,0 +1,3 @@ +Add audit events for :func:`sqlite3.connect/handle`, +:meth:`sqlite3.Connection.enable_load_extension`, and +:meth:`sqlite3.Connection.load_extension`. Patch by Erlend E. Aasland. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 150291cb723..5f8e41b6169 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1154,6 +1154,11 @@ pysqlite_connection_enable_load_extension_impl(pysqlite_Connection *self, { int rc; + if (PySys_Audit("sqlite3.enable_load_extension", + "OO", self, onoff ? Py_True : Py_False) < 0) { + return NULL; + } + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; } @@ -1185,6 +1190,10 @@ pysqlite_connection_load_extension_impl(pysqlite_Connection *self, int rc; char* errmsg; + if (PySys_Audit("sqlite3.load_extension", "Os", self, extension_name) < 0) { + return NULL; + } + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; } diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 8dbfa7b38a1..2f323fcd001 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -96,6 +96,14 @@ static PyObject* module_connect(PyObject* self, PyObject* args, PyObject* } result = PyObject_Call(factory, args, kwargs); + if (result == NULL) { + return NULL; + } + + if (PySys_Audit("sqlite3.connect/handle", "O", self) < 0) { + Py_DECREF(result); + return NULL; + } return result; }