Skip to content

Commit c38a347

Browse files
authored
[3.15] gh-129813: Check size in PyBytesWriter_FinishWithSize() (#157226) (#159261)
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. (cherry picked from commit 4e530b8)
1 parent aed16da commit c38a347

3 files changed

Lines changed: 191 additions & 27 deletions

File tree

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,11 @@ def test_join(self):
316316
bytes_join(b'', NULL)
317317

318318

319-
class BytesWriterTest(unittest.TestCase):
320-
RESULT_TYPE = bytes
319+
class BaseWriterTest:
320+
RESULT_TYPE = NotImplementedError
321321

322322
def create_writer(self, alloc=0, string=b''):
323-
return _testcapi.PyBytesWriter(alloc, string, 0)
323+
raise NotImplementedError
324324

325325
def bytes_equal(self, result, expected):
326326
# Similar to assertEqual(), but check also that the result type
@@ -340,14 +340,20 @@ def test_create(self):
340340

341341
def test_finish_with_size(self):
342342
# Test PyBytesWriter_FinishWithSize()
343-
writer = self.create_writer(10, b'abc')
343+
writer = self.create_writer(10, b'abcdef')
344344
self.assertEqual(writer.get_size(), 10)
345345
self.bytes_equal(writer.finish_with_size(3), b'abc')
346346

347+
# Error if the size is negative
347348
writer = self.create_writer(3, b'abc')
348-
with self.assertRaises(SystemError):
349+
with self.assertRaises(ValueError):
349350
writer.finish_with_size(-3)
350351

352+
# Error if the requested size is larger than the allocated size
353+
writer = self.create_writer(3, b'abc')
354+
with self.assertRaises(ValueError):
355+
writer.finish_with_size(4)
356+
351357
def test_write_bytes(self):
352358
# Test PyBytesWriter_WriteBytes()
353359
writer = self.create_writer()
@@ -420,6 +426,36 @@ def test_format_s_memory_error(self):
420426
writer.write_bytes(b'after', -1)
421427
self.bytes_equal(writer.finish(), b'after')
422428

429+
430+
class BytesWriterTest(BaseWriterTest, unittest.TestCase):
431+
RESULT_TYPE = bytes
432+
433+
def create_writer(self, alloc=0, string=b''):
434+
# Test PyBytesWriter_Create()
435+
return _testcapi.PyBytesWriter(alloc, string, 0)
436+
437+
# Only PyBytesWriter_Create() returns singletons
438+
def test_singletons(self):
439+
empty = b''
440+
singletons = {ch: bytes((ch,)) for ch in range(256)}
441+
small_buffer = _testcapi.PyBytesWriter_small_buffer
442+
443+
writer = self.create_writer()
444+
self.assertIs(writer.finish(), empty)
445+
446+
# Test writer larger than small_buffer
447+
writer = self.create_writer()
448+
unused_text = b'x' * (small_buffer * 2)
449+
writer.write_bytes(unused_text, len(unused_text))
450+
self.assertIs(writer.finish_with_size(0), empty)
451+
452+
for ch in range(256):
453+
text = bytes((ch,))
454+
455+
writer = self.create_writer()
456+
writer.write_bytes(text, 1)
457+
self.assertIs(writer.finish(), singletons[ch])
458+
423459
def test_example_abc(self):
424460
self.assertEqual(_testcapi.byteswriter_abc(), b'abc')
425461

@@ -430,10 +466,11 @@ def test_example_highlevel(self):
430466
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
431467

432468

433-
class ByteArrayWriterTest(BytesWriterTest):
469+
class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase):
434470
RESULT_TYPE = bytearray
435471

436472
def create_writer(self, alloc=0, string=b''):
473+
# Test private _PyBytesWriter_CreateByteArray()
437474
return _testcapi.PyBytesWriter(alloc, string, 1)
438475

439476

‎Modules/_testcapi/bytes.c‎

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "parts.h"
55
#include "util.h"
66

7+
#include <stddef.h> // offsetof()
8+
79
#include "pycore_bytesobject.h" // _PyBytesWriter_CreateByteArray()
810

911

@@ -149,9 +151,9 @@ writer_write_bytes(PyObject *self_raw, PyObject *args)
149151
return NULL;
150152
}
151153

152-
char *bytes;
153-
Py_ssize_t size;
154-
if (!PyArg_ParseTuple(args, "yn", &bytes, &size)) {
154+
const char *bytes;
155+
Py_ssize_t unused_size, size;
156+
if (!PyArg_ParseTuple(args, "y#n", &bytes, &unused_size, &size)) {
155157
return NULL;
156158
}
157159

@@ -373,12 +375,114 @@ byteswriter_highlevel(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
373375
}
374376

375377

378+
static size_t
379+
pybyteswriter_small_buffer_size(void)
380+
{
381+
return offsetof(PyBytesWriter, obj);
382+
}
383+
384+
385+
// Test the "Pointer" API of PyBytesWriter
386+
static PyObject *
387+
test_byteswriter_ptr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
388+
{
389+
// Test PyBytesWriter_FinishWithPointer(): create the string "abc"
390+
PyBytesWriter *writer = PyBytesWriter_Create(3);
391+
if (writer == NULL) {
392+
return NULL;
393+
}
394+
char *str = PyBytesWriter_GetData(writer);
395+
memcpy(str, "abc", 3);
396+
str += 3;
397+
PyObject *result = PyBytesWriter_FinishWithPointer(writer, str);
398+
if (result == NULL) {
399+
return NULL;
400+
}
401+
assert(PyBytes_GET_SIZE(result) == 3);
402+
assert(memcmp(PyBytes_AS_STRING(result), "abc", 3) == 0);
403+
Py_DECREF(result);
404+
405+
// Test PyBytesWriter_GrowAndUpdatePointer().
406+
// Start by using the small buffer, and then resize to use a bytes object.
407+
writer = PyBytesWriter_Create(0);
408+
if (writer == NULL) {
409+
return NULL;
410+
}
411+
str = PyBytesWriter_GetData(writer);
412+
413+
str = PyBytesWriter_GrowAndUpdatePointer(writer, 100, str);
414+
if (str == NULL) {
415+
PyBytesWriter_Discard(writer);
416+
return NULL;
417+
}
418+
memset(str, 'x', 100);
419+
str += 100;
420+
421+
// make sure that the test switchs to a bytes object
422+
assert((100 + 200) > pybyteswriter_small_buffer_size());
423+
char *old_str = str;
424+
str = PyBytesWriter_GrowAndUpdatePointer(writer, 200, str);
425+
if (str == NULL) {
426+
PyBytesWriter_Discard(writer);
427+
return NULL;
428+
}
429+
// make sure that we moved from the small buffer to a bytes object
430+
assert(str != old_str);
431+
memset(str, 'y', 200);
432+
str += 200;
433+
434+
result = PyBytesWriter_FinishWithPointer(writer, str);
435+
if (result == NULL) {
436+
return NULL;
437+
}
438+
assert(PyBytes_GET_SIZE(result) == 300);
439+
str = PyBytes_AS_STRING(result);
440+
for (Py_ssize_t i=0; i < 100; i++) {
441+
assert(str[i] == 'x');
442+
}
443+
for (Py_ssize_t i=0; i < 200; i++) {
444+
assert(str[100 + i] == 'y');
445+
}
446+
Py_DECREF(result);
447+
448+
// Check that PyBytesWriter_FinishWithPointer() rejects pointer
449+
// after the buffer end (create a string larger than the allocated size)
450+
writer = PyBytesWriter_Create(3);
451+
if (writer == NULL) {
452+
return NULL;
453+
}
454+
str = PyBytesWriter_GetData(writer);
455+
memcpy(str, "abc", 3);
456+
str += 4; // off-by-one bug on purpose
457+
result = PyBytesWriter_FinishWithPointer(writer, str);
458+
assert(result == NULL);
459+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
460+
PyErr_Clear();
461+
462+
// Check that PyBytesWriter_FinishWithPointer() rejects pointer
463+
// before the buffer start (negative size)
464+
writer = PyBytesWriter_Create(3);
465+
if (writer == NULL) {
466+
return NULL;
467+
}
468+
str = PyBytesWriter_GetData(writer);
469+
str--; // bug on purpose: go before the buffer start
470+
result = PyBytesWriter_FinishWithPointer(writer, str);
471+
assert(result == NULL);
472+
assert(PyErr_ExceptionMatches(PyExc_ValueError));
473+
PyErr_Clear();
474+
475+
Py_RETURN_NONE;
476+
}
477+
478+
376479
static PyMethodDef test_methods[] = {
377480
{"bytes_resize", bytes_resize, METH_VARARGS},
378481
{"bytes_join", bytes_join, METH_VARARGS},
379482
{"byteswriter_abc", byteswriter_abc, METH_NOARGS},
380483
{"byteswriter_resize", byteswriter_resize, METH_NOARGS},
381484
{"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
485+
{"test_byteswriter_ptr", test_byteswriter_ptr, METH_NOARGS},
382486
{NULL},
383487
};
384488

@@ -399,5 +503,12 @@ _PyTestCapi_Init_Bytes(PyObject *m)
399503
}
400504
Py_DECREF(writer_type);
401505

506+
// PyBytesWriter.obj is the second member, small_buffer is the first member
507+
long size = (long)pybyteswriter_small_buffer_size();
508+
if (PyModule_AddIntConstant(m, "PyBytesWriter_small_buffer", size) < 0) {
509+
Py_DECREF(writer_type);
510+
return -1;
511+
}
512+
402513
return 0;
403514
}

‎Objects/bytesobject.c‎

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

3737

3838
#define CHARACTERS _Py_SINGLETON(bytes_characters)
@@ -2982,8 +2982,8 @@ _PyBytes_FromList(PyObject *x)
29822982
if (writer == NULL) {
29832983
return NULL;
29842984
}
2985+
size = _PyBytesWriter_ResizeToAllocated(writer);
29852986
char *str = PyBytesWriter_GetData(writer);
2986-
size = _PyBytesWriter_GetAllocated(writer);
29872987

29882988
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(x); i++) {
29892989
PyObject *item = _PyList_GetItemRef((PyListObject *)x, i);
@@ -3006,7 +3006,9 @@ _PyBytes_FromList(PyObject *x)
30063006
if (str == NULL) {
30073007
goto error;
30083008
}
3009-
size = _PyBytesWriter_GetAllocated(writer);
3009+
3010+
// Set the writer size to its allocated size
3011+
size = _PyBytesWriter_ResizeToAllocated(writer);
30103012
}
30113013
*str++ = (char) value;
30123014
}
@@ -3064,8 +3066,8 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
30643066
if (writer == NULL) {
30653067
return NULL;
30663068
}
3069+
size = _PyBytesWriter_ResizeToAllocated(writer);
30673070
char *str = PyBytesWriter_GetData(writer);
3068-
size = _PyBytesWriter_GetAllocated(writer);
30693071

30703072
/* Run the iterator to exhaustion */
30713073
for (i = 0; ; i++) {
@@ -3099,7 +3101,9 @@ _PyBytes_FromIterator(PyObject *it, PyObject *x)
30993101
if (str == NULL) {
31003102
goto error;
31013103
}
3102-
size = _PyBytesWriter_GetAllocated(writer);
3104+
3105+
// Set the writer size to its allocated size
3106+
size = _PyBytesWriter_ResizeToAllocated(writer);
31033107
}
31043108
*str++ = (char) value;
31053109
}
@@ -3734,6 +3738,19 @@ PyBytesWriter_Discard(PyBytesWriter *writer)
37343738
PyObject*
37353739
PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
37363740
{
3741+
// Check for negative size here to raise ValueError in all cases, rather
3742+
// than having a different exception depending on the code path. For
3743+
// example, _PyBytes_Resize() raises SystemError on negative size.
3744+
if (size < 0) {
3745+
PyErr_Format(PyExc_ValueError, "size must be positive");
3746+
goto error;
3747+
}
3748+
3749+
if (size > writer->size) {
3750+
PyErr_SetString(PyExc_ValueError, "size larger than allocated size");
3751+
goto error;
3752+
}
3753+
37373754
PyObject *result;
37383755
if (size == 0 && !writer->use_bytearray) {
37393756
result = bytes_get_empty();
@@ -3781,12 +3798,6 @@ PyObject*
37813798
PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
37823799
{
37833800
Py_ssize_t size = (char*)buf - byteswriter_data(writer);
3784-
if (size < 0 || size > byteswriter_allocated(writer)) {
3785-
PyBytesWriter_Discard(writer);
3786-
PyErr_SetString(PyExc_ValueError, "invalid end pointer");
3787-
return NULL;
3788-
}
3789-
37903801
return PyBytesWriter_FinishWithSize(writer, size);
37913802
}
37923803

@@ -3805,13 +3816,6 @@ PyBytesWriter_GetSize(PyBytesWriter *writer)
38053816
}
38063817

38073818

3808-
static Py_ssize_t
3809-
_PyBytesWriter_GetAllocated(PyBytesWriter *writer)
3810-
{
3811-
return byteswriter_allocated(writer);
3812-
}
3813-
3814-
38153819
int
38163820
PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
38173821
{
@@ -3918,3 +3922,15 @@ PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
39183922
Py_ssize_t size = buf - byteswriter_data(writer);
39193923
return PyBytesWriter_Resize(writer, size);
39203924
}
3925+
3926+
3927+
// Resize the writer to its allocated size.
3928+
// Return the new size.
3929+
// The function cannot fail.
3930+
static Py_ssize_t
3931+
_PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer)
3932+
{
3933+
Py_ssize_t allocated = byteswriter_allocated(writer);
3934+
writer->size = allocated;
3935+
return allocated;
3936+
}

0 commit comments

Comments
 (0)