https://github.com/python/cpython/commit/4e530b82f37ec89e3b54bc5d0a1612c643bff1e0
commit: 4e530b82f37ec89e3b54bc5d0a1612c643bff1e0
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-09T20:59:57+02:00
summary:
gh-129813: Check size in PyBytesWriter_FinishWithSize() (#157226)
Reject size larger than the allocated size.
Check also negative size in PyBytesWriter_FinishWithSize() to always
raise ValueError. Previously, the function raised SystemError or
ValueError depending on the code path.
Replace _PyBytesWriter_GetAllocated() optimization with
_PyBytesWriter_ResizeToAllocated() to update the writer size to its
allocated size.
test_capi: only run the 3 "example" tests in BytesWriterTest. There
is no need to rerun the same tests in ByteArrayWriterTest.
files:
M Lib/test/test_capi/test_bytes.py
M Modules/_testcapi/bytes.c
M Objects/bytesobject.c
diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py
index 38cda931e7d54f3..4c1431bacef0a27 100644
--- a/Lib/test/test_capi/test_bytes.py
+++ b/Lib/test/test_capi/test_bytes.py
@@ -317,14 +317,20 @@ def test_create(self):
def test_finish_with_size(self):
# Test PyBytesWriter_FinishWithSize()
- writer = self.create_writer(10, b'abc')
+ writer = self.create_writer(10, b'abcdef')
self.assertEqual(writer.get_size(), 10)
self.assertEqual(writer.finish_with_size(3), self.result_type(b'abc'))
+ # Error if the size is negative
writer = self.create_writer(3, b'abc')
- with self.assertRaises(SystemError):
+ with self.assertRaises(ValueError):
writer.finish_with_size(-3)
+ # Error if the requested size is larger than the allocated size
+ writer = self.create_writer(3, b'abc')
+ with self.assertRaises(ValueError):
+ writer.finish_with_size(4)
+
def test_write_bytes(self):
# Test PyBytesWriter_WriteBytes()
writer = self.create_writer()
@@ -378,15 +384,6 @@ def test_format_i(self):
writer.format_i(b'y=%i', 456)
self.assertEqual(writer.finish(), self.result_type(b'x=123, y=456'))
- def test_example_abc(self):
- self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
-
- def test_example_resize(self):
- self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World')
-
- def test_example_highlevel(self):
- self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
-
class BytesWriterTest(BaseWriterTest, unittest.TestCase):
result_type = bytes
@@ -424,6 +421,15 @@ def test_singletons(self):
writer.write_bytes(unused_text, len(unused_text))
self.assertIs(writer.finish_with_size(1), singletons[ch])
+ def test_example_abc(self):
+ self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
+
+ def test_example_resize(self):
+ self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World')
+
+ def test_example_highlevel(self):
+ self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
+
class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase):
result_type = bytearray
diff --git a/Modules/_testcapi/bytes.c b/Modules/_testcapi/bytes.c
index a868c684cc987cd..4830cc8b54bd837 100644
--- a/Modules/_testcapi/bytes.c
+++ b/Modules/_testcapi/bytes.c
@@ -353,12 +353,114 @@ byteswriter_highlevel(PyObject *Py_UNUSED(module),
PyObject *Py_UNUSED(args))
}
+static size_t
+pybyteswriter_small_buffer_size(void)
+{
+ return offsetof(PyBytesWriter, obj);
+}
+
+
+// Test the "Pointer" API of PyBytesWriter
+static PyObject *
+test_byteswriter_ptr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
+{
+ // Test PyBytesWriter_FinishWithPointer(): create the string "abc"
+ PyBytesWriter *writer = PyBytesWriter_Create(3);
+ if (writer == NULL) {
+ return NULL;
+ }
+ char *str = PyBytesWriter_GetData(writer);
+ memcpy(str, "abc", 3);
+ str += 3;
+ PyObject *result = PyBytesWriter_FinishWithPointer(writer, str);
+ if (result == NULL) {
+ return NULL;
+ }
+ assert(PyBytes_GET_SIZE(result) == 3);
+ assert(memcmp(PyBytes_AS_STRING(result), "abc", 3) == 0);
+ Py_DECREF(result);
+
+ // Test PyBytesWriter_GrowAndUpdatePointer().
+ // Start by using the small buffer, and then resize to use a bytes object.
+ writer = PyBytesWriter_Create(0);
+ if (writer == NULL) {
+ return NULL;
+ }
+ str = PyBytesWriter_GetData(writer);
+
+ str = PyBytesWriter_GrowAndUpdatePointer(writer, 100, str);
+ if (str == NULL) {
+ PyBytesWriter_Discard(writer);
+ return NULL;
+ }
+ memset(str, 'x', 100);
+ str += 100;
+
+ // make sure that the test switchs to a bytes object
+ assert((100 + 200) > pybyteswriter_small_buffer_size());
+ char *old_str = str;
+ str = PyBytesWriter_GrowAndUpdatePointer(writer, 200, str);
+ if (str == NULL) {
+ PyBytesWriter_Discard(writer);
+ return NULL;
+ }
+ // make sure that we moved from the small buffer to a bytes object
+ assert(str != old_str);
+ memset(str, 'y', 200);
+ str += 200;
+
+ result = PyBytesWriter_FinishWithPointer(writer, str);
+ if (result == NULL) {
+ return NULL;
+ }
+ assert(PyBytes_GET_SIZE(result) == 300);
+ str = PyBytes_AS_STRING(result);
+ for (Py_ssize_t i=0; i < 100; i++) {
+ assert(str[i] == 'x');
+ }
+ for (Py_ssize_t i=0; i < 200; i++) {
+ assert(str[100 + i] == 'y');
+ }
+ Py_DECREF(result);
+
+ // Check that PyBytesWriter_FinishWithPointer() rejects pointer
+ // after the buffer end (create a string larger than the allocated size)
+ writer = PyBytesWriter_Create(3);
+ if (writer == NULL) {
+ return NULL;
+ }
+ str = PyBytesWriter_GetData(writer);
+ memcpy(str, "abc", 3);
+ str += 4; // off-by-one bug on purpose
+ result = PyBytesWriter_FinishWithPointer(writer, str);
+ assert(result == NULL);
+ assert(PyErr_ExceptionMatches(PyExc_ValueError));
+ PyErr_Clear();
+
+ // Check that PyBytesWriter_FinishWithPointer() rejects pointer
+ // before the buffer start (negative size)
+ writer = PyBytesWriter_Create(3);
+ if (writer == NULL) {
+ return NULL;
+ }
+ str = PyBytesWriter_GetData(writer);
+ str--; // bug on purpose: go before the buffer start
+ result = PyBytesWriter_FinishWithPointer(writer, str);
+ assert(result == NULL);
+ assert(PyErr_ExceptionMatches(PyExc_ValueError));
+ PyErr_Clear();
+
+ Py_RETURN_NONE;
+}
+
+
static PyMethodDef test_methods[] = {
{"bytes_resize", bytes_resize, METH_VARARGS},
{"bytes_join", bytes_join, METH_VARARGS},
{"byteswriter_abc", byteswriter_abc, METH_NOARGS},
{"byteswriter_resize", byteswriter_resize, METH_NOARGS},
{"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
+ {"test_byteswriter_ptr", test_byteswriter_ptr, METH_NOARGS},
{NULL},
};
@@ -380,7 +482,7 @@ _PyTestCapi_Init_Bytes(PyObject *m)
Py_DECREF(writer_type);
// PyBytesWriter.obj is the second member, small_buffer is the first member
- long size = (long)offsetof(PyBytesWriter, obj);
+ long size = (long)pybyteswriter_small_buffer_size();
if (PyModule_AddIntConstant(m, "PyBytesWriter_small_buffer", size) < 0) {
Py_DECREF(writer_type);
return -1;
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 1b8dca734974153..2ae55b33f4f49d7 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -31,7 +31,7 @@ class bytes "PyBytesObject *" "&PyBytes_Type"
/* Forward declaration */
static void* _PyBytesWriter_ResizeAndUpdatePointer(PyBytesWriter *writer,
Py_ssize_t size, void
*data);
-static Py_ssize_t _PyBytesWriter_GetAllocated(PyBytesWriter *writer);
+static Py_ssize_t _PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer);
#define CHARACTERS _Py_SINGLETON(bytes_characters)
@@ -2993,8 +2993,8 @@ _PyBytes_FromList(PyObject *x)
if (writer == NULL) {
return NULL;
}
+ size = _PyBytesWriter_ResizeToAllocated(writer);
char *str = PyBytesWriter_GetData(writer);
- size = _PyBytesWriter_GetAllocated(writer);
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(x); i++) {
PyObject *item = _PyList_GetItemRef((PyListObject *)x, i);
@@ -3017,7 +3017,9 @@ _PyBytes_FromList(PyObject *x)
if (str == NULL) {
goto error;
}
- size = _PyBytesWriter_GetAllocated(writer);
+
+ // Set the writer size to its allocated size
+ size = _PyBytesWriter_ResizeToAllocated(writer);
}
*str++ = (char) value;
}
@@ -3075,8 +3077,8 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
if (writer == NULL) {
return NULL;
}
+ size = _PyBytesWriter_ResizeToAllocated(writer);
char *str = PyBytesWriter_GetData(writer);
- size = _PyBytesWriter_GetAllocated(writer);
/* Run the iterator to exhaustion */
for (i = 0; ; i++) {
@@ -3110,7 +3112,9 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
if (str == NULL) {
goto error;
}
- size = _PyBytesWriter_GetAllocated(writer);
+
+ // Set the writer size to its allocated size
+ size = _PyBytesWriter_ResizeToAllocated(writer);
}
*str++ = (char) value;
}
@@ -3747,6 +3751,19 @@ PyBytesWriter_Discard(PyBytesWriter *writer)
PyObject*
PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
{
+ // Check for negative size here to raise ValueError in all cases, rather
+ // than having a different exception depending on the code path. For
+ // example, _PyBytes_Resize() raises SystemError on negative size.
+ if (size < 0) {
+ PyErr_Format(PyExc_ValueError, "size must be positive");
+ goto error;
+ }
+
+ if (size > writer->size) {
+ PyErr_SetString(PyExc_ValueError, "size larger than allocated size");
+ goto error;
+ }
+
PyObject *result;
if (size == 0) {
result = bytes_get_empty();
@@ -3804,12 +3821,6 @@ PyObject*
PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
{
Py_ssize_t size = (char*)buf - byteswriter_data(writer);
- if (size < 0 || size > byteswriter_allocated(writer)) {
- PyBytesWriter_Discard(writer);
- PyErr_SetString(PyExc_ValueError, "invalid end pointer");
- return NULL;
- }
-
return PyBytesWriter_FinishWithSize(writer, size);
}
@@ -3828,13 +3839,6 @@ PyBytesWriter_GetSize(PyBytesWriter *writer)
}
-static Py_ssize_t
-_PyBytesWriter_GetAllocated(PyBytesWriter *writer)
-{
- return byteswriter_allocated(writer);
-}
-
-
int
PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
{
@@ -3934,3 +3938,15 @@ PyBytesWriter_Format(PyBytesWriter *writer, const char
*format, ...)
Py_ssize_t size = buf - byteswriter_data(writer);
return PyBytesWriter_Resize(writer, size);
}
+
+
+// Resize the writer to its allocated size.
+// Return the new size.
+// The function cannot fail.
+static Py_ssize_t
+_PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer)
+{
+ Py_ssize_t allocated = byteswriter_allocated(writer);
+ writer->size = allocated;
+ return allocated;
+}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]