Skip to content

Commit 4e530b8

Browse files
authored
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.
1 parent 52ffffe commit 4e530b8

3 files changed

Lines changed: 154 additions & 30 deletions

File tree

Lib/test/test_capi/test_bytes.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,20 @@ def test_create(self):
317317

318318
def test_finish_with_size(self):
319319
# Test PyBytesWriter_FinishWithSize()
320-
writer = self.create_writer(10, b'abc')
320+
writer = self.create_writer(10, b'abcdef')
321321
self.assertEqual(writer.get_size(), 10)
322322
self.assertEqual(writer.finish_with_size(3), self.result_type(b'abc'))
323323

324+
# Error if the size is negative
324325
writer = self.create_writer(3, b'abc')
325-
with self.assertRaises(SystemError):
326+
with self.assertRaises(ValueError):
326327
writer.finish_with_size(-3)
327328

329+
# Error if the requested size is larger than the allocated size
330+
writer = self.create_writer(3, b'abc')
331+
with self.assertRaises(ValueError):
332+
writer.finish_with_size(4)
333+
328334
def test_write_bytes(self):
329335
# Test PyBytesWriter_WriteBytes()
330336
writer = self.create_writer()
@@ -378,15 +384,6 @@ def test_format_i(self):
378384
writer.format_i(b'y=%i', 456)
379385
self.assertEqual(writer.finish(), self.result_type(b'x=123, y=456'))
380386

381-
def test_example_abc(self):
382-
self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
383-
384-
def test_example_resize(self):
385-
self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World')
386-
387-
def test_example_highlevel(self):
388-
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
389-
390387

391388
class BytesWriterTest(BaseWriterTest, unittest.TestCase):
392389
result_type = bytes
@@ -424,6 +421,15 @@ def test_singletons(self):
424421
writer.write_bytes(unused_text, len(unused_text))
425422
self.assertIs(writer.finish_with_size(1), singletons[ch])
426423

424+
def test_example_abc(self):
425+
self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
426+
427+
def test_example_resize(self):
428+
self.assertEqual(_testcapi.byteswriter_resize(), b'Hello World')
429+
430+
def test_example_highlevel(self):
431+
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
432+
427433

428434
class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase):
429435
result_type = bytearray

Modules/_testcapi/bytes.c

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,114 @@ byteswriter_highlevel(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
353353
}
354354

355355

356+
static size_t
357+
pybyteswriter_small_buffer_size(void)
358+
{
359+
return offsetof(PyBytesWriter, obj);
360+
}
361+
362+
363+
// Test the "Pointer" API of PyBytesWriter
364+
static PyObject *
365+
test_byteswriter_ptr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
366+
{
367+
// Test PyBytesWriter_FinishWithPointer(): create the string "abc"
368+
PyBytesWriter *writer = PyBytesWriter_Create(3);
369+
if (writer == NULL) {
370+
return NULL;
371+
}
372+
char *str = PyBytesWriter_GetData(writer);
373+
memcpy(str, "abc", 3);
374+
str += 3;
375+
PyObject *result = PyBytesWriter_FinishWithPointer(writer, str);
376+
if (result == NULL) {
377+
return NULL;
378+
}
379+
assert(PyBytes_GET_SIZE(result) == 3);
380+
assert(memcmp(PyBytes_AS_STRING(result), "abc", 3) == 0);
381+
Py_DECREF(result);
382+
383+
// Test PyBytesWriter_GrowAndUpdatePointer().
384+
// Start by using the small buffer, and then resize to use a bytes object.
385+
writer = PyBytesWriter_Create(0);
386+
if (writer == NULL) {
387+
return NULL;
388+
}
389+
str = PyBytesWriter_GetData(writer);
390+
391+
str = PyBytesWriter_GrowAndUpdatePointer(writer, 100, str);
392+
if (str == NULL) {
393+
PyBytesWriter_Discard(writer);
394+
return NULL;
395+
}
396+
memset(str, 'x', 100);
397+
str += 100;
398+
399+
// make sure that the test switchs to a bytes object
400+
assert((100 + 200) > pybyteswriter_small_buffer_size());
401+
char *old_str = str;
402+
str = PyBytesWriter_GrowAndUpdatePointer(writer, 200, str);
403+
if (str == NULL) {
404+
PyBytesWriter_Discard(writer);
405+
return NULL;
406+
}
407+
// make sure that we moved from the small buffer to a bytes object
408+
assert(str != old_str);
409+
memset(str, 'y', 200);
410+
str += 200;
411+
412+
result = PyBytesWriter_FinishWithPointer(writer, str);
413+
if (result == NULL) {
414+
return NULL;
415+
}
416+
assert(PyBytes_GET_SIZE(result) == 300);
417+
str = PyBytes_AS_STRING(result);
418+
for (Py_ssize_t i=0; i < 100; i++) {
419+
assert(str[i] == 'x');
420+
}
421+
for (Py_ssize_t i=0; i < 200; i++) {
422+
assert(str[100 + i] == 'y');
423+
}
424+
Py_DECREF(result);
425+
426+
// Check that PyBytesWriter_FinishWithPointer() rejects pointer
427+
// after the buffer end (create a string larger than the allocated size)
428+
writer = PyBytesWriter_Create(3);
429+
if (writer == NULL) {
430+
return NULL;
431+
}
432+
str = PyBytesWriter_GetData(writer);
433+
memcpy(str, "abc", 3);
434+
str += 4; // off-by-one bug on purpose
435+
result = PyBytesWriter_FinishWithPointer(writer, str);
436+
assert(result == NULL);
437+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
438+
PyErr_Clear();
439+
440+
// Check that PyBytesWriter_FinishWithPointer() rejects pointer
441+
// before the buffer start (negative size)
442+
writer = PyBytesWriter_Create(3);
443+
if (writer == NULL) {
444+
return NULL;
445+
}
446+
str = PyBytesWriter_GetData(writer);
447+
str--; // bug on purpose: go before the buffer start
448+
result = PyBytesWriter_FinishWithPointer(writer, str);
449+
assert(result == NULL);
450+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
451+
PyErr_Clear();
452+
453+
Py_RETURN_NONE;
454+
}
455+
456+
356457
static PyMethodDef test_methods[] = {
357458
{"bytes_resize", bytes_resize, METH_VARARGS},
358459
{"bytes_join", bytes_join, METH_VARARGS},
359460
{"byteswriter_abc", byteswriter_abc, METH_NOARGS},
360461
{"byteswriter_resize", byteswriter_resize, METH_NOARGS},
361462
{"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
463+
{"test_byteswriter_ptr", test_byteswriter_ptr, METH_NOARGS},
362464
{NULL},
363465
};
364466

@@ -380,7 +482,7 @@ _PyTestCapi_Init_Bytes(PyObject *m)
380482
Py_DECREF(writer_type);
381483

382484
// PyBytesWriter.obj is the second member, small_buffer is the first member
383-
long size = (long)offsetof(PyBytesWriter, obj);
485+
long size = (long)pybyteswriter_small_buffer_size();
384486
if (PyModule_AddIntConstant(m, "PyBytesWriter_small_buffer", size) < 0) {
385487
Py_DECREF(writer_type);
386488
return -1;

Objects/bytesobject.c

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class bytes "PyBytesObject *" "&PyBytes_Type"
3131
/* Forward declaration */
3232
static void* _PyBytesWriter_ResizeAndUpdatePointer(PyBytesWriter *writer,
3333
Py_ssize_t size, void *data);
34-
static Py_ssize_t _PyBytesWriter_GetAllocated(PyBytesWriter *writer);
34+
static Py_ssize_t _PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer);
3535

3636

3737
#define CHARACTERS _Py_SINGLETON(bytes_characters)
@@ -2993,8 +2993,8 @@ _PyBytes_FromList(PyObject *x)
29932993
if (writer == NULL) {
29942994
return NULL;
29952995
}
2996+
size = _PyBytesWriter_ResizeToAllocated(writer);
29962997
char *str = PyBytesWriter_GetData(writer);
2997-
size = _PyBytesWriter_GetAllocated(writer);
29982998

29992999
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(x); i++) {
30003000
PyObject *item = _PyList_GetItemRef((PyListObject *)x, i);
@@ -3017,7 +3017,9 @@ _PyBytes_FromList(PyObject *x)
30173017
if (str == NULL) {
30183018
goto error;
30193019
}
3020-
size = _PyBytesWriter_GetAllocated(writer);
3020+
3021+
// Set the writer size to its allocated size
3022+
size = _PyBytesWriter_ResizeToAllocated(writer);
30213023
}
30223024
*str++ = (char) value;
30233025
}
@@ -3075,8 +3077,8 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
30753077
if (writer == NULL) {
30763078
return NULL;
30773079
}
3080+
size = _PyBytesWriter_ResizeToAllocated(writer);
30783081
char *str = PyBytesWriter_GetData(writer);
3079-
size = _PyBytesWriter_GetAllocated(writer);
30803082

30813083
/* Run the iterator to exhaustion */
30823084
for (i = 0; ; i++) {
@@ -3110,7 +3112,9 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
31103112
if (str == NULL) {
31113113
goto error;
31123114
}
3113-
size = _PyBytesWriter_GetAllocated(writer);
3115+
3116+
// Set the writer size to its allocated size
3117+
size = _PyBytesWriter_ResizeToAllocated(writer);
31143118
}
31153119
*str++ = (char) value;
31163120
}
@@ -3747,6 +3751,19 @@ PyBytesWriter_Discard(PyBytesWriter *writer)
37473751
PyObject*
37483752
PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
37493753
{
3754+
// Check for negative size here to raise ValueError in all cases, rather
3755+
// than having a different exception depending on the code path. For
3756+
// example, _PyBytes_Resize() raises SystemError on negative size.
3757+
if (size < 0) {
3758+
PyErr_Format(PyExc_ValueError, "size must be positive");
3759+
goto error;
3760+
}
3761+
3762+
if (size > writer->size) {
3763+
PyErr_SetString(PyExc_ValueError, "size larger than allocated size");
3764+
goto error;
3765+
}
3766+
37503767
PyObject *result;
37513768
if (size == 0) {
37523769
result = bytes_get_empty();
@@ -3804,12 +3821,6 @@ PyObject*
38043821
PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
38053822
{
38063823
Py_ssize_t size = (char*)buf - byteswriter_data(writer);
3807-
if (size < 0 || size > byteswriter_allocated(writer)) {
3808-
PyBytesWriter_Discard(writer);
3809-
PyErr_SetString(PyExc_ValueError, "invalid end pointer");
3810-
return NULL;
3811-
}
3812-
38133824
return PyBytesWriter_FinishWithSize(writer, size);
38143825
}
38153826

@@ -3828,13 +3839,6 @@ PyBytesWriter_GetSize(PyBytesWriter *writer)
38283839
}
38293840

38303841

3831-
static Py_ssize_t
3832-
_PyBytesWriter_GetAllocated(PyBytesWriter *writer)
3833-
{
3834-
return byteswriter_allocated(writer);
3835-
}
3836-
3837-
38383842
int
38393843
PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
38403844
{
@@ -3934,3 +3938,15 @@ PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
39343938
Py_ssize_t size = buf - byteswriter_data(writer);
39353939
return PyBytesWriter_Resize(writer, size);
39363940
}
3941+
3942+
3943+
// Resize the writer to its allocated size.
3944+
// Return the new size.
3945+
// The function cannot fail.
3946+
static Py_ssize_t
3947+
_PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer)
3948+
{
3949+
Py_ssize_t allocated = byteswriter_allocated(writer);
3950+
writer->size = allocated;
3951+
return allocated;
3952+
}

0 commit comments

Comments
 (0)