]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
kernel: implement NX (No-Execute) support via IA32_EFER.NXE
authorTulio A M Mendes <[email protected]>
Mon, 25 May 2026 19:57:13 +0000 (16:57 -0300)
committerTulio A M Mendes <[email protected]>
Wed, 3 Jun 2026 04:02:35 +0000 (01:02 -0300)
Fix A01 (W^X/NX) which was deferred due to IA32_EFER.NXE MSR instability.
Root cause: NX bit was being set in PTEs without NXE enabled, causing
undefined behavior and kernel panic.

Changes:
- boot.S: Check CPUID.0x80000001:EDX bit 20 for NX support before enabling
- boot.S: Enable IA32_EFER.NXE (MSR 0xC0000080, bit 11) if NX supported
- vmm.c: Add g_nxe_enabled flag and check_nxe_enabled() function
- vmm.c: Conditionalize X86_PTE_NX usage based on g_nxe_enabled
- vmm.c: Print NX status in vmm_init()
- Makefile: Add -cpu qemu32,+nx to expose NX support in QEMU
- smoke_test.exp: Add -cpu qemu32,+nx for testing

Behavior:
- With NX support: NXE enabled, VMM uses NX bit for non-executable pages
- Without NX support: NXE not enabled, VMM ignores VMM_FLAG_NX
- W^X now works correctly for ELF loading, mmap/mprotect, etc.

Test: 119/119 PASS (SMP=4)

Makefile
src/arch/x86/boot.S
src/arch/x86/vmm.c
tests/smoke_test.exp

index 9198ca62bbc1df431431eac34879a75b7bfee774..c74c6dfeb6cbbc87793ee768d40be7ccdd51aedb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -293,6 +293,7 @@ run: iso
                -drive file=disk.img,if=ide,format=raw \
                -nic user,model=e1000 \
                -serial file:serial.log -monitor none -no-reboot -no-shutdown \
+               -cpu qemu32,+nx \
                $(QEMU_DFLAGS)
 
 run-arm: adros-arm.bin
index e17e1a3613c3f771bcbf780ada099aadc3440fa7..09fff385cd7e3f38db6f3301171aa5303cb17dba 100644 (file)
@@ -207,6 +207,23 @@ _start:
     or $0x20, %ecx              /* CR4.PAE = bit 5 */
     mov %ecx, %cr4
 
+    /* --- Check for NX support via CPUID.0x80000001:EDX bit 20 --- */
+    mov $0x80000000, %eax       /* Check max extended leaf */
+    cpuid
+    cmp $0x80000001, %eax       /* Need leaf 0x80000001 */
+    jb no_nxe                   /* Skip if not available */
+    mov $0x80000001, %eax       /* Get extended feature flags */
+    cpuid
+    bt $20, %edx                /* Test bit 20 (NX) */
+    jnc no_nxe                  /* Skip if NX not supported */
+
+    /* --- Enable NXE in IA32_EFER (MSR 0xC0000080, bit 11) --- */
+    mov $0xC0000080, %ecx       /* IA32_EFER MSR */
+    rdmsr
+    or $0x800, %eax             /* Set bit 11 (NXE) */
+    wrmsr
+
+no_nxe:
     /* --- Load CR3 with PDPT physical address --- */
     mov $boot_pdpt, %ecx
     sub $KERNEL_VIRT_BASE, %ecx
index 0934022bbe8cc0ba4e5f55f00d23907fe4819854..68f4e4942eb4270fc08470d5809bfb529769db0c 100644 (file)
@@ -67,11 +67,19 @@ extern uint64_t boot_pd3[512];
 
 static uintptr_t g_kernel_as = 0;
 static spinlock_t vmm_lock = {0};
+static int g_nxe_enabled = 0;  /* IA32_EFER.NXE enabled in boot.S */
 
 static inline void invlpg(uintptr_t vaddr) {
     __asm__ volatile("invlpg (%0)" : : "r" (vaddr) : "memory");
 }
 
+/* Check if IA32_EFER.NXE is enabled (MSR 0xC0000080, bit 11) */
+static int check_nxe_enabled(void) {
+    uint32_t eax, edx;
+    __asm__ volatile("rdmsr" : "=a"(eax), "=d"(edx) : "c"(0xC0000080));
+    return (eax & 0x800) != 0;  /* Bit 11 = NXE */
+}
+
 /* --- PAE address decomposition --- */
 
 static inline uint32_t pae_pdpt_index(uint64_t va) {
@@ -106,7 +114,8 @@ static uint64_t vmm_flags_to_x86(uint32_t flags) {
     if (flags & VMM_FLAG_PWT)     x86_flags |= X86_PTE_PWT;
     if (flags & VMM_FLAG_PCD)     x86_flags |= X86_PTE_PCD;
     if (flags & VMM_FLAG_COW)     x86_flags |= X86_PTE_COW;
-    if (flags & VMM_FLAG_NX)      x86_flags |= X86_PTE_NX;
+    /* Only set NX bit if IA32_EFER.NXE is enabled */
+    if ((flags & VMM_FLAG_NX) && g_nxe_enabled) x86_flags |= X86_PTE_NX;
     return x86_flags;
 }
 
@@ -577,6 +586,14 @@ void vmm_init(void) {
 
     g_kernel_as = hal_cpu_get_address_space();
 
+    /* Check if IA32_EFER.NXE was enabled in boot.S */
+    g_nxe_enabled = check_nxe_enabled();
+    if (g_nxe_enabled) {
+        kprintf("[VMM] NX (No-Execute) enabled.\n");
+    } else {
+        kprintf("[VMM] NX not available or not enabled.\n");
+    }
+
     /* Test mapping */
     vmm_map_page(0xB8000, 0xC00B8000, VMM_FLAG_PRESENT | VMM_FLAG_RW);
     kprintf("[VMM] Mapped VGA to 0xC00B8000.\n");
index b0a71ce28bfab9807fe361f280b296f6eb6a77c7..220cb8a3b60777c361732db4f674a487fbd66928 100755 (executable)
@@ -35,7 +35,8 @@ set qemu_pid [exec qemu-system-i386 \
     -drive file=$disk,if=ide,format=raw \
     -nic user,model=e1000 \
     -serial file:$serial_log -monitor none \
-    -no-reboot -no-shutdown &]
+    -no-reboot -no-shutdown \
+    -cpu qemu32,+nx &]
 
 # Wait for QEMU to start writing
 after 1000