diff --git a/proto/.gitignore b/proto/.gitignore index 512929c0e4c207f5b88922e4c455d6aa11a70938..95e3be92663aa337d4327a6ff6a8793810c95bf4 100644 --- a/proto/.gitignore +++ b/proto/.gitignore @@ -1,16 +1,16 @@ -*.mod.c -..module-common.o.cmd -.Module.symvers.cmd -.hypervisor.ko.cmd -.hypervisor.mod.cmd -.hypervisor.mod.o.cmd -.hypervisor.o.cmd -.modules.order.cmd -*.symvers +# clangd +.cache/ compile_commands.json -*.ko -*.mod -*.order -.module-common.o -*.mod.o -*.o + +# LKM +**.o +**.o.cmd +**.symvers +**.symvers.cmd +**.modules.order.cmd +**.order +**.mod +**.mod.c +**.ko.cmd +**.mod.cmd +**.ko diff --git a/proto/Makefile b/proto/Makefile index ed1540df28a45cb80bc797db6ec36f7b1cff32f4..0a72d0c291adab958b9164602112c76b4cc9fd38 100644 --- a/proto/Makefile +++ b/proto/Makefile @@ -1,5 +1,6 @@ # https://sysprog21.github.io/lkmpg/ -obj-m += hypervisor.o +obj-m += vmbr.o +vmbr-objs := hypervisor.o ./msr/msr.o ./region/vmxon_reg.o PWD := $(CURDIR) diff --git a/proto/debug/debug.h b/proto/debug/debug.h new file mode 100644 index 0000000000000000000000000000000000000000..11f6c5adddbee0b2d9f25585286da9f97e2f420a --- /dev/null +++ b/proto/debug/debug.h @@ -0,0 +1,11 @@ +#pragma once +#include <linux/kernel.h> +#include <linux/printk.h> + +// This GCC extension removes the trailing comma when __VA_ARGS__ is empty. This +// ensures the macro works whether or not extra arguments are provided. +#define DEBUG_FMT(fmt, ...) \ + { \ + pr_debug("[%s:%d in %s()]: " fmt, kbasename(__FILE__), __LINE__, \ + __func__, ##__VA_ARGS__); \ + } diff --git a/proto/hypervisor.c b/proto/hypervisor.c old mode 100755 new mode 100644 index fc44b494dc86a0def83fb710a0d545b5339c0655..ee967a3175b86d9f9d934de62396638f5411f73d --- a/proto/hypervisor.c +++ b/proto/hypervisor.c @@ -10,30 +10,11 @@ #include <linux/printk.h> #include <linux/slab.h> -#define IA32_FEATURE_CONTROL_LOCK_BIT (1UL << 0) -#define IA32_FEATURE_CONTROL_VMXON_IN_SMX (1UL << 1) -#define IA32_FEATURE_CONTROL_VMXON_OUTSIDE_SMX (1UL << 2) -#define IA32_FEATURE_CONTROL_MSR (0x3a) - -// NOTE: IA32_BASIC_VMX MSR, Appendix 1 p.4'586 -// Bits 30:0 contain the 31-bit VMCS revision identifier -// Bit 31 is always 0 -// Bit 44:32 report the number of bytes to allocate for the VMXON and VMCS -// regions -#define IA32_VMX_BASIC (0x480) - -#define VMCS_REVISION_ID (__rdmsr(IA32_VMX_BASIC) & 0x7fffffff) -#define REGION_SIZE ((__rdmsr(IA32_VMX_BASIC) >> 32) & 0x1fff) - -// Appendix 8 p. 4'592 of Intel SDM -#define IA32_VMX_CR0_FIXED0 (0x486) -#define IA32_VMX_CR0_FIXED1 (0x487) -#define IA32_VMX_CR4_FIXED0 (0x488) -#define IA32_VMX_CR4_FIXED1 (0x489) - -#define CR0_PE (1UL << 0) -#define CR0_NE (1UL << 5) -#define CR0_PG (1UL << 31) +/*============== my includes ==============*/ +#include "debug/debug.h" +#include "linux/cleanup.h" +#include "msr/msr.h" +#include "region/vxmon_reg.h" #define NULL ((void *)0) @@ -43,12 +24,7 @@ /* : clobbered registers list (optional)*/ /* );*/ -struct vmxon_region_t { - void *va; - unsigned long long pa; -} __attribute__((packed)); - -static struct vmxon_region_t vmxon_region; +static struct vmxon_reg_t vmxon_region; static void cr4_enable_vmx(void) { unsigned long cr4; @@ -58,8 +34,9 @@ static void cr4_enable_vmx(void) { __asm__ volatile("mov %0, %%cr4" ::"r"(cr4)); } -static bool vmx_supported(void) { +static bool vmx_support_cpuid(void) { unsigned int ecx = cpuid_ecx(1); + /*__asm__ volatile("mov $1, %rax");*/ /*__asm__ volatile("cpuid");*/ /*__asm__ volatile("mov %%ecx , %0\n\t" : "=r"(ecx));*/ @@ -67,139 +44,69 @@ static bool vmx_supported(void) { return (ecx >> 5) & 1; } -static bool ia32_feature_control_flags(void) { - int msr_value = __rdmsr(IA32_FEATURE_CONTROL_MSR); - - if (!(msr_value & IA32_FEATURE_CONTROL_LOCK_BIT)) { - printk(KERN_INFO "Writing to the IA32_FEATURE_CONTROL MSR\n"); - __wrmsr(IA32_FEATURE_CONTROL_MSR, - IA32_FEATURE_CONTROL_LOCK_BIT | - IA32_FEATURE_CONTROL_VMXON_OUTSIDE_SMX, - 0); - } - - if (!(msr_value & IA32_FEATURE_CONTROL_VMXON_OUTSIDE_SMX)) { - printk(KERN_INFO "Virtualization isn't available\n"); - return false; - } - - return true; +/* + * https://elixir.bootlin.com/linux/v6.12.4/source/tools/testing/selftests/kvm/include/x86_64/vmx.h#L297 + */ +static unsigned char vmxon(void *pa) { + unsigned char ret; + + __asm__ __volatile__("vmxon %[pa]; setna %[ret]" + : [ret] "=rm"(ret) + : [pa] "m"(vmxon_region.pa) + : "cc", "memory"); + return ret; } -static void restrictions_cr_msrs(void) { - unsigned long long cr0, cr4; - - __asm__ volatile("mov %%cr0, %0" : "=r"(cr0)); - __asm__ volatile("mov %%cr4, %0" : "=r"(cr4)); - - unsigned long long cr0_fixed0 = __rdmsr(IA32_VMX_CR0_FIXED0); - unsigned long long cr0_fixed1 = __rdmsr(IA32_VMX_CR0_FIXED1); - - unsigned long long cr4_fixed0 = __rdmsr(IA32_VMX_CR4_FIXED0); - unsigned long long cr4_fixed1 = __rdmsr(IA32_VMX_CR4_FIXED1); - - pr_debug("CR0 = 0x%llx\n", cr0); - pr_debug("CR4 = 0x%llx\n", cr4); - - // If bit X is 1 in IA32_VMX_CR0_FIXED0, then that bit of CR0 is fixed to 1 - // in VMX operation. Similarly, if bit X is 0 in IA32_VMX_CR0_FIXED1, then - // that bit of CR0 is fixed to 0 in VMX operation. It is always the case - - // Setting mandatory bits of FIXED0 in CR0 - cr0 |= cr0_fixed0; - // Clearing mandatory bits of FIXED1 in CR0; - cr0 &= cr0_fixed1; - - cr4 |= cr4_fixed0; - cr4 &= cr4_fixed1; - - __asm__ volatile("mov %0, %%cr0" ::"r"(cr0)); - __asm__ volatile("mov %0, %%cr4" ::"r"(cr4)); -} +static int my_init(void) { + pr_info("Checking VMX support using CPUID\n"); + if (!vmx_support_cpuid()) { + pr_err("VMX isn't supported\n"); + return -1; + } -static int vmxon_region_alloc(void) { - void *region = kzalloc(REGION_SIZE, GFP_KERNEL); + DEBUG_FMT("IA32_VMX_BASIC_MSR = 0x%llx\n", __rdmsr(IA32_VMX_BASIC)); - if (!region) { + pr_info("Initializing VMXON region\n"); + if (init_vmxon_reg(&vmxon_region) != 0) { + pr_err("Failed to initialized the VMXON region\n"); return -1; } - vmxon_region.va = region; - vmxon_region.pa = __pa(region); + pr_info("VA of the allocated region = 0x%px\n", vmxon_region.va); + pr_info("PA of the allocated region = 0x%px\n", vmxon_region.pa); - return 0; -} + pr_info("Reading VMXON region for VMCS ID: 0x%x\n", + (*(uint32_t *)vmxon_region.va)); -static void write_vmcs_rev_id_to_vmxon(void) { - (*(uint32_t *)vmxon_region.va) = VMCS_REVISION_ID; -} - -static int my_init(void) { - if (!vmx_supported()) { - printk(KERN_INFO "VMX isn't supported\n"); - return -1; - } + pr_info("Patching CR0 and CR4 depending on the value of their respective " + "MSRs\n"); + patch_control_registers(); - printk(KERN_INFO "VMX is supported!\n"); + pr_info("Enabling VMX in CR4\n"); cr4_enable_vmx(); - printk(KERN_INFO "VMX has been successfully enabled!\n"); - - printk(KERN_INFO - "Check the necessary flags of the IA32_FEATURE_CONTROL_MSR\n"); + pr_info("Checking the necessary flags of the IA32_FEATURE_CONTROL_MSR\n"); + DEBUG_FMT("IA32_FEATURE_CONTROL = %llu\n", + __rdmsr(IA32_FEATURE_CONTROL_MSR)); if (!ia32_feature_control_flags()) { - printk(KERN_INFO "The flags of the IA32_FEATURE_CONTROL MSR do not " - "permit virtualization\n"); + pr_err("The flags of the IA32_FEATURE_CONTROL MSR do not permit " + "virtualization\n"); return -1; } - printk(KERN_INFO "IA32_FEATURE_CONTROL MSR flags allow virtualization\n"); + pr_info("Executing VMXON with address = 0x%px as its operand\n", + vmxon_region.pa); - printk(KERN_INFO "Patching CR0 and CR4 depending on the value of their " - "respective MSRs\n"); + /*__asm__ volatile("vmxon %0" ::"m"(vmxon_region.pa) : "memory");*/ - restrictions_cr_msrs(); + unsigned char vmxon_ret; - printk(KERN_INFO "Allocating memory for VMXON region\n"); - - if (vmxon_region_alloc() != 0) { - pr_err("Failed to allocated memory for the VMXON region\n"); + if ((vmxon_ret = vmxon(vmxon_region.pa) != 0)) { + pr_err("vmxon failed with return code %d\n", vmxon_ret); return -1; } - printk(KERN_INFO "VA of the allocated region = 0x%px\n", - __va(vmxon_region.pa)); - printk(KERN_INFO "PA of the allocated region = 0x%llx\n", vmxon_region.pa); - - printk(KERN_INFO - "Reading IA32_VMX_BASIC MSR for VMCS revision identifier\n"); - - unsigned long long vmx_basic_msr = __rdmsr(IA32_VMX_BASIC); - pr_debug("IA32_VMX_BASIC_MSR = 0x%llx\n", vmx_basic_msr); - - unsigned long size_to_alloc = REGION_SIZE; - pr_debug("Region size to allocate = %lu bytes\n", size_to_alloc); - - unsigned long vmcs_revision_id = VMCS_REVISION_ID; - pr_debug("VMCS revision identifier = 0x%lx\n", vmcs_revision_id); - - printk(KERN_INFO "Writing VMCS Revision ID to VMXON\n"); - - pr_debug("BEFORE: Reading VMXON region for VMCS ID: 0x%x\n", - (*(uint32_t *)vmxon_region.va)); - - write_vmcs_rev_id_to_vmxon(); - - pr_debug("AFTER: Reading VMXON region for VMCS ID: 0x%x\n", - (*(uint32_t *)vmxon_region.va)); - - /*__asm__ volatile("mov %0, %%rax" ::"r"(vmxon_region.pa) : "rax");*/ - - printk(KERN_INFO "Executing VMXON with address = 0x%llx as its operand\n", - vmxon_region.pa); - - // NOTE: VMXON p.4233 - __asm__ volatile("vmxon %0" ::"m"(vmxon_region.pa) : "memory"); + DEBUG_FMT("vmxon ret = %d\n", vmxon_ret); return 0; } diff --git a/proto/msr/msr.c b/proto/msr/msr.c new file mode 100644 index 0000000000000000000000000000000000000000..e6c7058eb80c270b1a863f2cd4dbea4cfc65ecaa --- /dev/null +++ b/proto/msr/msr.c @@ -0,0 +1,56 @@ +#include "msr.h" +#include "../debug/debug.h" +#include <linux/printk.h> +#include <linux/types.h> + +bool ia32_feature_control_flags(void) { + int msr_value = __rdmsr(IA32_FEATURE_CONTROL_MSR); + + if (!(msr_value & IA32_FEATURE_CONTROL_LOCK_BIT)) { + DEBUG_FMT("Lock bit is not set in IA32_FEATURE_CONTROL_MSR\n"); + __wrmsr(IA32_FEATURE_CONTROL_MSR, + IA32_FEATURE_CONTROL_LOCK_BIT | + IA32_FEATURE_CONTROL_VMXON_OUTSIDE_SMX, + 0); + } + + if (!(msr_value & IA32_FEATURE_CONTROL_VMXON_OUTSIDE_SMX)) { + return false; + } + + return true; +} + +void patch_control_registers(void) { + unsigned long long cr0, cr4; + + __asm__ volatile("mov %%cr0, %0" : "=r"(cr0)); + __asm__ volatile("mov %%cr4, %0" : "=r"(cr4)); + + unsigned long long cr0_fixed0 = __rdmsr(IA32_VMX_CR0_FIXED0); + unsigned long long cr0_fixed1 = __rdmsr(IA32_VMX_CR0_FIXED1); + + unsigned long long cr4_fixed0 = __rdmsr(IA32_VMX_CR4_FIXED0); + unsigned long long cr4_fixed1 = __rdmsr(IA32_VMX_CR4_FIXED1); + + DEBUG_FMT("Pre-patch: CR0 = 0x%llx\n", cr0); + DEBUG_FMT("Pre-patch: CR4 = 0x%llx\n", cr4); + + // If bit X is 1 in IA32_VMX_CR0_FIXED0, then that bit of CR0 is fixed to 1 + // in VMX operation. Similarly, if bit X is 0 in IA32_VMX_CR0_FIXED1, then + // that bit of CR0 is fixed to 0 in VMX operation. It is always the case + + // Setting mandatory bits of FIXED0 in CR0 + cr0 |= cr0_fixed0; + // Clearing mandatory bits of FIXED1 in CR0; + cr0 &= cr0_fixed1; + + cr4 |= cr4_fixed0; + cr4 &= cr4_fixed1; + + DEBUG_FMT("Post-patch: CR0 = 0x%llx\n", cr0); + DEBUG_FMT("Post-patch: CR4 = 0x%llx\n", cr4); + + __asm__ volatile("mov %0, %%cr0" ::"r"(cr0)); + __asm__ volatile("mov %0, %%cr4" ::"r"(cr4)); +} diff --git a/proto/msr/msr.h b/proto/msr/msr.h new file mode 100644 index 0000000000000000000000000000000000000000..60e18a51002dd1bfba7af9bb10f3c0ad08164064 --- /dev/null +++ b/proto/msr/msr.h @@ -0,0 +1,31 @@ +#pragma once +#include <asm/msr.h> + +#define IA32_FEATURE_CONTROL_LOCK_BIT (1UL << 0) +#define IA32_FEATURE_CONTROL_VMXON_IN_SMX (1UL << 1) +#define IA32_FEATURE_CONTROL_VMXON_OUTSIDE_SMX (1UL << 2) +#define IA32_FEATURE_CONTROL_MSR (0x3a) + +// NOTE: IA32_BASIC_VMX MSR, Appendix 1 p.4'586 +// Bits 30:0 contain the 31-bit VMCS revision identifier +// Bit 31 is always 0 +// Bit 44:32 report the number of bytes to allocate for the VMXON and VMCS +// regions +#define IA32_VMX_BASIC (0x480) + +#define VMCS_REVISION_ID (__rdmsr(IA32_VMX_BASIC) & 0x7fffffff) +#define REGION_SIZE ((__rdmsr(IA32_VMX_BASIC) >> 32) & 0x1fff) + +// NOTE: Appendix 8 p. 4'592 of Intel SDM +#define IA32_VMX_CR0_FIXED0 (0x486) +#define IA32_VMX_CR0_FIXED1 (0x487) +#define IA32_VMX_CR4_FIXED0 (0x488) +#define IA32_VMX_CR4_FIXED1 (0x489) + +#define CR0_PE (1UL << 0) +#define CR0_NE (1UL << 5) +#define CR0_PG (1UL << 31) + +bool ia32_feature_control_flags(void); + +void patch_control_registers(void); diff --git a/proto/region/vmxon_reg.c b/proto/region/vmxon_reg.c new file mode 100644 index 0000000000000000000000000000000000000000..fa7fc247d01ce16242e2bffb9a7ffd4e8d2d55bb --- /dev/null +++ b/proto/region/vmxon_reg.c @@ -0,0 +1,64 @@ +#include "../debug/debug.h" +#include "../msr/msr.h" +#include "vxmon_reg.h" +#include <asm/page.h> +#include <linux/printk.h> +#include <linux/slab.h> + +static int alloc_vmxon_internals(struct vmxon_reg_t *reg) { + if (!reg) { + DEBUG_FMT("vmxon_reg_t isn't allocated\n"); + return -1; + } + + void *region = kzalloc(PAGE_SIZE, GFP_KERNEL); + + if (!region) { + DEBUG_FMT("VMXON region allocation has failed\n"); + return -1; + } + + if (((unsigned long long)region & 0x1fff) != 0) { + DEBUG_FMT("Region isn't properly aligned\n"); + return -1; + } + + DEBUG_FMT( + "Reading IA32_VMX_BASIC MSR for allocation size (in bytes) = %llu \n", + REGION_SIZE); + + reg->size = REGION_SIZE; + reg->va = region; + reg->pa = (void *)__pa(region); + + return 0; +} + +static int write_vmcs_rev_id_to_vmxon(struct vmxon_reg_t *reg) { + if (!reg) { + DEBUG_FMT("vmxon_reg_t isn't allocated\n"); + return -1; + } + + DEBUG_FMT( + "Reading IA32_VMX_BASIC MSR for VMCS revision identifier = %llu\n", + VMCS_REVISION_ID); + + DEBUG_FMT("Writing VMCS Revision ID to VMXON region\n"); + + (*(uint32_t *)reg->va) = VMCS_REVISION_ID; + + return 0; +} + +int init_vmxon_reg(struct vmxon_reg_t *reg) { + if (alloc_vmxon_internals(reg) != 0) { + return -1; + } + + if (write_vmcs_rev_id_to_vmxon(reg) != 0) { + return -1; + } + + return 0; +} diff --git a/proto/region/vxmon_reg.h b/proto/region/vxmon_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..3b058b4112969bdf99437d6c855149937a89b2ce --- /dev/null +++ b/proto/region/vxmon_reg.h @@ -0,0 +1,9 @@ +#pragma once + +struct vmxon_reg_t { + unsigned long size; + void *va; + void *pa; +}; + +int init_vmxon_reg(struct vmxon_reg_t *reg);