From 7e480afd755cd782c6e8c2a4525fe77697ba0f94 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 31 Jan 2024 15:44:21 +0300 Subject: [PATCH 1/5] gh-114685: `PyBuffer_FillInfo` now raises on `PyBUF_{READ,WRITE}` --- Lib/test/test_buffer.py | 17 ++++++++++++++++ ...-01-31-15-43-35.gh-issue-114685.n7aRmX.rst | 3 +++ Modules/_testcapimodule.c | 20 +++++++++++++++++++ Objects/abstract.c | 16 ++++++++++----- 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 535b795f508a24..3e2be34694c905 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4591,6 +4591,23 @@ def test_c_buffer_invalid_flags(self): self.assertRaises(SystemError, buf.__buffer__, PyBUF_READ) self.assertRaises(SystemError, buf.__buffer__, PyBUF_WRITE) + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_fill_buffer_invalid_flags(self): + # PyBuffer_FillInfo + self.assertRaises(SystemError, _testcapi.buffer_fill_info, + "abc", 0, PyBUF_READ) + self.assertRaises(SystemError, _testcapi.buffer_fill_info, + "abc", 0, PyBUF_WRITE) + + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_fill_buffer_readonly_and_writable(self): + self.assertEqual( + str(_testcapi.buffer_fill_info("abc", 1, PyBUF_SIMPLE), "utf8"), + "abc", + ) + self.assertRaises(BufferError, _testcapi.buffer_fill_info, + "abc", 1, PyBUF_WRITABLE) + def test_inheritance(self): class A(bytearray): def __buffer__(self, flags): diff --git a/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst b/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst new file mode 100644 index 00000000000000..76ff00645fe57d --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst @@ -0,0 +1,3 @@ +:c:func:`PyBuffer_FillInfo` now raises a :exc:`SystemError` if called with +:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should +only be used with the ``PyMemoryView_*`` C API. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 6def680190b1a6..8f795077d26801 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1261,6 +1261,25 @@ make_memoryview_from_NULL_pointer(PyObject *self, PyObject *Py_UNUSED(ignored)) return PyMemoryView_FromBuffer(&info); } +static PyObject * +buffer_fill_info(PyObject *self, PyObject *args) +{ + Py_buffer info; + const char *data; + Py_ssize_t size; + int readonly; + int flags; + + if (!PyArg_ParseTuple(args, "s#ii:buffer_fill_info", + &data, &size, &readonly, &flags)) { + return NULL; + } + + if (PyBuffer_FillInfo(&info, NULL, (void *)data, size, readonly, flags) < 0) + return NULL; + return PyMemoryView_FromBuffer(&info); +} + static PyObject * test_from_contiguous(PyObject* self, PyObject *Py_UNUSED(ignored)) { @@ -3314,6 +3333,7 @@ static PyMethodDef TestMethods[] = { {"eval_code_ex", eval_eval_code_ex, METH_VARARGS}, {"make_memoryview_from_NULL_pointer", make_memoryview_from_NULL_pointer, METH_NOARGS}, + {"buffer_fill_info", buffer_fill_info, METH_VARARGS}, {"crash_no_current_thread", crash_no_current_thread, METH_NOARGS}, {"test_current_tstate_matches", test_current_tstate_matches, METH_NOARGS}, {"run_in_subinterp", run_in_subinterp, METH_VARARGS}, diff --git a/Objects/abstract.c b/Objects/abstract.c index daf04eb4ab2cda..07d4b89fe188c8 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -767,11 +767,17 @@ PyBuffer_FillInfo(Py_buffer *view, PyObject *obj, void *buf, Py_ssize_t len, return -1; } - if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && - (readonly == 1)) { - PyErr_SetString(PyExc_BufferError, - "Object is not writable."); - return -1; + if (flags != PyBUF_SIMPLE) { /* fast path */ + if (flags == PyBUF_READ || flags == PyBUF_WRITE) { + PyErr_BadInternalCall(); + return -1; + } + if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && + (readonly == 1)) { + PyErr_SetString(PyExc_BufferError, + "Object is not writable."); + return -1; + } } view->obj = Py_XNewRef(obj); From 81d751e1d616c79588e82032f5f1888aee065410 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 1 Feb 2024 08:05:31 +0300 Subject: [PATCH 2/5] Update Modules/_testcapimodule.c Co-authored-by: AN Long --- Modules/_testcapimodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 8f795077d26801..e67de3eeb6e17e 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1275,8 +1275,9 @@ buffer_fill_info(PyObject *self, PyObject *args) return NULL; } - if (PyBuffer_FillInfo(&info, NULL, (void *)data, size, readonly, flags) < 0) + if (PyBuffer_FillInfo(&info, NULL, (void *)data, size, readonly, flags) < 0) { return NULL; + } return PyMemoryView_FromBuffer(&info); } From 35fb1d5f26aa34e4a3649fa92f6dff8a53ae963b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Feb 2024 11:26:22 +0300 Subject: [PATCH 3/5] Address review --- Lib/test/test_buffer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 3e2be34694c905..c12ce493e48840 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4601,10 +4601,12 @@ def test_c_fill_buffer_invalid_flags(self): @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_fill_buffer_readonly_and_writable(self): - self.assertEqual( - str(_testcapi.buffer_fill_info("abc", 1, PyBUF_SIMPLE), "utf8"), - "abc", - ) + with _testcapi.buffer_fill_info(b"abc", 1, PyBUF_SIMPLE) as m: + self.assertEqual(bytes(m), b"abc") + self.assertTrue(m.readonly) + with _testcapi.buffer_fill_info(b"abc", 0, PyBUF_WRITABLE) as m: + self.assertEqual(bytes(m), b"abc") + self.assertFalse(m.readonly) self.assertRaises(BufferError, _testcapi.buffer_fill_info, "abc", 1, PyBUF_WRITABLE) From 557706af06dbc99e9f93ff5ce4d87079845c7b01 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Feb 2024 17:28:58 +0300 Subject: [PATCH 4/5] Address review --- Lib/test/test_buffer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index c12ce493e48840..22d7093efdb549 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4601,10 +4601,11 @@ def test_c_fill_buffer_invalid_flags(self): @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_fill_buffer_readonly_and_writable(self): - with _testcapi.buffer_fill_info(b"abc", 1, PyBUF_SIMPLE) as m: + source = b"abc" + with _testcapi.buffer_fill_info(source, 1, PyBUF_SIMPLE) as m: self.assertEqual(bytes(m), b"abc") self.assertTrue(m.readonly) - with _testcapi.buffer_fill_info(b"abc", 0, PyBUF_WRITABLE) as m: + with _testcapi.buffer_fill_info(source, 0, PyBUF_WRITABLE) as m: self.assertEqual(bytes(m), b"abc") self.assertFalse(m.readonly) self.assertRaises(BufferError, _testcapi.buffer_fill_info, From 17e93822eb1897ff949055d54339cd4f42c47466 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 4 Feb 2024 21:54:11 +0300 Subject: [PATCH 5/5] Address review --- Lib/test/test_buffer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 22d7093efdb549..5b1b95b9c82064 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4594,10 +4594,11 @@ def test_c_buffer_invalid_flags(self): @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_fill_buffer_invalid_flags(self): # PyBuffer_FillInfo + source = b"abc" self.assertRaises(SystemError, _testcapi.buffer_fill_info, - "abc", 0, PyBUF_READ) + source, 0, PyBUF_READ) self.assertRaises(SystemError, _testcapi.buffer_fill_info, - "abc", 0, PyBUF_WRITE) + source, 0, PyBUF_WRITE) @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_fill_buffer_readonly_and_writable(self): @@ -4609,7 +4610,7 @@ def test_c_fill_buffer_readonly_and_writable(self): self.assertEqual(bytes(m), b"abc") self.assertFalse(m.readonly) self.assertRaises(BufferError, _testcapi.buffer_fill_info, - "abc", 1, PyBUF_WRITABLE) + source, 1, PyBUF_WRITABLE) def test_inheritance(self): class A(bytearray):