On Tue, Feb 16, 2021 at 11:49:31AM +0100, Theo Buehler wrote: > sprintf strikes again. > > https://nvd.nist.gov/vuln/detail/CVE-2021-3177#vulnCurrentDescriptionTitle > > This was made public last night UTC and looks pretty bad, but there seem > to be no releases for in-tree Pythons available as of now. I'm not > familiar with how Python deals with security issues... > > This page links to commits against the various releases: > https://python-security.readthedocs.io/vuln/ctypes-buffer-overflow-pycarg_repr.html > I expect the 3.9 patches to apply as easily as the present one. I can do > that if I'm told to do it. > > A simple test is from https://bugs.python.org/issue42938 > > >>> from ctypes import * > >>> c_double.from_param(1e300) > Trace/BPT trap (core dumped) > > This is fixed with this patch. I tried running python's test suite, but > there are lots of issues with threading and async io...
Here's the corresponding diff for Python 3.9.1 Index: Makefile =================================================================== RCS file: /cvs/ports/lang/python/3.9/Makefile,v retrieving revision 1.2 diff -u -p -r1.2 Makefile --- Makefile 28 Dec 2020 22:28:14 -0000 1.2 +++ Makefile 16 Feb 2021 11:55:24 -0000 @@ -9,6 +9,7 @@ VERSION = 3.9 PATCHLEVEL = .1 SHARED_LIBS = python3.9 0.0 VERSION_SPEC = >=3.9,<3.10 +REVISION = 0 #PSUBDIR = python/3.9.0 CONFIGURE_ARGS += --with-ensurepip=no Index: files/CHANGES.OpenBSD =================================================================== RCS file: /cvs/ports/lang/python/3.9/files/CHANGES.OpenBSD,v retrieving revision 1.1.1.1 diff -u -p -r1.1.1.1 CHANGES.OpenBSD --- files/CHANGES.OpenBSD 5 Oct 2020 20:48:10 -0000 1.1.1.1 +++ files/CHANGES.OpenBSD 16 Feb 2021 11:55:59 -0000 @@ -14,5 +14,7 @@ http://bugs.python.org/issue25191 4. Disable libuuid, otherwise Python prefers it over the libc uuid functions. +5. Applied a patch for CVE-2021-3177. + These changes are available in the OpenBSD CVS repository <http://www.openbsd.org/anoncvs.html> in ports/lang/python/3.9. Index: patches/patch-2021-01-18-09-27-31_bpo-42938_4Zn4Mp_rst =================================================================== RCS file: patches/patch-2021-01-18-09-27-31_bpo-42938_4Zn4Mp_rst diff -N patches/patch-2021-01-18-09-27-31_bpo-42938_4Zn4Mp_rst --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-2021-01-18-09-27-31_bpo-42938_4Zn4Mp_rst 16 Feb 2021 11:58:17 -0000 @@ -0,0 +1,11 @@ +$OpenBSD$ + +CVE-2021-3177 +https://github.com/python/cpython/pull/24247 + +Index: 2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst +--- 2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst.orig ++++ 2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst +@@ -0,0 +1,2 @@ ++Avoid static buffers when computing the repr of :class:`ctypes.c_double` and ++:class:`ctypes.c_longdouble` values. Index: patches/patch-Lib_ctypes_test_test_parameters_py =================================================================== RCS file: patches/patch-Lib_ctypes_test_test_parameters_py diff -N patches/patch-Lib_ctypes_test_test_parameters_py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-Lib_ctypes_test_test_parameters_py 16 Feb 2021 11:58:21 -0000 @@ -0,0 +1,58 @@ +$OpenBSD$ + +CVE-2021-3177 +https://github.com/python/cpython/pull/24247 + +Index: Lib/ctypes/test/test_parameters.py +--- Lib/ctypes/test/test_parameters.py.orig ++++ Lib/ctypes/test/test_parameters.py +@@ -201,6 +201,49 @@ class SimpleTypesTestCase(unittest.TestCase): + with self.assertRaises(ZeroDivisionError): + WorseStruct().__setstate__({}, b'foo') + ++ def test_parameter_repr(self): ++ from ctypes import ( ++ c_bool, ++ c_char, ++ c_wchar, ++ c_byte, ++ c_ubyte, ++ c_short, ++ c_ushort, ++ c_int, ++ c_uint, ++ c_long, ++ c_ulong, ++ c_longlong, ++ c_ulonglong, ++ c_float, ++ c_double, ++ c_longdouble, ++ c_char_p, ++ c_wchar_p, ++ c_void_p, ++ ) ++ self.assertRegex(repr(c_bool.from_param(True)), r"^<cparam '\?' at 0x[A-Fa-f0-9]+>$") ++ self.assertEqual(repr(c_char.from_param(97)), "<cparam 'c' ('a')>") ++ self.assertRegex(repr(c_wchar.from_param('a')), r"^<cparam 'u' at 0x[A-Fa-f0-9]+>$") ++ self.assertEqual(repr(c_byte.from_param(98)), "<cparam 'b' (98)>") ++ self.assertEqual(repr(c_ubyte.from_param(98)), "<cparam 'B' (98)>") ++ self.assertEqual(repr(c_short.from_param(511)), "<cparam 'h' (511)>") ++ self.assertEqual(repr(c_ushort.from_param(511)), "<cparam 'H' (511)>") ++ self.assertRegex(repr(c_int.from_param(20000)), r"^<cparam '[li]' \(20000\)>$") ++ self.assertRegex(repr(c_uint.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$") ++ self.assertRegex(repr(c_long.from_param(20000)), r"^<cparam '[li]' \(20000\)>$") ++ self.assertRegex(repr(c_ulong.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$") ++ self.assertRegex(repr(c_longlong.from_param(20000)), r"^<cparam '[liq]' \(20000\)>$") ++ self.assertRegex(repr(c_ulonglong.from_param(20000)), r"^<cparam '[LIQ]' \(20000\)>$") ++ self.assertEqual(repr(c_float.from_param(1.5)), "<cparam 'f' (1.5)>") ++ self.assertEqual(repr(c_double.from_param(1.5)), "<cparam 'd' (1.5)>") ++ self.assertEqual(repr(c_double.from_param(1e300)), "<cparam 'd' (1e+300)>") ++ self.assertRegex(repr(c_longdouble.from_param(1.5)), r"^<cparam ('d' \(1.5\)|'g' at 0x[A-Fa-f0-9]+)>$") ++ self.assertRegex(repr(c_char_p.from_param(b'hihi')), "^<cparam 'z' \(0x[A-Fa-f0-9]+\)>$") ++ self.assertRegex(repr(c_wchar_p.from_param('hihi')), "^<cparam 'Z' \(0x[A-Fa-f0-9]+\)>$") ++ self.assertRegex(repr(c_void_p.from_param(0x12)), r"^<cparam 'P' \(0x0*12\)>$") ++ + ################################################################ + + if __name__ == '__main__': Index: patches/patch-Modules__ctypes_callproc_c =================================================================== RCS file: patches/patch-Modules__ctypes_callproc_c diff -N patches/patch-Modules__ctypes_callproc_c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-Modules__ctypes_callproc_c 16 Feb 2021 11:58:23 -0000 @@ -0,0 +1,109 @@ +$OpenBSD$ + +CVE-2021-3177 +https://github.com/python/cpython/pull/24247 + +Index: Modules/_ctypes/callproc.c +--- Modules/_ctypes/callproc.c.orig ++++ Modules/_ctypes/callproc.c +@@ -489,58 +489,47 @@ is_literal_char(unsigned char c) + static PyObject * + PyCArg_repr(PyCArgObject *self) + { +- char buffer[256]; + switch(self->tag) { + case 'b': + case 'B': +- sprintf(buffer, "<cparam '%c' (%d)>", ++ return PyUnicode_FromFormat("<cparam '%c' (%d)>", + self->tag, self->value.b); +- break; + case 'h': + case 'H': +- sprintf(buffer, "<cparam '%c' (%d)>", ++ return PyUnicode_FromFormat("<cparam '%c' (%d)>", + self->tag, self->value.h); +- break; + case 'i': + case 'I': +- sprintf(buffer, "<cparam '%c' (%d)>", ++ return PyUnicode_FromFormat("<cparam '%c' (%d)>", + self->tag, self->value.i); +- break; + case 'l': + case 'L': +- sprintf(buffer, "<cparam '%c' (%ld)>", ++ return PyUnicode_FromFormat("<cparam '%c' (%ld)>", + self->tag, self->value.l); +- break; + + case 'q': + case 'Q': +- sprintf(buffer, +-#ifdef MS_WIN32 +- "<cparam '%c' (%I64d)>", +-#else +- "<cparam '%c' (%lld)>", +-#endif ++ return PyUnicode_FromFormat("<cparam '%c' (%lld)>", + self->tag, self->value.q); +- break; + case 'd': +- sprintf(buffer, "<cparam '%c' (%f)>", +- self->tag, self->value.d); +- break; +- case 'f': +- sprintf(buffer, "<cparam '%c' (%f)>", +- self->tag, self->value.f); +- break; +- ++ case 'f': { ++ PyObject *f = PyFloat_FromDouble((self->tag == 'f') ? self->value.f : self->value.d); ++ if (f == NULL) { ++ return NULL; ++ } ++ PyObject *result = PyUnicode_FromFormat("<cparam '%c' (%R)>", self->tag, f); ++ Py_DECREF(f); ++ return result; ++ } + case 'c': + if (is_literal_char((unsigned char)self->value.c)) { +- sprintf(buffer, "<cparam '%c' ('%c')>", ++ return PyUnicode_FromFormat("<cparam '%c' ('%c')>", + self->tag, self->value.c); + } + else { +- sprintf(buffer, "<cparam '%c' ('\\x%02x')>", ++ return PyUnicode_FromFormat("<cparam '%c' ('\\x%02x')>", + self->tag, (unsigned char)self->value.c); + } +- break; + + /* Hm, are these 'z' and 'Z' codes useful at all? + Shouldn't they be replaced by the functionality of c_string +@@ -549,22 +538,20 @@ PyCArg_repr(PyCArgObject *self) + case 'z': + case 'Z': + case 'P': +- sprintf(buffer, "<cparam '%c' (%p)>", ++ return PyUnicode_FromFormat("<cparam '%c' (%p)>", + self->tag, self->value.p); + break; + + default: + if (is_literal_char((unsigned char)self->tag)) { +- sprintf(buffer, "<cparam '%c' at %p>", ++ return PyUnicode_FromFormat("<cparam '%c' at %p>", + (unsigned char)self->tag, (void *)self); + } + else { +- sprintf(buffer, "<cparam 0x%02x at %p>", ++ return PyUnicode_FromFormat("<cparam 0x%02x at %p>", + (unsigned char)self->tag, (void *)self); + } +- break; + } +- return PyUnicode_FromString(buffer); + } + + static PyMemberDef PyCArgType_members[] = {
