feat: extend paging with pagingNxSupported and pagingIsExecutable
This commit is contained in:
1 parent
19f79ca2fe
commit
fc4688937b
1 file changed
+161
-3
+161
-3
@@ -6,6 +6,20 @@
|
||||
#define entryPageSize 0x80
|
||||
#define addressMask 0x000FFFFFFFFFF000ull
|
||||
|
||||
/* Set by boot.S once, before kmain ever runs: 1 if CPUID.80000001H:EDX.NX
|
||||
was reported AND IA32_EFER.NXE was actually written, 0 otherwise (either
|
||||
the CPU never claimed the feature, or -- unreachable on any CPU that
|
||||
passed this kernel's own checkLongMode, but checked independently and
|
||||
honestly anyway -- some hypervisor quirk refused it). A plain uint32,
|
||||
not this codebase's own enum bool, so boot.S's 32-bit `movl` store has
|
||||
an unambiguous, non-implementation-defined width to write into. */
|
||||
extern uint32 cpuNxSupported;
|
||||
|
||||
bool pagingNxSupported(void)
|
||||
{
|
||||
return cpuNxSupported != 0;
|
||||
}
|
||||
|
||||
static uint64 readCr3(void)
|
||||
{
|
||||
uint64 value;
|
||||
@@ -65,11 +79,72 @@ bool pagingMap(uint64 virtualAddr, uint64 physicalAddr, uint64 flags)
|
||||
if (!pt)
|
||||
return false;
|
||||
|
||||
pt[ptIndex] = (physicalAddr & addressMask) | (flags & 0xFFF) | pagePresent;
|
||||
/* flags & 0xFFF keeps the low-12-bits PTE flags (pagePresent/pageWrite/
|
||||
pageUser, plus whatever hardware-ignored bits a caller might pass);
|
||||
pageNoExecute (bit 63) is a completely separate mask so it survives
|
||||
that truncation -- silently dropped rather than written when
|
||||
pagingNxSupported() is false, since bit 63 of a PTE is architecturally
|
||||
reserved (must be 0) whenever IA32_EFER.NXE is clear, and setting a
|
||||
reserved bit faults. */
|
||||
uint64 leafFlags = (flags & 0xFFF) | pagePresent;
|
||||
if ((flags & pageNoExecute) && pagingNxSupported())
|
||||
leafFlags |= pageNoExecute;
|
||||
|
||||
pt[ptIndex] = (physicalAddr & addressMask) | leafFlags;
|
||||
invalidatePage(virtualAddr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pagingIsExecutable(uint64 virtualAddr)
|
||||
{
|
||||
uint64 pml4Index = (virtualAddr >> 39) & 0x1FF;
|
||||
uint64 pdptIndex = (virtualAddr >> 30) & 0x1FF;
|
||||
uint64 pdIndex = (virtualAddr >> 21) & 0x1FF;
|
||||
uint64 ptIndex = (virtualAddr >> 12) & 0x1FF;
|
||||
|
||||
uint64* pml4 = tableFrom(readCr3());
|
||||
uint64* pdpt = nextTable(pml4, pml4Index, 0, false);
|
||||
if (!pdpt)
|
||||
return false;
|
||||
uint64* pd = nextTable(pdpt, pdptIndex, 0, false);
|
||||
if (!pd)
|
||||
return false;
|
||||
if (pd[pdIndex] & entryPageSize)
|
||||
return (pd[pdIndex] & entryPresent) && !(pd[pdIndex] & pageNoExecute);
|
||||
|
||||
uint64* pt = nextTable(pd, pdIndex, 0, false);
|
||||
if (!pt)
|
||||
return false;
|
||||
if (!(pt[ptIndex] & entryPresent))
|
||||
return false;
|
||||
return !(pt[ptIndex] & pageNoExecute);
|
||||
}
|
||||
|
||||
bool pagingIsWritable(uint64 virtualAddr)
|
||||
{
|
||||
uint64 pml4Index = (virtualAddr >> 39) & 0x1FF;
|
||||
uint64 pdptIndex = (virtualAddr >> 30) & 0x1FF;
|
||||
uint64 pdIndex = (virtualAddr >> 21) & 0x1FF;
|
||||
uint64 ptIndex = (virtualAddr >> 12) & 0x1FF;
|
||||
|
||||
uint64* pml4 = tableFrom(readCr3());
|
||||
uint64* pdpt = nextTable(pml4, pml4Index, 0, false);
|
||||
if (!pdpt)
|
||||
return false;
|
||||
uint64* pd = nextTable(pdpt, pdptIndex, 0, false);
|
||||
if (!pd)
|
||||
return false;
|
||||
if (pd[pdIndex] & entryPageSize)
|
||||
return (pd[pdIndex] & entryPresent) && (pd[pdIndex] & pageWrite);
|
||||
|
||||
uint64* pt = nextTable(pd, pdIndex, 0, false);
|
||||
if (!pt)
|
||||
return false;
|
||||
if (!(pt[ptIndex] & entryPresent))
|
||||
return false;
|
||||
return (pt[ptIndex] & pageWrite) != 0;
|
||||
}
|
||||
|
||||
uint64 pagingResolve(uint64 virtualAddr)
|
||||
{
|
||||
uint64 pml4Index = (virtualAddr >> 39) & 0x1FF;
|
||||
@@ -95,7 +170,7 @@ uint64 pagingResolve(uint64 virtualAddr)
|
||||
return (pt[ptIndex] & addressMask) + (virtualAddr & 0xFFF);
|
||||
}
|
||||
|
||||
bool pagingMapUserRange(uint64 virtualAddr, uint64 size)
|
||||
bool pagingMapUserRangeExact(uint64 virtualAddr, uint64 size, uint64 flags)
|
||||
{
|
||||
uint64 first = virtualAddr & ~(uint64)(frameSize - 1);
|
||||
uint64 last = (virtualAddr + size + frameSize - 1) & ~(uint64)(frameSize - 1);
|
||||
@@ -106,9 +181,92 @@ bool pagingMapUserRange(uint64 virtualAddr, uint64 size)
|
||||
uint64 frame = pmmAlloc();
|
||||
if (frame == 0)
|
||||
return false;
|
||||
if (!pagingMap(page, frame, pageUser | pageWrite))
|
||||
if (!pagingMap(page, frame, pageUser | flags))
|
||||
return false;
|
||||
memset((void*)page, 0, frameSize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pagingMapUserRangeFlags(uint64 virtualAddr, uint64 size, uint64 extraFlags)
|
||||
{
|
||||
return pagingMapUserRangeExact(virtualAddr, size, pageWrite | extraFlags);
|
||||
}
|
||||
|
||||
bool pagingMapUserRange(uint64 virtualAddr, uint64 size)
|
||||
{
|
||||
return pagingMapUserRangeFlags(virtualAddr, size, 0);
|
||||
}
|
||||
|
||||
/* True if every one of a table's 512 entries is not-present -- used to
|
||||
decide whether a page-table page that unmapping just emptied out can be
|
||||
given back to the pmm itself, not just the leaf data frame it pointed
|
||||
to. Must scan the whole table, not just the entry this call touched: a
|
||||
PD table in particular can be shared by several unrelated elfSlotSize
|
||||
slots at once (a PDPT entry spans 1GB, four times a 256 MiB slot), so
|
||||
only a table that is genuinely empty end-to-end is safe to free. */
|
||||
static bool tableEmpty(const uint64* table)
|
||||
{
|
||||
for (uint64 i = 0; i < 512; i++)
|
||||
if (table[i] & entryPresent)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void unmapOnePage(uint64 virtualAddr)
|
||||
{
|
||||
uint64 pml4Index = (virtualAddr >> 39) & 0x1FF;
|
||||
uint64 pdptIndex = (virtualAddr >> 30) & 0x1FF;
|
||||
uint64 pdIndex = (virtualAddr >> 21) & 0x1FF;
|
||||
uint64 ptIndex = (virtualAddr >> 12) & 0x1FF;
|
||||
|
||||
uint64* pml4 = tableFrom(readCr3());
|
||||
uint64* pdpt = nextTable(pml4, pml4Index, 0, false);
|
||||
if (!pdpt)
|
||||
return;
|
||||
uint64* pd = nextTable(pdpt, pdptIndex, 0, false);
|
||||
if (!pd)
|
||||
return;
|
||||
if (pd[pdIndex] & entryPageSize)
|
||||
return;
|
||||
|
||||
uint64* pt = nextTable(pd, pdIndex, 0, false);
|
||||
if (!pt)
|
||||
return;
|
||||
if (!(pt[ptIndex] & entryPresent))
|
||||
return;
|
||||
|
||||
pmmFree(pt[ptIndex] & addressMask);
|
||||
pt[ptIndex] = 0;
|
||||
invalidatePage(virtualAddr);
|
||||
|
||||
/* Bottom-up structural cleanup: a PT/PD page-table page is itself just
|
||||
a pmm frame (see nextTable's create path), and every page
|
||||
pagingMapUserRange ever maps allocates one on demand -- leaving them
|
||||
behind once the last leaf entry they held is gone would be a second,
|
||||
smaller leak underneath the one this function exists to fix.
|
||||
Deliberately stops at PD: reclaiming a PDPT or the PML4 itself would
|
||||
mean tearing down a structure shared by up to 512GB / 1GB of
|
||||
unrelated address space respectively, which nothing in this kernel's
|
||||
single shared page table can ever actually make fully empty (the
|
||||
kernel's own mappings always live somewhere under the same PML4
|
||||
entry as every user slot). */
|
||||
if (tableEmpty(pt)) {
|
||||
pmmFree((uint64)pt);
|
||||
pd[pdIndex] = 0;
|
||||
|
||||
if (tableEmpty(pd)) {
|
||||
pmmFree((uint64)pd);
|
||||
pdpt[pdptIndex] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pagingUnmapUserRange(uint64 virtualAddr, uint64 size)
|
||||
{
|
||||
uint64 first = virtualAddr & ~(uint64)(frameSize - 1);
|
||||
uint64 last = (virtualAddr + size + frameSize - 1) & ~(uint64)(frameSize - 1);
|
||||
|
||||
for (uint64 page = first; page < last; page += frameSize)
|
||||
unmapOnePage(page);
|
||||
}
|
||||
Reference in new issue
Block a user