Skip to content

Instantly share code, notes, and snippets.

@lethalbit
Last active June 22, 2023 03:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lethalbit/1f2f8046cab7442a722428a230477ad4 to your computer and use it in GitHub Desktop.
Save lethalbit/1f2f8046cab7442a722428a230477ad4 to your computer and use it in GitHub Desktop.
r0rplz: Read/write "ring 0" CPU registers from userspace via `/dev` nodes
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
#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 <aki@lethalbit.net>");
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