#include <linux/ioctl.h>
#include <asm/ioctl.h>
#include <asm/delay.h>
#include <linux/sched/task_stack.h>
typedef struct {
int pid;
struct pt_regs regs;
} SavedRegisters;
typedef struct {
uintptr_t* buffer;
uintptr_t length;
uintptr_t capacity;
} NumberBuffer;
static void push_to_buffer(NumberBuffer* buffer, uintptr_t address) {
if(buffer -> buffer == NULL) {
buffer -> capacity = 100;
buffer -> buffer = kmalloc(buffer -> capacity * sizeof(uintptr_t), GFP_KERNEL);
}
if(buffer -> length == buffer -> capacity) {
buffer -> capacity *= 2;
uintptr_t new_buf_size = buffer -> capacity * sizeof(uintptr_t);
uintptr_t* old_buffer = buffer -> buffer;
uintptr_t* new_buffer = kmalloc(new_buf_size, GFP_KERNEL);
memcpy(new_buffer, old_buffer, (buffer -> length) * sizeof(uintptr_t));
buffer -> buffer = new_buffer;
kfree(old_buffer);
}
(buffer -> length)++;
(buffer -> buffer)[buffer -> length - 1] = address;
}
static void swap_remove_index(NumberBuffer* buffer, uintptr_t index) {
if(buffer -> length > 1) {
(buffer -> buffer)[index] = (buffer -> buffer)[buffer -> length - 1];
}
(buffer -> length)--;
}
static NumberBuffer modified_addr_list;
DEFINE_MUTEX(modified_addr_list_lock);
static NumberBuffer finish_sig_buf;
DEFINE_MUTEX(finish_sig_buf_lock);
static NumberBuffer saved_regs_buf;
DEFINE_MUTEX(saved_regs_buf_lock);
typedef struct {
int pid;
uint64_t instruction_pointer;
} InstructionPointerRequest;
#define RS_MAGIC 123
#define WAIT_FOR_FINISH _IOW(RS_MAGIC, 0, int)
#define TOGGLE_EXEC_WRITE _IOW(RS_MAGIC, 1, int)
#define GET_INST_PTR _IOWR(RS_MAGIC, 2, InstructionPointerRequest)
#define RESTORE_REGS _IOW(RS_MAGIC, 3, int)
static long raminspect_ioctl(struct file *fptr, unsigned int cmd, unsigned long arg) {
switch(cmd) {
case TOGGLE_EXEC_WRITE: {
int pid = (int)arg;
struct task_struct* task = pid_task(find_vpid(pid), PIDTYPE_PID);
if(task == NULL) {
pr_alert("Error: The target process was not running!\n");
return -EINVAL;
}
struct vm_area_struct* current_vma;
struct mm_struct* mm = task -> mm;
VMA_ITERATOR(vmi, mm, 0);
for_each_vma(vmi, current_vma) {
unsigned long vma_start = current_vma->vm_start;
vm_flags_t flags = current_vma -> vm_flags;
if((flags & VM_EXEC) != 0) {
mutex_lock(&modified_addr_list_lock);
unsigned long i;
unsigned long addr_index;
bool found_addr = false;
for(i = 0; i < modified_addr_list.length; i++) {
if(modified_addr_list.buffer[i] == vma_start) {
addr_index = i;
break;
}
}
if(found_addr) {
swap_remove_index(&modified_addr_list, addr_index);
vm_flags_set(current_vma, (current_vma -> vm_flags) & ~VM_WRITE);
} else if((flags & VM_WRITE) == 0) {
push_to_buffer(&modified_addr_list, vma_start);
vm_flags_set(current_vma, (current_vma -> vm_flags) | VM_WRITE);
}
mutex_unlock(&modified_addr_list_lock);
}
}
break;
}
case WAIT_FOR_FINISH: {
int pid = (int)arg;
uintptr_t index = 0;
bool found_index = false;
while(!found_index) {
if(pid_task(find_vpid(pid), PIDTYPE_PID) == NULL) {
pr_alert("Error: The hijacked process unexpectedly terminated.\n");
return -ECANCELED;
}
uintptr_t i;
mutex_lock(&finish_sig_buf_lock);
for(i = 0; i < finish_sig_buf.length; i++) {
if(finish_sig_buf.buffer[i] == pid) {
found_index = true;
index = i;
break;
}
}
if(found_index) {
swap_remove_index(&finish_sig_buf, index);
}
mutex_unlock(&finish_sig_buf_lock);
udelay(1);
}
break;
}
case GET_INST_PTR: {
void* data_ptr = (void*)arg;
InstructionPointerRequest request;
if(copy_from_user(&request, data_ptr, sizeof(InstructionPointerRequest)) != 0) {
pr_alert("Error: Failed to copy instruction pointer request data from user\n");
return -EINVAL;
}
struct task_struct* task = pid_task(find_vpid(request.pid), PIDTYPE_PID);
if(task == NULL) {
pr_alert("Error: The target process was not running!\n");
return -EINVAL;
}
struct pt_regs* regs = task_pt_regs(task);
SavedRegisters* allocated_regs = kmalloc(sizeof(SavedRegisters), GFP_KERNEL);
allocated_regs -> regs = *regs;
allocated_regs -> pid = request.pid;
mutex_lock(&saved_regs_buf_lock);
push_to_buffer(&saved_regs_buf, (uintptr_t)(allocated_regs));
mutex_unlock(&saved_regs_buf_lock);
request.instruction_pointer = instruction_pointer(regs);
if(copy_to_user(data_ptr, &request, sizeof(InstructionPointerRequest)) != 0) {
pr_alert("Error: Failed to copy instruction pointer request data to user\n");
return -EINVAL;
}
break;
}
case RESTORE_REGS: {
uintptr_t i;
int pid = (int)arg;
struct task_struct* task = pid_task(find_vpid(pid), PIDTYPE_PID);
if(task == NULL) {
pr_alert("Error: The target process is not currently running!\n");
return -EINVAL;
}
bool found_pid = false;
uintptr_t pid_index = 0;
mutex_lock(&saved_regs_buf_lock);
for(i = 0; i < saved_regs_buf.length; i++) {
if(((SavedRegisters*)(saved_regs_buf.buffer[i])) -> pid == pid) {
found_pid = true;
pid_index = i;
break;
}
}
if(!found_pid) {
pr_alert("Error: No such process has had its registers saved!\n");
mutex_unlock(&saved_regs_buf_lock);
return -EINVAL;
}
*task_pt_regs(task) = ((SavedRegisters*)(saved_regs_buf.buffer[pid_index])) -> regs;
kfree((void*)(saved_regs_buf.buffer[pid_index]));
swap_remove_index(&saved_regs_buf, pid_index);
mutex_unlock(&saved_regs_buf_lock);
break;
}
default:
pr_alert("Invalid ioctl command\n");
return -EINVAL;
}
return 0;
}
int no_op_open(struct inode* _file_info, struct file* _file) {
return 0;
}
int no_op_close(struct inode* _file_info, struct file* _file) {
return 0;
}
ssize_t maybe_finish_signal(struct file *fptr, char __user *buffer, size_t blen, loff_t *offs) {
if(blen == 1) {
mutex_lock(&finish_sig_buf_lock);
uintptr_t i;
int pid_num = current -> pid;
pr_info("Notice: Got finishing signal from process with PID %d\n", pid_num);
for(i = 0; i < finish_sig_buf.length; i++) {
if(finish_sig_buf.buffer[i] == pid_num) {
pr_alert("Error: Got duplicate finishing signal from process with PID %d\n", pid_num);
mutex_unlock(&finish_sig_buf_lock);
return -EINVAL;
}
}
push_to_buffer(&finish_sig_buf, pid_num);
mutex_unlock(&finish_sig_buf_lock);
return 0;
}
pr_alert("Error: Got invalid read with buffer length of %ld\n", blen);
return -EINVAL;
}
ssize_t no_op_write(struct file *fptr, const char __user *buffer, size_t buf_len, loff_t *offs) {
return 0;
}
static struct file_operations raminspect_fops = {
.open = no_op_open,
.write = no_op_write,
.release = no_op_close,
.read = maybe_finish_signal,
.unlocked_ioctl = raminspect_ioctl,
};