Eric Blake
2022-May-26 01:27 UTC
[Libguestfs] [libnbd PATCH] python: Accept buffers in nbd.Buffer.from_bytearray()
Prior to this patch, the following fails, but at least seems to give a sensible error: $ nbdsh -c 'nbd.Buffer.from_bytearray(b"1"*8)' Traceback (most recent call last): File "/usr/lib64/python3.10/runpy.py", line 196, in _run_module_as_main return _run_code(code, main_globals, None, File "/usr/lib64/python3.10/runpy.py", line 86, in _run_code exec(code, run_globals) File "/usr/lib64/python3.10/site-packages/nbd.py", line 2726, in <module> nbdsh.shell() File "/usr/lib64/python3.10/site-packages/nbdsh.py", line 139, in shell exec(c, d, d) File "<string>", line 1, in <module> File "/usr/lib64/python3.10/site-packages/nbd.py", line 132, in from_bytearray o = libnbdmod.aio_buffer_from_bytearray(ba) RuntimeError: parameter is not a bytearray while this version 1 byte longer flat out segfaults: $ nbdsh -c 'nbd.Buffer.from_bytearray(b"1"*9)' and this one is subtly different: $ nbdsh -c 'nbd.Buffer.from_bytearray(h)' Traceback (most recent call last): ... File "/usr/lib64/python3.10/site-packages/nbd.py", line 132, in from_bytearray o = libnbdmod.aio_buffer_from_bytearray(ba) MemoryError That's because PyByteArray_AsString() blindly assumes that its argument is a PyByteArray, and goes haywire when it is not, unless we got lucky that the incorrectly-typed object behaves similarly enough (which, for byte literals, is size-dependent). But Python already has a handy way to convert any object that supports the buffer interface into a bytearray. Using it, we can now support many more parameters; passing in b"1"*9 now correctly creates a 9-byte buffer rather than failing. And the error message for a non-buffer also improves: $ ./run nbdsh -c 'nbd.Buffer.from_bytearray(h)' Traceback (most recent call last): ... File "/home/eblake/libnbd/python/nbd.py", line 132, in from_bytearray o = libnbdmod.aio_buffer_from_bytearray(ba) TypeError: cannot convert 'NBD' object to bytearray (A reliable TypeError is always better than an unexpected MemoryError or segfault). --- python/handle.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/python/handle.c b/python/handle.c index 9c08dc1..9fe3f8e 100644 --- a/python/handle.c +++ b/python/handle.c @@ -1,5 +1,5 @@ /* NBD client library in userspace - * Copyright (C) 2013-2020 Red Hat Inc. + * Copyright (C) 2013-2022 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -159,6 +159,7 @@ PyObject * nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) { PyObject *obj; + PyObject *arr = NULL; Py_ssize_t len; void *data; struct py_aio_buffer *buf; @@ -169,9 +170,17 @@ nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) &obj)) return NULL; + if (! PyByteArray_Check (obj)) { + arr = PyByteArray_FromObject (obj); + if (arr == NULL) + return NULL; + obj = arr; + } data = PyByteArray_AsString (obj); if (!data) { - PyErr_SetString (PyExc_RuntimeError, "parameter is not a bytearray"); + PyErr_SetString (PyExc_RuntimeError, + "parameter is not a bytearray or buffer"); + Py_XDECREF (arr); return NULL; } len = PyByteArray_Size (obj); @@ -179,6 +188,7 @@ nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) buf = malloc (sizeof *buf); if (buf == NULL) { PyErr_NoMemory (); + Py_XDECREF (arr); return NULL; } @@ -187,9 +197,11 @@ nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) if (buf->data == NULL) { PyErr_NoMemory (); free (buf); + Py_XDECREF (arr); return NULL; } memcpy (buf->data, data, len); + Py_XDECREF (arr); ret = PyCapsule_New (buf, aio_buffer_name, free_aio_buffer); if (ret == NULL) { -- 2.36.1
Richard W.M. Jones
2022-May-26 08:16 UTC
[Libguestfs] [libnbd PATCH] python: Accept buffers in nbd.Buffer.from_bytearray()
On Wed, May 25, 2022 at 08:27:15PM -0500, Eric Blake wrote:> Prior to this patch, the following fails, but at least seems to give a > sensible error: > > $ nbdsh -c 'nbd.Buffer.from_bytearray(b"1"*8)' > Traceback (most recent call last): > File "/usr/lib64/python3.10/runpy.py", line 196, in _run_module_as_main > return _run_code(code, main_globals, None, > File "/usr/lib64/python3.10/runpy.py", line 86, in _run_code > exec(code, run_globals) > File "/usr/lib64/python3.10/site-packages/nbd.py", line 2726, in <module> > nbdsh.shell() > File "/usr/lib64/python3.10/site-packages/nbdsh.py", line 139, in shell > exec(c, d, d) > File "<string>", line 1, in <module> > File "/usr/lib64/python3.10/site-packages/nbd.py", line 132, in from_bytearray > o = libnbdmod.aio_buffer_from_bytearray(ba) > RuntimeError: parameter is not a bytearray > > while this version 1 byte longer flat out segfaults: > > $ nbdsh -c 'nbd.Buffer.from_bytearray(b"1"*9)' > > and this one is subtly different: > > $ nbdsh -c 'nbd.Buffer.from_bytearray(h)' > Traceback (most recent call last): > ... > File "/usr/lib64/python3.10/site-packages/nbd.py", line 132, in from_bytearray > o = libnbdmod.aio_buffer_from_bytearray(ba) > MemoryError > > That's because PyByteArray_AsString() blindly assumes that its > argument is a PyByteArray, and goes haywire when it is not, unless we > got lucky that the incorrectly-typed object behaves similarly enough > (which, for byte literals, is size-dependent). > > But Python already has a handy way to convert any object that supports > the buffer interface into a bytearray. Using it, we can now support > many more parameters; passing in b"1"*9 now correctly creates a 9-byte > buffer rather than failing. And the error message for a non-buffer > also improves: > > $ ./run nbdsh -c 'nbd.Buffer.from_bytearray(h)' > Traceback (most recent call last): > ... > File "/home/eblake/libnbd/python/nbd.py", line 132, in from_bytearray > o = libnbdmod.aio_buffer_from_bytearray(ba) > TypeError: cannot convert 'NBD' object to bytearray > > (A reliable TypeError is always better than an unexpected MemoryError > or segfault). > --- > python/handle.c | 16 ++++++++++++++-- > 1 file changed, 14 insertions(+), 2 deletions(-) > > diff --git a/python/handle.c b/python/handle.c > index 9c08dc1..9fe3f8e 100644 > --- a/python/handle.c > +++ b/python/handle.c > @@ -1,5 +1,5 @@ > /* NBD client library in userspace > - * Copyright (C) 2013-2020 Red Hat Inc. > + * Copyright (C) 2013-2022 Red Hat Inc. > * > * This library is free software; you can redistribute it and/or > * modify it under the terms of the GNU Lesser General Public > @@ -159,6 +159,7 @@ PyObject * > nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) > { > PyObject *obj; > + PyObject *arr = NULL; > Py_ssize_t len; > void *data; > struct py_aio_buffer *buf; > @@ -169,9 +170,17 @@ nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) > &obj)) > return NULL; > > + if (! PyByteArray_Check (obj)) { > + arr = PyByteArray_FromObject (obj); > + if (arr == NULL) > + return NULL; > + obj = arr; > + } > data = PyByteArray_AsString (obj); > if (!data) { > - PyErr_SetString (PyExc_RuntimeError, "parameter is not a bytearray"); > + PyErr_SetString (PyExc_RuntimeError, > + "parameter is not a bytearray or buffer"); > + Py_XDECREF (arr); > return NULL; > } > len = PyByteArray_Size (obj); > @@ -179,6 +188,7 @@ nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) > buf = malloc (sizeof *buf); > if (buf == NULL) { > PyErr_NoMemory (); > + Py_XDECREF (arr); > return NULL; > } > > @@ -187,9 +197,11 @@ nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args) > if (buf->data == NULL) { > PyErr_NoMemory (); > free (buf); > + Py_XDECREF (arr); > return NULL; > } > memcpy (buf->data, data, len); > + Py_XDECREF (arr); > > ret = PyCapsule_New (buf, aio_buffer_name, free_aio_buffer); > if (ret == NULL) {Reviewed-by: Richard W.M. Jones <rjones at redhat.com> Thanks! Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into KVM guests. http://libguestfs.org/virt-v2v