Last active
June 22, 2023 03:37
-
-
Save lethalbit/1f2f8046cab7442a722428a230477ad4 to your computer and use it in GitHub Desktop.
r0rplz: Read/write "ring 0" CPU registers from userspace via `/dev` nodes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
obj-m += r0rplz.o | |
all: | |
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules | |
install: | |
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install | |
clean: | |
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <asm/msr.h> | |
#include <asm/processor.h> | |
#include <linux/cpu.h> | |
#include <linux/device.h> | |
#include <linux/errno.h> | |
#include <linux/types.h> | |
#include <linux/fcntl.h> | |
#include <linux/fs.h> | |
#include <linux/gfp.h> | |
#include <linux/init.h> | |
#include <linux/kernel.h> | |
#include <linux/major.h> | |
#include <linux/module.h> | |
#include <linux/notifier.h> | |
#include <linux/poll.h> | |
#include <linux/smp.h> | |
#include <linux/types.h> | |
#include <linux/uaccess.h> | |
#include <linux/completion.h> | |
#include <linux/cdev.h> | |
#include <linux/slab.h> | |
#include <linux/version.h> | |
extern unsigned long __force_order; | |
#define _str(s) #s | |
#define str(s) _str(s) | |
#define r0rplz_DEF_REG_READ(REG) \ | |
static inline r0rplz_reg_ret_t r0rplz_read_reg_##REG(void) { \ | |
r0rplz_reg_ret_t val = { .val = 0x0ULL, .ext = 0x0000U }; \ | |
asm volatile ("MOV %%" str(REG) ", %0\n\t" : "=r"(val.val), "=m"(__force_order) : : ); \ | |
return val; \ | |
} | |
#define READ_REG(REG) r0rplz_read_reg_##REG() | |
static int MAJOR_NUM = 0; | |
static struct class* R0RPLZ_CLASS; | |
static enum cpuhp_state cpuhp_r0rplz_state; | |
static LIST_HEAD(r0rplz_cpu_callbacks); | |
static dev_t r0rplz_cdev_base; | |
typedef struct file file; | |
typedef struct device device; | |
typedef struct inode inode; | |
typedef struct list_head list_head; | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) | |
typedef struct call_single_data call_single_data_t; | |
#endif | |
/* Using this for all register returns is slightly more expensive, but safer */ | |
typedef struct { | |
uint64_t val; | |
uint16_t ext; | |
} r0rplz_reg_ret_t; | |
typedef r0rplz_reg_ret_t (*r0rplz_reader_t)(void); | |
typedef struct { | |
r0rplz_reader_t reader; | |
r0rplz_reg_ret_t val; | |
struct completion done; | |
} reginfo_t; | |
typedef struct { | |
uint cpu; | |
dev_t dev_begin; | |
dev_t dev_end; | |
list_head link; | |
} r0rplz_callback_t; | |
/* Read register defines */ | |
r0rplz_DEF_REG_READ(cr0); | |
r0rplz_DEF_REG_READ(cr2); | |
r0rplz_DEF_REG_READ(cr3); | |
r0rplz_DEF_REG_READ(cr4); | |
r0rplz_DEF_REG_READ(cr8); | |
/* The GDT, IDT, LDT, MSW, and TR are special cases */ | |
static inline r0rplz_reg_ret_t r0rplz_read_reg_gdt(void) { | |
r0rplz_reg_ret_t val = { .val = 0x0ULL, .ext = 0x0000U }; | |
asm volatile ("SGDT %0\n\t" : "=m"(val), "=m"(__force_order) : : ); | |
return val; | |
} | |
static inline r0rplz_reg_ret_t r0rplz_read_reg_idt(void) { | |
r0rplz_reg_ret_t val = { .val = 0x0ULL, .ext = 0x0000U }; | |
asm volatile ("SIDT %0\n\t" : "=m"(val), "=m"(__force_order) : : ); | |
return val; | |
} | |
static inline r0rplz_reg_ret_t r0rplz_read_reg_ldt(void) { | |
r0rplz_reg_ret_t val = { .val = 0x0ULL, .ext = 0x0000U }; | |
asm volatile ("SLDT %0\n\t" : "=r"(val), "=m"(__force_order) : : ); | |
return val; | |
} | |
static inline r0rplz_reg_ret_t r0rplz_read_reg_msw(void) { | |
r0rplz_reg_ret_t val = { .val = 0x0ULL, .ext = 0x0000U }; | |
asm volatile ("SMSW %0\n\t" : "=r"(val), "=m"(__force_order) : : ); | |
return val; | |
} | |
static inline r0rplz_reg_ret_t r0rplz_read_reg_tr(void) { | |
r0rplz_reg_ret_t val = { .val = 0x0ULL, .ext = 0x0000U }; | |
asm volatile ("STR %0\n\t" : "=r"(val), "=m"(__force_order) : : ); | |
return val; | |
} | |
/* register map */ | |
static const char* r0rplz_reg_map[9] = { | |
"cr0", "cr2", "cr3", "cr4", "cr8", | |
"gdt", "idt", "ldt", "tr" | |
}; | |
static r0rplz_reader_t r0rplz_reg_readers[9] = { | |
r0rplz_read_reg_cr0, | |
r0rplz_read_reg_cr2, | |
r0rplz_read_reg_cr3, | |
r0rplz_read_reg_cr4, | |
r0rplz_read_reg_cr8, | |
r0rplz_read_reg_gdt, | |
r0rplz_read_reg_idt, | |
r0rplz_read_reg_ldt, | |
r0rplz_read_reg_tr, | |
}; | |
static void r0rplz_read_smp(void * inf) { | |
reginfo_t* rinf = inf; | |
printk(KERN_DEBUG "r0rplz: read_smp() reader: %p\n", rinf->reader); | |
rinf->val = rinf->reader(); | |
printk(KERN_DEBUG "r0rplz: read_smp() val: 0x%016llX ext: 0x%04x \n", rinf->val.val, rinf->val.ext); | |
complete(&rinf->done); | |
} | |
static r0rplz_callback_t *r0rplz_callback_from(dev_t dev) { | |
list_head *i; | |
list_for_each(i, &r0rplz_cpu_callbacks) { | |
r0rplz_callback_t *callbacks = list_entry(i, r0rplz_callback_t, link); | |
if (callbacks->dev_begin <= dev && callbacks->dev_end > dev) | |
return callbacks; | |
} | |
return NULL; | |
} | |
/* IO Handlers */ | |
static ssize_t r0rplz_read(file *file, char __user *buf, size_t count, loff_t *ppos) { | |
int ierr; | |
ulong ulerr; | |
/* extract the CPU# and the register # from the minor version of the file */ | |
r0rplz_callback_t *callback = file->private_data; | |
/* Initialize empty register */ | |
reginfo_t rf; | |
call_single_data_t csd = { | |
.func = r0rplz_read_smp, | |
.info = &rf | |
}; | |
uint cpu = callback->cpu; | |
int reg = MINOR(file_inode(file)->i_rdev - callback->dev_begin); | |
printk(KERN_DEBUG "r0rplz: read() cpu: %u reg: %d\n", cpu, reg); | |
if(count < sizeof(uint64_t)) | |
return -ENOMEM; | |
/* Async SMP stuff */ | |
init_completion(&rf.done); | |
rf.reader = r0rplz_reg_readers[reg]; | |
rf.val.val = 0x0000000000000000ULL; | |
rf.val.ext = 0x0000U; | |
ierr = smp_call_function_single_async(cpu, &csd); | |
printk(KERN_DEBUG "r0rplz: smp_call_function_single_async (%d)\n", ierr); | |
if(ierr) | |
return ierr; | |
wait_for_completion(&rf.done); | |
/* Copy the result back into userspace */ | |
ulerr = copy_to_user(buf, &rf.val, sizeof(uint64_t)); | |
printk(KERN_DEBUG "r0rplz: copy_to_user (%lu)\n", ulerr); | |
if(ulerr) | |
return ulerr; | |
return sizeof(uint64_t); | |
} | |
static int r0rplz_open(inode *finode, file *file) { | |
/* extract the CPU# and the register # from the minor version of the file */ | |
r0rplz_callback_t *callback = r0rplz_callback_from(finode->i_rdev); | |
uint cpu_n; | |
if (!callback) | |
return -ENODEV; | |
cpu_n = callback->cpu; | |
/* Check if we can actually poke the CPU */ | |
if(cpu_n >= nr_cpu_ids || !cpu_online(cpu_n)) | |
return -ENODEV; | |
file->private_data = callback; | |
return 0; | |
} | |
static const struct file_operations r0rplz_fops = { | |
.owner = THIS_MODULE, | |
.llseek = no_seek_end_llseek, | |
.read = r0rplz_read, | |
.open = r0rplz_open | |
}; | |
/* CPU device creation/destruction */ | |
static int r0rplz_mkcpu_dev(uint cpu) { | |
device *dev = NULL; | |
int r; | |
uint minor_start = cpu * 9; | |
r0rplz_callback_t *r0rplz_cpu = kmalloc(sizeof(r0rplz_callback_t), GFP_KERNEL); | |
if (!r0rplz_cpu) | |
return -ENOMEM; | |
r0rplz_cpu->cpu = cpu; | |
r0rplz_cpu->dev_begin = MKDEV(MAJOR_NUM, minor_start); | |
r0rplz_cpu->dev_end = MKDEV(MAJOR_NUM, (minor_start + 9)); | |
printk(KERN_DEBUG "r0rplz: Allocating devices for CPU %u - range %u:%u to %u:%u\n", cpu, MAJOR(r0rplz_cpu->dev_begin), | |
MINOR(r0rplz_cpu->dev_begin), MAJOR(r0rplz_cpu->dev_end), MINOR(r0rplz_cpu->dev_end)); | |
for(r = 0; r < 9; ++r) { | |
if(IS_ERR(dev = device_create(R0RPLZ_CLASS, NULL, MKDEV(MAJOR_NUM, (minor_start + r)), NULL, "r0rp/cpu%d/%s", cpu, r0rplz_reg_map[r]))) { | |
return PTR_ERR(dev); | |
} | |
} | |
list_add(&r0rplz_cpu->link, &r0rplz_cpu_callbacks); | |
return 0; | |
} | |
static r0rplz_callback_t *r0rplz_callbacks_for(uint cpu) { | |
list_head *i; | |
list_for_each(i, &r0rplz_cpu_callbacks) { | |
r0rplz_callback_t *callbacks = list_entry(i, r0rplz_callback_t, link); | |
if (callbacks->cpu == cpu) | |
return callbacks; | |
} | |
return NULL; | |
} | |
static int r0rplz_rmcpu_dev(uint cpu) { | |
int r; | |
uint minor_start = cpu * 9; | |
r0rplz_callback_t *r0rplz_cpu = r0rplz_callbacks_for(cpu); | |
for(r = 0; r < 9; ++r) { | |
device_destroy(R0RPLZ_CLASS, MKDEV(MAJOR_NUM, (minor_start + r))); | |
} | |
if (r0rplz_cpu) { | |
__list_del_entry(&r0rplz_cpu->link); | |
kfree(r0rplz_cpu); | |
} | |
return 0; | |
} | |
int __init r0rplz_ctor(void) { | |
int err = 0; | |
/* Try to register the base device */ | |
if((MAJOR_NUM = __register_chrdev(0, 0, NR_CPUS * 9, "r0rp", &r0rplz_fops)) < 0) | |
return -EBUSY; | |
r0rplz_cdev_base = MKDEV(MAJOR_NUM, 0); | |
/* Create the class needed */ | |
if(IS_ERR(R0RPLZ_CLASS = class_create(THIS_MODULE, "r0rplz"))) | |
err = PTR_ERR(R0RPLZ_CLASS); | |
if((cpuhp_r0rplz_state = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/r0rplz:online", r0rplz_mkcpu_dev, r0rplz_rmcpu_dev)) < 0) | |
err = cpuhp_r0rplz_state; | |
return err; | |
} | |
static void __exit r0rplz_dtor(void) { | |
/* punt everything off */ | |
cpuhp_remove_state(cpuhp_r0rplz_state); | |
class_destroy(R0RPLZ_CLASS); | |
__unregister_chrdev(MAJOR_NUM, 0, NR_CPUS, "r0rp"); | |
} | |
module_init(r0rplz_ctor); | |
module_exit(r0rplz_dtor); | |
MODULE_AUTHOR("Aki Van Ness <[email protected]>"); | |
MODULE_DESCRIPTION("Exposes (most) ring 0 registers as read only device nodes"); | |
MODULE_LICENSE("GPL"); /* This is only GPL'd because I need API's that are exported as GPL-Only, such BS */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment