summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Rigo <arigo@tunes.org>2022-06-08 09:10:21 +0200
committerArmin Rigo <arigo@tunes.org>2022-06-08 09:10:21 +0200
commit34c9e300eb82d937d231c1bc3b95a5c035d98e0f (patch)
treec59018d275d2d8e6ae009e6283c59262a775a0f2
parent2ff7999df707d8d1191553c892519b3791e15341 (diff)
downloadcffi-34c9e300eb82d937d231c1bc3b95a5c035d98e0f.tar.gz
issue #535
Give a warning when we ask a ffi.buffer() that can be proven to overflow. Might help with the confusion on that issue #535.
-rw-r--r--c/_cffi_backend.c24
-rw-r--r--c/test_c.py27
2 files changed, 50 insertions, 1 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
index 4e055f8..29c7f80 100644
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -2197,7 +2197,7 @@ static PyObject *_frombuf_repr(CDataObject *cd, const char *cd_type_name)
}
}
-static PyObject *cdataowning_repr(CDataObject *cd)
+static Py_ssize_t cdataowning_size_bytes(CDataObject *cd)
{
Py_ssize_t size = _cdata_var_byte_size(cd);
if (size < 0) {
@@ -2208,6 +2208,12 @@ static PyObject *cdataowning_repr(CDataObject *cd)
else
size = cd->c_type->ct_size;
}
+ return size;
+}
+
+static PyObject *cdataowning_repr(CDataObject *cd)
+{
+ Py_ssize_t size = cdataowning_size_bytes(cd);
return PyText_FromFormat("<cdata '%s' owning %zd bytes>",
cd->c_type->ct_name, size);
}
@@ -7016,12 +7022,14 @@ b_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
/* this is the constructor of the type implemented in minibuffer.h */
CDataObject *cd;
Py_ssize_t size = -1;
+ int explicit_size;
static char *keywords[] = {"cdata", "size", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|n:buffer", keywords,
&CData_Type, &cd, &size))
return NULL;
+ explicit_size = size >= 0;
if (size < 0)
size = _cdata_var_byte_size(cd);
@@ -7045,6 +7053,20 @@ b_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
cd->c_type->ct_name);
return NULL;
}
+
+ if (explicit_size && CDataOwn_Check(cd)) {
+ Py_ssize_t size_max = cdataowning_size_bytes(cd);
+ if (size > size_max) {
+ char msg[256];
+ sprintf(msg, "ffi.buffer(cdata, bytes): creating a buffer of %llu "
+ "bytes over a cdata that owns only %llu bytes. This "
+ "will crash if you access the extra memory",
+ (unsigned PY_LONG_LONG)size,
+ (unsigned PY_LONG_LONG)size_max);
+ if (PyErr_WarnEx(PyExc_UserWarning, msg, 1))
+ return NULL;
+ }
+ }
/*WRITE(cd->c_data, size)*/
return minibuffer_new(cd->c_data, size, (PyObject *)cd);
}
diff --git a/c/test_c.py b/c/test_c.py
index 906eb07..9dda28b 100644
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -3498,6 +3498,18 @@ def test_bitfield_as_ppc_gcc():
_test_bitfield_details(flag=SF_GCC_X86_BITFIELDS|SF_GCC_BIG_ENDIAN)
+def buffer_warning(cdata):
+ import warnings
+ buf = buffer(cdata)
+ bytes = len(buf)
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ buffer(cdata, bytes)
+ assert len(w) == 0
+ buffer(cdata, bytes + 1)
+ assert len(w) <= 1
+ return len(w) == 1
+
def test_struct_array_no_length():
BInt = new_primitive_type("int")
BIntP = new_pointer_type(BInt)
@@ -3612,6 +3624,7 @@ def test_struct_array_no_length():
assert p.a[1] == 20
assert p.a[2] == 30
assert p.a[3] == 0
+ assert buffer_warning(p)
#
# struct of struct of varsized array
BStruct2 = new_struct_type("bar")
@@ -3620,6 +3633,20 @@ def test_struct_array_no_length():
for i in range(2): # try to detect heap overwrites
p = newp(new_pointer_type(BStruct2), [100, [200, list(range(50))]])
assert p.tail.y[49] == 49
+ assert buffer_warning(p)
+ assert not buffer_warning(cast(new_pointer_type(BStruct2), p))
+ assert not buffer_warning(cast(BIntP, p))
+
+def test_more_buffer_warning():
+ BChar = new_primitive_type("char")
+ BCharP = new_pointer_type(BChar)
+ BArray = new_array_type(BCharP, 10) # char[10]
+ p = newp(BArray)
+ assert buffer_warning(p)
+ assert not buffer_warning(cast(BCharP, p))
+ p = newp(BCharP)
+ assert buffer_warning(p)
+ assert not buffer_warning(cast(BCharP, p))
def test_struct_array_no_length_explicit_position():