Skip to content

Commit 7994e4e

Browse files
committed
Rewrite canary byte test in Python
* Add test_get_data_canary() * Update test_get_data()
1 parent c81cbd7 commit 7994e4e

2 files changed

Lines changed: 68 additions & 60 deletions

File tree

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ class BaseWriterTest:
324324
assert SMALL_BUFFER < _testcapi.PyBytesWriter_small_buffer
325325
LARGE_BUFFER = _testcapi.PyBytesWriter_small_buffer + 17 # bytes
326326
NEW_BYTE = b'\xff'
327+
CANARY_BYTE = b'\xdd'
327328

328329
def create_writer(self, alloc=0, string=b''):
329330
raise NotImplementedError
@@ -346,6 +347,7 @@ def test_get_data(self):
346347
# Test PyBytesWriter_GetData()
347348
writer = self.create_writer(6)
348349
NEW_BYTE = self.NEW_BYTE
350+
CANARY_BYTE = self.CANARY_BYTE
349351
self.assertEqual(writer.get_data(), NEW_BYTE * 6)
350352
writer.write(0, b'abc')
351353
self.assertEqual(writer.get_data(), b'abc' + NEW_BYTE * 3)
@@ -359,7 +361,7 @@ def test_get_data(self):
359361
writer.write(0, b's' * small)
360362
self.assertEqual(writer.get_data(), b's' * small)
361363
writer.resize(large)
362-
self.assertEqual(writer.get_data(), b's' * small + NEW_BYTE * (large - small))
364+
self.assertEqual(writer.get_data(), b's' * small + CANARY_BYTE + NEW_BYTE * (large - small - 1))
363365
writer.write(small, b'L' * (large - small))
364366
self.assertEqual(writer.get_data(), b's' * small + b'L' * (large - small))
365367

@@ -515,6 +517,51 @@ def test_format_i(self):
515517
writer.format_i(b'y=%i', 456)
516518
self.assertEqual(writer.finish(), b'x=123, y=456')
517519

520+
@unittest.skipUnless(support.Py_DEBUG, 'need a Python debug build')
521+
def test_canary_byte(self):
522+
small_buffer = _testcapi.PyBytesWriter_small_buffer
523+
large_size = small_buffer * 10
524+
use_bytearray = (self.RESULT_TYPE == bytearray)
525+
526+
# Test small buffer and large buffer
527+
for size in (0, self.SMALL_BUFFER, self.LARGE_BUFFER):
528+
with self.subTest(size=size):
529+
code = textwrap.dedent(f"""
530+
from test.support import SuppressCrashReport
531+
import _testcapi
532+
size = {size}
533+
# Add an extra '#' byte to trigger a buffer overflow
534+
data = b'x' * size + b'#'
535+
use_bytearray = {use_bytearray}
536+
writer = _testcapi.PyBytesWriter(size, use_bytearray)
537+
with SuppressCrashReport():
538+
writer.write(0, data, check=False)
539+
writer.finish()
540+
""")
541+
proc = assert_python_failure('-c', code)
542+
self.assertIn(b'Buffer overflow detected in PyBytesWriter',
543+
proc.err)
544+
self.assertIn(f'at position {size}'.encode(),
545+
proc.err)
546+
547+
@unittest.skipUnless(support.Py_DEBUG, 'need Py_DEBUG')
548+
def test_get_data_canary(self):
549+
# Test PyBytesWriter_GetData()
550+
NEW_BYTE = self.NEW_BYTE
551+
CANARY_BYTE = self.CANARY_BYTE
552+
canary_byte_size = len(CANARY_BYTE)
553+
554+
def get_data_canary():
555+
size = writer.get_size() + canary_byte_size
556+
return writer.get_data(size)
557+
558+
writer = self.create_writer(6)
559+
self.assertEqual(get_data_canary(), NEW_BYTE * 6 + CANARY_BYTE)
560+
writer.write(0, b'abc')
561+
self.assertEqual(get_data_canary(), b'abc' + NEW_BYTE * 3 + CANARY_BYTE)
562+
writer.write(3, b'123')
563+
self.assertEqual(get_data_canary(), b'abc123' + CANARY_BYTE)
564+
518565

519566
class BytesWriterTest(BaseWriterTest, unittest.TestCase):
520567
RESULT_TYPE = bytes
@@ -567,28 +614,6 @@ def test_example_resize(self):
567614
def test_example_highlevel(self):
568615
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
569616

570-
@unittest.skipUnless(support.Py_DEBUG, 'need a Python debug build')
571-
def test_canary_byte(self):
572-
small_buffer = _testcapi.PyBytesWriter_small_buffer
573-
large_size = small_buffer * 10
574-
575-
# Test small buffer and large buffer
576-
for size in (0, 3, large_size):
577-
with self.subTest(size=size):
578-
code = textwrap.dedent(f"""
579-
from test.support import SuppressCrashReport
580-
import _testcapi
581-
size = {size}
582-
data = b'x' * size
583-
with SuppressCrashReport():
584-
_testcapi.byteswriter_test_canary_byte(data)
585-
""")
586-
proc = assert_python_failure('-c', code)
587-
self.assertIn(b'Buffer overflow detected in PyBytesWriter',
588-
proc.err)
589-
self.assertIn(f'at position {size}'.encode(),
590-
proc.err)
591-
592617

593618
class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase):
594619
RESULT_TYPE = bytearray

‎Modules/_testcapi/bytes.c‎

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -135,22 +135,29 @@ writer_check(WriterObject *self)
135135

136136

137137
static PyObject*
138-
writer_write(PyObject *self_raw, PyObject *args)
138+
writer_write(PyObject *self_raw, PyObject *args, PyObject *kwargs)
139139
{
140140
WriterObject *self = (WriterObject *)self_raw;
141141
if (writer_check(self) < 0) {
142142
return NULL;
143143
}
144144

145+
static char *kwlist[] = {"pos", "str", "check", NULL};
145146
Py_ssize_t pos, size;
146147
char *str;
147-
if (!PyArg_ParseTuple(args, "ny#", &pos, &str, &size)) {
148+
int check = 1;
149+
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
150+
"ny#|i", kwlist,
151+
&pos, &str, &size, &check)) {
148152
return NULL;
149153
}
150154

151-
if (pos < 0 || (pos + size) > PyBytesWriter_GetSize(self->writer)) {
152-
PyErr_SetString(PyExc_ValueError, "invalid position or size");
153-
return NULL;
155+
// Use check=0 to trigger a buffer overflow for example
156+
if (check) {
157+
if (pos < 0 || (pos + size) > PyBytesWriter_GetSize(self->writer)) {
158+
PyErr_SetString(PyExc_ValueError, "invalid position or size");
159+
return NULL;
160+
}
154161
}
155162

156163
char *data = PyBytesWriter_GetData(self->writer);
@@ -245,15 +252,19 @@ writer_grow(PyObject *self_raw, PyObject *args)
245252

246253

247254
static PyObject*
248-
writer_get_data(PyObject *self_raw, PyObject *Py_UNUSED(args))
255+
writer_get_data(PyObject *self_raw, PyObject *args)
249256
{
250257
WriterObject *self = (WriterObject *)self_raw;
251258
if (writer_check(self) < 0) {
252259
return NULL;
253260
}
254261

255-
const char *data = PyBytesWriter_GetData(self->writer);
256262
Py_ssize_t size = PyBytesWriter_GetSize(self->writer);
263+
if (!PyArg_ParseTuple(args, "|n", &size)) {
264+
return NULL;
265+
}
266+
267+
const char *data = PyBytesWriter_GetData(self->writer);
257268
return PyBytes_FromStringAndSize(data, size);
258269
}
259270

@@ -305,12 +316,12 @@ writer_finish_with_size(PyObject *self_raw, PyObject *args)
305316

306317

307318
static PyMethodDef writer_methods[] = {
308-
{"write", _PyCFunction_CAST(writer_write), METH_VARARGS},
319+
{"write", _PyCFunction_CAST(writer_write), METH_VARARGS | METH_KEYWORDS},
309320
{"write_bytes", _PyCFunction_CAST(writer_write_bytes), METH_VARARGS},
310321
{"format_i", _PyCFunction_CAST(writer_format_i), METH_VARARGS},
311322
{"resize", _PyCFunction_CAST(writer_resize), METH_VARARGS},
312323
{"grow", _PyCFunction_CAST(writer_grow), METH_VARARGS},
313-
{"get_data", _PyCFunction_CAST(writer_get_data), METH_NOARGS},
324+
{"get_data", _PyCFunction_CAST(writer_get_data), METH_VARARGS},
314325
{"get_size", _PyCFunction_CAST(writer_get_size), METH_NOARGS},
315326
{"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},
316327
{"finish_with_size", _PyCFunction_CAST(writer_finish_with_size), METH_VARARGS},
@@ -502,41 +513,13 @@ test_byteswriter_ptr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
502513
}
503514

504515

505-
// Trigger a buffer overflow on purpose to test the canary byte feature
506-
// which detects buffer overflow
507-
static PyObject *
508-
byteswriter_test_canary_byte(PyObject *Py_UNUSED(module), PyObject *args)
509-
{
510-
const char *str;
511-
Py_ssize_t len;
512-
if (!PyArg_ParseTuple(args, "s#", &str, &len)) {
513-
return NULL;
514-
}
515-
516-
PyBytesWriter *writer = PyBytesWriter_Create(len);
517-
if (writer == NULL) {
518-
return NULL;
519-
}
520-
521-
char *data = PyBytesWriter_GetData(writer);
522-
if (len) {
523-
memcpy(data, str, len);
524-
}
525-
data[len] = '#'; // Overflow!
526-
527-
// In debug mode, PyBytesWriter_Finish() checks for buffer overflow
528-
return PyBytesWriter_Finish(writer);
529-
}
530-
531-
532516
static PyMethodDef test_methods[] = {
533517
{"bytes_resize", bytes_resize, METH_VARARGS},
534518
{"bytes_join", bytes_join, METH_VARARGS},
535519
{"byteswriter_abc", byteswriter_abc, METH_NOARGS},
536520
{"byteswriter_resize", byteswriter_resize, METH_NOARGS},
537521
{"byteswriter_highlevel", byteswriter_highlevel, METH_NOARGS},
538522
{"test_byteswriter_ptr", test_byteswriter_ptr, METH_NOARGS},
539-
{"byteswriter_test_canary_byte", byteswriter_test_canary_byte, METH_VARARGS},
540523
{NULL},
541524
};
542525

0 commit comments

Comments
 (0)