Re: [PATCH] libsemanage: treat module data as bytes in the Python bindings
James Carter <[email protected]>
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAP+JOzR9S4Gpn-Q1Q3KVDT5o9qPMv1ayjSgBLBJYW2bQUL3b0Q@mail.gmail.com> |
On Thu, Aug 13, 2026 at 2:41 PM Stephen Smalley <[email protected]> wrote: > > semanage_module_install() / semanage_module_install_info() take a > (char *data, size_t data_len) pair, and semanage_module_extract() > returns (void **mapped_data, size_t *data_len). In both directions > the payload is an opaque module blob (.pp / bzip2 / CIL / HLL), not a > NUL-terminated C string. > > On Python 3 the current SWIG bindings get this wrong on both sides: > > - install()/install_info(): the SWIG default for a bare "char *" > only accepts str objects and UTF-8-encodes them, so a .pp blob > cannot be passed at all, and even for CIL text the separately > supplied data_len is wrong for any non-ASCII content since > Python len(str) counts code points, not UTF-8 bytes. > > - extract(): %cstring_output_allocate_size turns the mapped buffer > into a str via UTF-8 decoding, which for a binary module either > raises UnicodeDecodeError or (with surrogateescape) yields a str > that cannot be fed straight back into install(). > > Add a (char *module_data, size_t data_len) input typemap that > accepts any object exposing the buffer protocol (bytes, bytearray, > mmap, ...) as a single argument and derives the length from it, and > apply it to the (char *data, size_t data_len) pair of > semanage_module_install_info() as well. > > Replace the extract() output typemap with an explicit multi-argument > typemap that returns a bytes object built from the mapped buffer and > then unmaps it, so extract() -> install() round-trips. Drop the > now-unused <cstring.i> include. > > The Python signatures become > semanage_module_install(sh, data, name, ext_lang) > semanage_module_install_info(sh, modinfo, data) > There are no in-tree Python callers. > > Fixes: https://github.com/SELinuxProject/selinux/issues/502 > Signed-off-by: Stephen Smalley <[email protected]> Acked-by: James Carter <[email protected]> > --- > libsemanage/src/semanageswig_python.i | 43 +++++++++++++++++++++++++-- > 1 file changed, 40 insertions(+), 3 deletions(-) > > diff --git a/libsemanage/src/semanageswig_python.i b/libsemanage/src/semanageswig_python.i > index 0ca09763..1729e965 100644 > --- a/libsemanage/src/semanageswig_python.i > +++ b/libsemanage/src/semanageswig_python.i > @@ -102,9 +102,46 @@ > %apply int *OUTPUT { unsigned int * }; > %apply int *OUTPUT { uint16_t * }; > > -%include <cstring.i> > -/* This is needed to properly mmap binary data in SWIG */ > -%cstring_output_allocate_size(void **mapped_data, size_t *data_len, munmap(*$1, *$2)); > +/* > + * semanage_module_extract(): the (void **mapped_data, size_t *data_len) pair > + * is an opaque module blob (.pp / bzip2 / CIL / HLL). Return it to Python > + * as a bytes object; %cstring_output_allocate_size would UTF-8-decode it > + * into a str, which corrupts binary modules. > + */ > +%typemap(in, numinputs=0) (void **mapped_data, size_t *data_len) > + (void *temp_data = NULL, size_t temp_len = 0) { > + $1 = &temp_data; > + $2 = &temp_len; > +} > +%typemap(argout) (void **mapped_data, size_t *data_len) { > + if (*$1) { > + $result = SWIG_AppendOutput( > + $result, > + PyBytes_FromStringAndSize((const char *)*$1, *$2)); > + munmap(*$1, *$2); > + } else { > + $result = SWIG_AppendOutput($result, SWIG_Py_Void()); > + } > +} > + > +/* > + * semanage_module_install() and semanage_module_install_info() take a > + * (char *data, size_t data_len) pair that is an opaque module blob > + * (.pp / bzip2 / CIL / HLL), not a NUL-terminated C string. Accept any > + * Python object exposing the buffer protocol (bytes, bytearray, mmap, ...) > + * as a single argument and derive the length from it. > + */ > +%typemap(in) (char *module_data, size_t data_len) (Py_buffer view) { > + view.obj = NULL; > + if (PyObject_GetBuffer($input, &view, PyBUF_SIMPLE) < 0) > + SWIG_fail; > + $1 = (char *)view.buf; > + $2 = (size_t)view.len; > +} > +%typemap(freearg) (char *module_data, size_t data_len) { > + PyBuffer_Release(&view$argnum); > +} > +%apply (char *module_data, size_t data_len) { (char *data, size_t data_len) }; > > %typemap(in, numinputs=0) char **(char *temp=NULL) { > $1 = &temp; > -- > 2.55.0 >