Harnessing the Kernel Module
We will harness the kernel module two ways:
- With harness code compiled into the kernel module
- With harness code compiled into a user-space application that drives the kernel module
This demonstrates the flexibility of the fuzzer -- however your real target software should be harnessed, should be chosen.
Kernel Module Harness
Because the build process for the buildroot is quite long (5-10 mins on a fast machine),
we will avoid compiling it twice. Modify the device_write
function:
static ssize_t device_write(struct file *file, const char __user *buffer,
size_t length, loff_t *offset) {
int i;
pr_info("device_write(%p,%p,%ld)", file, buffer, length);
for (i = 0; i < length && i < BUF_LEN; i++) {
get_user(message[i], buffer + i);
}
size_t size = BUF_LEN;
size_t *size_ptr = &size;
HARNESS_START(message, size_ptr);
check(message);
HARNESS_STOP();
return i;
}
This adds our harness such that the first time the device_write
function is called,
via a user-space application writing or using the IOCTL system call, the fuzzer will
take over and start the fuzzing loop.
Userspace Driver Code
First, copy tsffs.h
from the harness
directory in the repository into
src/tsffs.h
.
We'll also create src/tutorial-mod-driver.c
, a user-space application which we will
use to drive the kernel module code via IOCTL.
#include <fcntl.h>
#include <linux/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "tsffs.h"
#define MAJOR_NUM 100
#define IOCTL_SET_MSG _IOW(MAJOR_NUM, 0, char *)
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)
#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)
#define DEVICE_FILE_NAME "char_dev"
#define DEVICE_PATH "/dev/char_dev"
int ioctl_set_msg(int file_desc, char *message) {
int ret_val;
ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);
if (ret_val < 0) {
printf("ioctl_set_msg failed:%d\n", ret_val);
}
return ret_val;
}
int ioctl_get_msg(int file_desc) {
int ret_val;
char message[100] = {0};
ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);
if (ret_val < 0) {
printf("ioctl_get_msg failed:%d\n", ret_val);
}
printf("get_msg message:%s", message);
return ret_val;
}
int ioctl_get_nth_byte(int file_desc) {
int i, c;
printf("get_nth_byte message:");
i = 0;
do {
c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);
if (c < 0) {
printf("\nioctl_get_nth_byte failed at the %d'th byte:\n", i);
return c;
}
putchar(c);
} while (c != 0);
return 0;
}
int main(void) {
int file_desc, ret_val;
char *msg = "AAAAAAAA\n";
file_desc = open(DEVICE_PATH, O_RDWR);
if (file_desc < 0) {
printf("Can't open device file: %s, error:%d\n", DEVICE_PATH, file_desc);
exit(EXIT_FAILURE);
}
ret_val = ioctl_set_msg(file_desc, msg);
if (ret_val) goto error;
close(file_desc);
return 0;
error:
close(file_desc);
exit(EXIT_FAILURE);
}
This application opens the character device of our module, sets the message, and closes the device.
Harnessing the Userspace Driver Code
Once again, because the build process is quite long, we'll add the user-space harness
now. Modify the main
function:
int main(void) {
int file_desc, ret_val;
char msg[80] = {0};
file_desc = open(DEVICE_PATH, O_RDWR);
if (file_desc < 0) {
printf("Can't open device file: %s, error:%d\n", DEVICE_PATH, file_desc);
exit(EXIT_FAILURE);
}
size_t msg_size = 80;
size_t *msg_size_ptr = &msg_size;
__arch_harness_start(MAGIC_ALT_0, msg, msg_size_ptr);
ret_val = ioctl_set_msg(file_desc, msg);
__arch_harness_stop(MAGIC_ALT_1);
if (ret_val) goto error;
close(file_desc);
return 0;
error:
close(file_desc);
exit(EXIT_FAILURE);
}
Notice that instead of using HARNESS_START
and HARNESS_STOP
here, we use
__arch_harness_start
and stop
so that we can send a signal with a different n
value. This allows us to keep the compiled-in harnessing in the test kernel module,
while leaving it inactive.