stack smashing error when calling c function in my python module

Question:

I’m making my first python module, and I am encountering a problem.

Goal: use the process_vm_readv and process_vm_writev functions through a python interface.

At first I made a shared library with these functions in it and that worked fine. But I want to publish it on PyPI, and from what I’ve found this is not the way to do it, so I wrote some new c code using the Python.h api:

#define _GNU_SOURCE
#include <sys/uio.h>
#include <stdint.h>
#include <Python.h>

//int process_read(pid_t pid, uint64_t address, uint8_t *buffer, size_t size) {
static PyObject *process_read(PyObject *self, PyObject *args) {
    struct iovec remote[1];
    struct iovec local[1];

    int32_t pid;
    uint64_t address;
    size_t size;
    uint8_t *buffer;

    if(!PyArg_ParseTuple(args, "iky*k", &pid, &address, &buffer, &size)){
        return NULL;
    }

    remote[0].iov_base = (void *)address;
    remote[0].iov_len = size;

    local[0].iov_base = buffer;
    local[0].iov_len = size;
    
    return PyLong_FromLong(process_vm_readv(pid, local, 1, remote, 1, 0));
}

//int process_write(pid_t pid, uint64_t address, uint8_t *data, size_t size) {
static PyObject *process_write(PyObject *self, PyObject *args) {
    struct iovec remote[1];
    struct iovec local[1];

    int32_t pid;
    uint64_t address;
    size_t size;
    uint8_t *buffer;

    if(!PyArg_ParseTuple(args, "iky*k", &pid, &address, &buffer, &size)){
        return NULL;
    }

    local[0].iov_base = buffer;
    local[0].iov_len = size;

    remote[0].iov_base = (void *) address;
    remote[0].iov_len = size;


    return PyLong_FromLong(process_vm_writev(pid, local, 1, remote, 1, 0));
}

static PyMethodDef PyuioMethods[] = {
    {"_process_read", process_read, METH_VARARGS, "Python interface for the process_vm_readv function"},
    {"_process_write", process_write, METH_VARARGS, "Python interface for the process_vm_writev function"},
    {NULL,NULL,0,NULL}
};

static struct PyModuleDef pyuiomodule = {
    PyModuleDef_HEAD_INIT,
    "pyuiolib",
    "Python Linux Userspace IO interface library",
    -1,
    PyuioMethods
};

PyMODINIT_FUNC PyInit_pyuiolib(void) {
    return PyModule_Create(&pyuiomodule);
}

And this python module to make using it easier:

from pyuiolib import _process_read, _process_write
import ctypes
import errno
from struct import pack, unpack

class asap_datatypes:
    uint8 = 0
    int8 = 1
    uint16 = 2
    int16 = 3
    uint32 = 4
    int32 = 5
    uint64 = 6
    int64 = 7
    single = 8
    double = 9
    dataSizes = [1,1,2,2,4,4,8,8,4,8]

class asap_element:

    address = 0
    size_element = 0
    size_t =  0
    dataType = 0

    def __init__(self, address:int, dataType: int, arraySize:int = 1):
        self.address = address
        self.dataType = dataType
        self.size_element = asap_datatypes.dataSizes[dataType]
        self.size_t = asap_datatypes.dataSizes[dataType]*arraySize

def chunks(lst, n):
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

def convert_to_bytes(data, dataSize, dataType):
    array = []
    if not isinstance(data, list):
        data = [data]
    if dataType == asap_datatypes.int8:
        for unit in data:
            array += int.to_bytes(unit&0xff, dataSize, 'little')
    elif dataType == asap_datatypes.int16:
        for unit in data:
            array += int.to_bytes(unit&0xffff, dataSize, 'little')
    elif dataType == asap_datatypes.int32:
        for unit in data:
            array += int.to_bytes(unit&0xffffffff, dataSize, 'little')
    elif dataType == asap_datatypes.int64:
        for unit in data:
            array += int.to_bytes(unit&0xffffffffffffffff, dataSize, 'little')
    elif dataType == asap_datatypes.single:
        for unit in data:
            array += pack("f", unit)
    elif dataType == asap_datatypes.double:
        for unit in data:
            array += pack("d", unit)
    else:
        for unit in data:
            array += int.to_bytes(unit, dataSize, 'little')
    return array

def convert_to_value(data, dataSize, dataType):
    dataBlocks = chunks(data, dataSize)
    array = []
    if dataType == asap_datatypes.int8 or dataType == asap_datatypes.int16 or dataType == asap_datatypes.int32 or dataType == asap_datatypes.int64:
        for unit in dataBlocks:
            array.append(int.from_bytes(bytearray(unit), 'little', signed=True))
    elif dataType == asap_datatypes.single:
        for unit in dataBlocks:
            array.append(unpack("f", bytearray(unit)))
    elif dataType == asap_datatypes.double:
        for unit in dataBlocks:
            array.append(unpack("d", bytearray(unit)))
    else:
        for unit in dataBlocks:
            array.append(int.from_bytes(bytearray(unit), 'little', signed=False))
    if len(array) == 1:
        array = array[0] 
    return array


def process_write(pid: int, asap_ele: asap_element, data):
    array = convert_to_bytes(data, asap_ele.size_element, asap_ele.dataType)
    array = (ctypes.c_uint8 * asap_ele.size_t)(*array)
    res = _process_write(pid, asap_ele.address, array, asap_ele.size_t)
    if res < 0:
        if res == -errno.EFAULT:
            raise Exception("The address or the buffer are not in an accessible memory location.")
        elif res == -errno.EINVAL:
            raise Exception("The required buffer size is too large.")
        elif res == -errno.ENOMEM:
            raise Exception("Could not allocate memory for the iovec structs.")
        elif res == -errno.EPERM:
            raise Exception("Insufficient permissions to access process memory space.")
        elif res == -errno.ESRCH:
            raise Exception(f"No process exists with the pid {pid}.")
        else:
            raise Exception("An exception occured of an unknown type, memory write likely failed.")

def process_read(pid: int, asap_ele: asap_element):
    array = [0]*asap_ele.size_t
    array = (ctypes.c_uint8 * asap_ele.size_t)(*array)
    res = _process_read(pid, asap_ele.address, array, asap_ele.size_t)
    if res < 0:
        if res == -errno.EFAULT:
            raise Exception("The address or the buffer are not in an accessible memory location.")
        elif res == -errno.EINVAL:
            raise Exception("The required buffer size is too large.")
        elif res == -errno.ENOMEM:
            raise Exception("Could not allocate memory for the iovec structs.")
        elif res == -errno.EPERM:
            raise Exception("Insufficient permissions to access process memory space.")
        elif res == -errno.ESRCH:
            raise Exception(f"No process exists with the pid {pid}.")
        else:
            raise Exception("An exception occured of an unknown type, memory read likely failed.")
    return convert_to_value(array, asap_ele.size_element, asap_ele.dataType)

But now when I try to call the functions I get:

*** stack smashing detected ***: terminated
Aborted

I’m used to segmentation faults but I haven’t seen this before, I believe it is a similar error? Trying to access an array at an index that doesn’t exist?

So I assume it has something to do with the buffer that I pass to the C function. I just copied the way I did it when I used the shared library but I don’t know how else to do it.

edit: I have narrowed it down to the buffer, all the other inputs are fine, but I don’t really know of a way to check if the buffer has come through alright as it is just a pointer to an array.

Asked By: owndampu

||

Answers:

I figured a way out to fix it, it isn’t very pretty in my opinion but it works:

res = _process_read(pid, asap_ele.address, ctypes.addressof(array), asap_ele.size_t)

I now pass my arguments like this.

and receive them like this:

uint64_t bufferAddress;
uint8_t *buffer;

if(!PyArg_ParseTuple(args, "ikkk", &pid, &address, &bufferAddress, &size)){
    return NULL;
}
buffer = (uint8_t *) bufferAddress;

and that works as desired

Answered By: owndampu
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.