Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Include/cpython/bytearrayobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ typedef struct {
PyObject_VAR_HEAD
/* How many bytes allocated in ob_bytes
In the current implementation this is equivalent to Py_SIZE(ob_bytes_object).
In the current implementation this is equivalent to
PyBytes_GET_SIZE(ob_bytes_object).
The value is always loaded and stored atomically for thread safety.
There are API compatibilty concerns with removing so keeping for now. */
Py_ssize_t ob_alloc;
Expand Down
40 changes: 40 additions & 0 deletions Lib/test/test_capi/test_bytearray.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import sys
import textwrap
import unittest
from test import support
from test.support import import_helper
from test.support.script_helper import assert_python_failure

_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX
Expand Down Expand Up @@ -172,6 +175,43 @@ def test_resize(self):
# CRASHES resize(object(), 0)
# CRASHES resize(NULL, 0)

@unittest.skipUnless(support.built_with_c_assertions(),
'Python built without assertions')
def test_detect_overflow(self):
# Test detection of buffer overflow
size = 123
for operation in (
'repr(ba)',
'ba.resize(5)',
'del ba[5:]',
'ba[5]',
'ba % ()',
):
with self.subTest(operation=operation):
code = textwrap.dedent(f'''
from test.support import SuppressCrashReport
import os
import _testcapi

size = {size}
with SuppressCrashReport():
# Trigger a buffer overflow in a new bytearray
ba = _testcapi.bytearray_overflow(size)
try:
{operation}
except:
# Ignore all exceptions
pass
# If we reached this line, the operation didn't
# detect the overflow. Exit immediatetly without
# calling the bytearray destructor since it can detect
# the overflow.
os._exit(0)
''')
proc = assert_python_failure('-c', code)
self.assertIn(b'Buffer overflow detected in bytearray', proc.err)
self.assertIn(f'at position {size}'.encode(), proc.err)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
When Python is built in debug mode, :class:`bytearray` now detects buffer
overflow. Patch by Victor Stinner.
16 changes: 16 additions & 0 deletions Modules/_testcapi/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -528,13 +528,29 @@ test_byteswriter_ptr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
}


static PyObject *
bytearray_overflow(PyObject *Py_UNUSED(module), PyObject *arg)
{
PyObject *bytearray = PyObject_CallOneArg((PyObject*)&PyByteArray_Type, arg);
if (bytearray == NULL) {
return NULL;
}

char *data = PyByteArray_AS_STRING(bytearray);
Py_ssize_t size = PyByteArray_GET_SIZE(bytearray);
data[size] = '#'; // Buffer overflow!
return bytearray;
}


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},
{"bytearray_overflow", bytearray_overflow, METH_O},
{NULL},
};

Expand Down
Loading
Loading