Skip to content

Commit 169f4c6

Browse files
committed
gh-156939: Detect PyBytesWriter buffer overflow earlier
Check the canary byte in all PyBytesWriter methods, not only in PyBytesWriter_Finish(). Add a discard() method to the _testcapi wrapper.
1 parent 2cd6d4b commit 169f4c6

3 files changed

Lines changed: 83 additions & 18 deletions

File tree

Lib/test/test_capi/test_bytes.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -591,24 +591,42 @@ def test_canary_byte(self):
591591

592592
# Test small buffer and large buffer
593593
for size in (0, self.SMALL_BUFFER, self.LARGE_BUFFER):
594-
with self.subTest(size=size):
595-
code = textwrap.dedent(f"""
596-
from test.support import SuppressCrashReport
597-
import _testcapi
598-
size = {size}
599-
# Add an extra '#' byte to trigger a buffer overflow
600-
data = b'x' * size + b'#'
601-
use_bytearray = {use_bytearray}
602-
writer = _testcapi.PyBytesWriter(size, use_bytearray)
603-
with SuppressCrashReport():
604-
writer.write(0, data, check=False)
605-
writer.finish()
606-
""")
607-
proc = assert_python_failure('-c', code)
608-
self.assertIn(b'Buffer overflow detected in PyBytesWriter',
609-
proc.err)
610-
self.assertIn(f'at position {size}'.encode(),
611-
proc.err)
594+
for operation in (
595+
'writer.get_data()',
596+
'writer.get_size()',
597+
f'writer.resize({size} * 2)',
598+
f'writer.grow({size})',
599+
'writer.discard()',
600+
'writer.finish()',
601+
):
602+
with self.subTest(size=size, operation=operation):
603+
code = textwrap.dedent(f"""
604+
from test.support import SuppressCrashReport
605+
import os
606+
import _testcapi
607+
size = {size}
608+
# Add an extra '#' byte to trigger a buffer overflow
609+
data = b'x' * size + b'#'
610+
use_bytearray = {use_bytearray}
611+
writer = _testcapi.PyBytesWriter(size, use_bytearray)
612+
with SuppressCrashReport():
613+
writer.write(0, data, check=False)
614+
try:
615+
{operation}
616+
except:
617+
# Ignore all exceptions
618+
pass
619+
# If we reached this line, the operation didn't
620+
# detect the overflow. Exit immediatetly without
621+
# calling the writer destructor since it can detect
622+
# the overflow.
623+
os._exit(0)
624+
""")
625+
proc = assert_python_failure('-c', code)
626+
self.assertIn(b'Buffer overflow detected in PyBytesWriter',
627+
proc.err)
628+
self.assertIn(f'at position {size}'.encode(),
629+
proc.err)
612630

613631
@unittest.skipUnless(support.Py_DEBUG, 'need debug build')
614632
def test_get_data_canary(self):

Modules/_testcapi/bytes.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,20 @@ writer_finish_with_size(PyObject *self_raw, PyObject *args)
315315
}
316316

317317

318+
static PyObject*
319+
writer_discard(PyObject *self_raw, PyObject *Py_UNUSED(args))
320+
{
321+
WriterObject *self = (WriterObject *)self_raw;
322+
if (writer_check(self) < 0) {
323+
return NULL;
324+
}
325+
326+
PyBytesWriter_Discard(self->writer);
327+
self->writer = NULL;
328+
Py_RETURN_NONE;
329+
}
330+
331+
318332
static PyMethodDef writer_methods[] = {
319333
{"write", _PyCFunction_CAST(writer_write), METH_VARARGS | METH_KEYWORDS},
320334
{"write_bytes", _PyCFunction_CAST(writer_write_bytes), METH_VARARGS},
@@ -325,6 +339,7 @@ static PyMethodDef writer_methods[] = {
325339
{"get_size", _PyCFunction_CAST(writer_get_size), METH_NOARGS},
326340
{"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},
327341
{"finish_with_size", _PyCFunction_CAST(writer_finish_with_size), METH_VARARGS},
342+
{"discard", _PyCFunction_CAST(writer_discard), METH_VARARGS},
328343
{NULL, NULL} /* sentinel */
329344
};
330345

Objects/bytesobject.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3803,6 +3803,10 @@ PyBytesWriter_Discard(PyBytesWriter *writer)
38033803
return;
38043804
}
38053805

3806+
#ifdef Py_DEBUG
3807+
byteswriter_check_canary_byte(writer);
3808+
#endif
3809+
38063810
Py_XDECREF(writer->obj);
38073811
_Py_FREELIST_FREE(bytes_writers, writer, PyMem_Free);
38083812
}
@@ -3875,6 +3879,14 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
38753879
// The function returns single byte singleton if size equals 1
38763880
result = PyBytes_FromStringAndSize(writer->small_buffer, size);
38773881
}
3882+
3883+
#ifdef Py_DEBUG
3884+
// Reset the writer, so byteswriter_check_canary_byte() doesn't fail
3885+
// in PyBytesWriter_Discard().
3886+
writer->size = 0;
3887+
byteswriter_write_canary_byte(writer);
3888+
#endif
3889+
38783890
PyBytesWriter_Discard(writer);
38793891
return result;
38803892

@@ -3901,20 +3913,32 @@ PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
39013913
void*
39023914
PyBytesWriter_GetData(PyBytesWriter *writer)
39033915
{
3916+
#ifdef Py_DEBUG
3917+
byteswriter_check_canary_byte(writer);
3918+
#endif
3919+
39043920
return byteswriter_data(writer);
39053921
}
39063922

39073923

39083924
Py_ssize_t
39093925
PyBytesWriter_GetSize(PyBytesWriter *writer)
39103926
{
3927+
#ifdef Py_DEBUG
3928+
byteswriter_check_canary_byte(writer);
3929+
#endif
3930+
39113931
return _PyBytesWriter_GetSize(writer);
39123932
}
39133933

39143934

39153935
int
39163936
PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t new_size)
39173937
{
3938+
#ifdef Py_DEBUG
3939+
byteswriter_check_canary_byte(writer);
3940+
#endif
3941+
39183942
if (new_size < 0) {
39193943
PyErr_SetString(PyExc_ValueError, "size must be >= 0");
39203944
return -1;
@@ -3950,6 +3974,10 @@ _PyBytesWriter_ResizeAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size,
39503974
int
39513975
PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
39523976
{
3977+
#ifdef Py_DEBUG
3978+
byteswriter_check_canary_byte(writer);
3979+
#endif
3980+
39533981
if (grow == 0) {
39543982
// Nothing to do
39553983
return 0;
@@ -4042,6 +4070,10 @@ PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
40424070
static Py_ssize_t
40434071
_PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer)
40444072
{
4073+
#ifdef Py_DEBUG
4074+
byteswriter_check_canary_byte(writer);
4075+
#endif
4076+
40454077
Py_ssize_t allocated = byteswriter_allocated(writer);
40464078
writer->size = allocated;
40474079
#ifdef Py_DEBUG

0 commit comments

Comments
 (0)