The Reserved Bits in Page Table Entries: How the CPU Catches Bugs the OS Forgot

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.

See it in action: Check out Delete These From Your PC! by Dedman Tech to see this theory applied.
Key Takeaway: Reserved bits in page table entries are the CPU's contract that unused bits stay zero — violate it and you get a distinctive fault (error code bit 3) that points at PTE construction, not memory access.

All newsletters