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[] = {

Reply via email to