2026-06-16
Every 64-bit page table entry on x86-64 has fewer meaningful bits than you'd think. The address field tops out at bit 51 on most chips (52 with 5-level paging), the low 12 bits are flags, and bits 62-52 plus bit 51 (on 4-level systems) are reserved — the CPU requires them to be zero. Set any of them, and the next access through that PTE triggers a reserved bit page fault, signaled by bit 3 of the page-fault error code.
Why does this exist? The architects wanted forward compatibility. If future CPUs widen the physical address space, today's kernels that zero those bits will keep working; kernels that scribbled garbage there would silently translate to wrong physical pages. The reserved-bit fault forces software to be explicit.
The catch nobody warns you about: the width of the "reserved" region depends on the CPU's physical address width, exposed by CPUID.80000008H:EAX[7:0]. A Xeon with 46-bit physical addressing reserves bits 51-46. A laptop chip with 39-bit addressing reserves bits 51-39. Move a memory image between machines (live migration, snapshot restore) and a PTE that was legal on the source can fault on the destination.
Real-world example: In 2018, KVM had a bug where shadow page tables copied guest PTE bits without masking against the host's MAXPHYADDR. A guest running on a 52-bit host, migrated to a 46-bit host, immediately took unrecoverable reserved-bit faults on the first memory access after resume. The fix: mask high bits against host_maxphyaddr during shadow PTE construction. Linux commit d6321d493319 in arch/x86/kvm/mmu.c.
The NX bit twist: Bit 63 is not reserved — it's the No-Execute bit. But it only exists if EFER.NXE = 1. Set bit 63 with NXE disabled and you get… a reserved-bit fault. Many early hypervisors hit this when forwarding PTEs into nested guests that hadn't enabled NXE yet.
Rule of thumb: when constructing a PTE, mask the physical address field with ((1ULL << maxphyaddr) - 1) & ~0xFFFULL, then OR in your flags. Never trust that the upper bits are zero just because you didn't set them — uninitialized stack memory, copied structures, and shifted-in addresses can all smuggle ones into the reserved range.
Debugging tip: when you see error code bit 3 set in a page-fault trace (RSVD in Linux's decoded output), don't look at the faulting address. Look at the PTE itself with x/gx on the page-table walk — the bug is in what you wrote, not what you read.
