From 00923c63995e34cdc25d699478f113de99a69df9 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 18 Nov 2019 11:32:46 -0800 Subject: [PATCH] bpo-38622: Add missing audit events for ctypes module (GH-17158) --- Doc/library/ctypes.rst | 36 +++++++++++++ Lib/ctypes/__init__.py | 4 ++ .../2019-11-14-16-13-23.bpo-38622.3DYkfb.rst | 1 + Modules/_ctypes/_ctypes.c | 21 ++++++++ Modules/_ctypes/callproc.c | 51 ++++++++++++++++--- 5 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2019-11-14-16-13-23.bpo-38622.3DYkfb.rst diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 1f84b7ab298..e0bc28f5e50 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -1526,6 +1526,12 @@ object is available: ``ctypes.dlsym`` with arguments ``library`` (the library object) and ``name`` (the symbol's name as a string or integer). +.. audit-event:: ctypes.dlsym/handle handle,name ctypes.LibraryLoader + + In cases when only the library handle is available rather than the object, + accessing a function raises an auditing event ``ctypes.dlsym/handle`` with + arguments ``handle`` (the raw library handle) and ``name``. + .. _ctypes-foreign-functions: Foreign functions @@ -1611,6 +1617,19 @@ They are instances of a private class: passed arguments. +.. audit-event:: ctypes.seh_exception code foreign-functions + + On Windows, when a foreign function call raises a system exception (for + example, due to an access violation), it will be captured and replaced with + a suitable Python exception. Further, an auditing event + ``ctypes.seh_exception`` with argument ``code`` will be raised, allowing an + audit hook to replace the exception with its own. + +.. audit-event:: ctypes.call_function func_pointer,arguments ctype-foreign-functions + + Some ways to invoke foreign function calls may raise an auditing event + ``ctypes.call_function`` with arguments ``function pointer`` and ``arguments``. + .. _ctypes-function-prototypes: Function prototypes @@ -1802,6 +1821,8 @@ Utility functions Returns the address of the memory buffer as integer. *obj* must be an instance of a ctypes type. + .. audit-event:: ctypes.addressof obj ctypes.addressof + .. function:: alignment(obj_or_type) @@ -1844,6 +1865,7 @@ Utility functions termination character. An integer can be passed as second argument which allows specifying the size of the array if the length of the bytes should not be used. + .. audit-event:: ctypes.create_string_buffer init,size ctypes.create_string_buffer .. function:: create_unicode_buffer(init_or_size, size=None) @@ -1860,6 +1882,7 @@ Utility functions allows specifying the size of the array if the length of the string should not be used. + .. audit-event:: ctypes.create_unicode_buffer init,size ctypes.create_unicode_buffer .. function:: DllCanUnloadNow() @@ -1917,11 +1940,15 @@ Utility functions Returns the current value of the ctypes-private copy of the system :data:`errno` variable in the calling thread. + .. audit-event:: ctypes.get_errno "" ctypes.get_errno + .. function:: get_last_error() Windows only: returns the current value of the ctypes-private copy of the system :data:`LastError` variable in the calling thread. + .. audit-event:: ctypes.get_last_error "" ctypes.get_last_error + .. function:: memmove(dst, src, count) Same as the standard C memmove library function: copies *count* bytes from @@ -1965,6 +1992,7 @@ Utility functions Set the current value of the ctypes-private copy of the system :data:`errno` variable in the calling thread to *value* and return the previous value. + .. audit-event:: ctypes.set_errno errno ctypes.set_errno .. function:: set_last_error(value) @@ -1973,6 +2001,7 @@ Utility functions :data:`LastError` variable in the calling thread to *value* and return the previous value. + .. audit-event:: ctypes.set_last_error error ctypes.set_last_error .. function:: sizeof(obj_or_type) @@ -1987,6 +2016,8 @@ Utility functions object. If size is specified, it is used as size, otherwise the string is assumed to be zero-terminated. + .. audit-event:: ctypes.string_at address,size ctypes.string_at + .. function:: WinError(code=None, descr=None) @@ -2007,6 +2038,8 @@ Utility functions characters of the string, otherwise the string is assumed to be zero-terminated. + .. audit-event:: ctypes.wstring_at address,size ctypes.wstring_at + .. _ctypes-data-types: @@ -2034,6 +2067,7 @@ Data types source buffer in bytes; the default is zero. If the source buffer is not large enough a :exc:`ValueError` is raised. + .. audit-event:: ctypes.cdata/buffer pointer,size,offset ctypes._CData.from_buffer .. method:: _CData.from_buffer_copy(source[, offset]) @@ -2043,6 +2077,8 @@ Data types is zero. If the source buffer is not large enough a :exc:`ValueError` is raised. + .. audit-event:: ctypes.cdata/buffer pointer,size,offset ctypes._CData.from_buffer_copy + .. method:: from_address(address) This method returns a ctypes type instance using the memory specified by diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 128155dbf4f..8f0991147d7 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -52,11 +52,13 @@ def create_string_buffer(init, size=None): if isinstance(init, bytes): if size is None: size = len(init)+1 + _sys.audit("ctypes.create_string_buffer", init, size) buftype = c_char * size buf = buftype() buf.value = init return buf elif isinstance(init, int): + _sys.audit("ctypes.create_string_buffer", None, init) buftype = c_char * init buf = buftype() return buf @@ -283,11 +285,13 @@ def create_unicode_buffer(init, size=None): # 32-bit wchar_t (1 wchar_t per Unicode character). +1 for # trailing NUL character. size = len(init) + 1 + _sys.audit("ctypes.create_unicode_buffer", init, size) buftype = c_wchar * size buf = buftype() buf.value = init return buf elif isinstance(init, int): + _sys.audit("ctypes.create_unicode_buffer", None, init) buftype = c_wchar * init buf = buftype() return buf diff --git a/Misc/NEWS.d/next/Security/2019-11-14-16-13-23.bpo-38622.3DYkfb.rst b/Misc/NEWS.d/next/Security/2019-11-14-16-13-23.bpo-38622.3DYkfb.rst new file mode 100644 index 00000000000..0373c14d0a1 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2019-11-14-16-13-23.bpo-38622.3DYkfb.rst @@ -0,0 +1 @@ +Add additional audit events for the :mod:`ctypes` module. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index b4a3fc433d2..d38d892be62 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -641,6 +641,12 @@ CDataType_from_buffer(PyObject *type, PyObject *args) return NULL; } + if (PySys_Audit("ctypes.cdata/buffer", "nnn", + (Py_ssize_t)buffer->buf, buffer->len, offset) < 0) { + Py_DECREF(mv); + return NULL; + } + result = PyCData_AtAddress(type, (char *)buffer->buf + offset); if (result == NULL) { Py_DECREF(mv); @@ -691,6 +697,12 @@ CDataType_from_buffer_copy(PyObject *type, PyObject *args) return NULL; } + if (PySys_Audit("ctypes.cdata/buffer", "nnn", + (Py_ssize_t)buffer.buf, buffer.len, offset) < 0) { + PyBuffer_Release(&buffer); + return NULL; + } + result = GenericPyCData_new((PyTypeObject *)type, NULL, NULL); if (result != NULL) { memcpy(((CDataObject *)result)->b_ptr, @@ -714,6 +726,9 @@ CDataType_in_dll(PyObject *type, PyObject *args) if (!PyArg_ParseTuple(args, "Os:in_dll", &dll, &name)) return NULL; + if (PySys_Audit("ctypes.dlsym", "O", args) < 0) { + return NULL; + } obj = PyObject_GetAttrString(dll, "_handle"); if (!obj) @@ -5535,6 +5550,9 @@ create_comerror(void) static PyObject * string_at(const char *ptr, int size) { + if (PySys_Audit("ctypes.string_at", "ni", (Py_ssize_t)ptr, size) < 0) { + return NULL; + } if (size == -1) return PyBytes_FromStringAndSize(ptr, strlen(ptr)); return PyBytes_FromStringAndSize(ptr, size); @@ -5626,6 +5644,9 @@ static PyObject * wstring_at(const wchar_t *ptr, int size) { Py_ssize_t ssize = size; + if (PySys_Audit("ctypes.wstring_at", "nn", (Py_ssize_t)ptr, ssize) < 0) { + return NULL; + } if (ssize == -1) ssize = wcslen(ptr); return PyUnicode_FromWideChar(ptr, ssize); diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index a13c89fa826..71060148d66 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -199,8 +199,9 @@ set_error_internal(PyObject *self, PyObject *args, int index) PyObject *errobj; int *space; - if (!PyArg_ParseTuple(args, "i", &new_errno)) + if (!PyArg_ParseTuple(args, "i", &new_errno)) { return NULL; + } errobj = _ctypes_get_errobj(&space); if (errobj == NULL) return NULL; @@ -213,12 +214,18 @@ set_error_internal(PyObject *self, PyObject *args, int index) static PyObject * get_errno(PyObject *self, PyObject *args) { + if (PySys_Audit("ctypes.get_errno", NULL) < 0) { + return NULL; + } return get_error_internal(self, args, 0); } static PyObject * set_errno(PyObject *self, PyObject *args) { + if (PySys_Audit("ctypes.set_errno", "O", args) < 0) { + return NULL; + } return set_error_internal(self, args, 0); } @@ -227,12 +234,18 @@ set_errno(PyObject *self, PyObject *args) static PyObject * get_last_error(PyObject *self, PyObject *args) { + if (PySys_Audit("ctypes.get_last_error", NULL) < 0) { + return NULL; + } return get_error_internal(self, args, 1); } static PyObject * set_last_error(PyObject *self, PyObject *args) { + if (PySys_Audit("ctypes.set_last_error", "O", args) < 0) { + return NULL; + } return set_error_internal(self, args, 1); } @@ -262,6 +275,11 @@ static WCHAR *FormatError(DWORD code) #ifndef DONT_USE_SEH static void SetException(DWORD code, EXCEPTION_RECORD *pr) { + if (PySys_Audit("ctypes.seh_exception", "I", code) < 0) { + /* An exception was set by the audit hook */ + return; + } + /* The 'code' is a normal win32 error code so it could be handled by PyErr_SetFromWindowsErr(). However, for some errors, we have additional information not included in the error code. We handle those here and @@ -1427,6 +1445,9 @@ static PyObject *py_dl_sym(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "O&s:dlsym", &_parse_voidp, &handle, &name)) return NULL; + if (PySys_Audit("ctypes.dlsym/handle", "O", args) < 0) { + return NULL; + } ptr = ctypes_dlsym((void*)handle, name); if (!ptr) { PyErr_SetString(PyExc_OSError, @@ -1454,6 +1475,10 @@ call_function(PyObject *self, PyObject *args) &_parse_voidp, &func, &PyTuple_Type, &arguments)) return NULL; + if (PySys_Audit("ctypes.call_function", "nO", + (Py_ssize_t)func, arguments) < 0) { + return NULL; + } result = _ctypes_callproc((PPROC)func, arguments, @@ -1485,6 +1510,10 @@ call_cdeclfunction(PyObject *self, PyObject *args) &_parse_voidp, &func, &PyTuple_Type, &arguments)) return NULL; + if (PySys_Audit("ctypes.call_function", "nO", + (Py_ssize_t)func, arguments) < 0) { + return NULL; + } result = _ctypes_callproc((PPROC)func, arguments, @@ -1597,11 +1626,15 @@ static const char addressof_doc[] = static PyObject * addressof(PyObject *self, PyObject *obj) { - if (CDataObject_Check(obj)) - return PyLong_FromVoidPtr(((CDataObject *)obj)->b_ptr); - PyErr_SetString(PyExc_TypeError, - "invalid type"); - return NULL; + if (!CDataObject_Check(obj)) { + PyErr_SetString(PyExc_TypeError, + "invalid type"); + return NULL; + } + if (PySys_Audit("ctypes.addressof", "O", obj) < 0) { + return NULL; + } + return PyLong_FromVoidPtr(((CDataObject *)obj)->b_ptr); } static int @@ -1615,8 +1648,12 @@ static PyObject * My_PyObj_FromPtr(PyObject *self, PyObject *args) { PyObject *ob; - if (!PyArg_ParseTuple(args, "O&:PyObj_FromPtr", converter, &ob)) + if (!PyArg_ParseTuple(args, "O&:PyObj_FromPtr", converter, &ob)) { return NULL; + } + if (PySys_Audit("ctypes.PyObj_FromPtr", "O", ob) < 0) { + return NULL; + } Py_INCREF(ob); return ob; }