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.
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
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.
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