From dcc026217fdc363f55c217039fc43d344f69fed6 Mon Sep 17 00:00:00 2001 From: Jian J Wang Date: Mon, 20 Aug 2018 11:31:00 +0800 Subject: [PATCH] UefiCpuPkg/CpuDxe: implement non-stop mode for uefi Same as SMM profile feature, a special #PF is used to set page attribute to 'present' and a special #DB handler to reset it back to 'not-present', right after the instruction causing #PF got executed. Since the new #PF handler won't enter into dead-loop, the instruction which caused the #PF will get chance to re-execute with accessible pages. The exception message will still be printed out on debug console so that the developer/QA can find that there's potential heap overflow or null pointer access occurred. Cc: Eric Dong Cc: Laszlo Ersek Cc: Ruiyu Ni Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: Jian J Wang Reviewed-by: Eric Dong Acked-by: Laszlo Ersek --- UefiCpuPkg/CpuDxe/CpuDxe.h | 39 ++++++++ UefiCpuPkg/CpuDxe/CpuDxe.inf | 3 + UefiCpuPkg/CpuDxe/CpuMp.c | 34 +++++-- UefiCpuPkg/CpuDxe/CpuPageTable.c | 167 +++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 6 deletions(-) diff --git a/UefiCpuPkg/CpuDxe/CpuDxe.h b/UefiCpuPkg/CpuDxe/CpuDxe.h index 540f5f2dbf..7d65e39e90 100644 --- a/UefiCpuPkg/CpuDxe/CpuDxe.h +++ b/UefiCpuPkg/CpuDxe/CpuDxe.h @@ -57,6 +57,12 @@ EFI_MEMORY_RO \ ) +#define HEAP_GUARD_NONSTOP_MODE \ + ((PcdGet8 (PcdHeapGuardPropertyMask) & (BIT6|BIT1|BIT0)) > BIT6) + +#define NULL_DETECTION_NONSTOP_MODE \ + ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & (BIT6|BIT0)) > BIT6) + /** Flush CPU data cache. If the instruction cache is fully coherent with all DMA operations then function can just return EFI_SUCCESS. @@ -273,7 +279,40 @@ RefreshGcdMemoryAttributesFromPaging ( VOID ); +/** + Special handler for #DB exception, which will restore the page attributes + (not-present). It should work with #PF handler which will set pages to + 'present'. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. + +**/ +VOID +EFIAPI +DebugExceptionHandler ( + IN EFI_EXCEPTION_TYPE InterruptType, + IN EFI_SYSTEM_CONTEXT SystemContext + ); + +/** + Special handler for #PF exception, which will set the pages which caused + #PF to be 'present'. The attribute of those pages should be restored in + the subsequent #DB handler. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. + +**/ +VOID +EFIAPI +PageFaultExceptionHandler ( + IN EFI_EXCEPTION_TYPE InterruptType, + IN EFI_SYSTEM_CONTEXT SystemContext + ); + extern BOOLEAN mIsAllocatingPageTable; +extern UINTN mNumberOfProcessors; #endif diff --git a/UefiCpuPkg/CpuDxe/CpuDxe.inf b/UefiCpuPkg/CpuDxe/CpuDxe.inf index 6a199b72f7..97a381b046 100644 --- a/UefiCpuPkg/CpuDxe/CpuDxe.inf +++ b/UefiCpuPkg/CpuDxe/CpuDxe.inf @@ -46,6 +46,7 @@ ReportStatusCodeLib MpInitLib TimerLib + PeCoffGetEntryPointLib [Sources] CpuDxe.c @@ -79,6 +80,8 @@ [Pcd] gEfiMdeModulePkgTokenSpaceGuid.PcdPteMemoryEncryptionAddressOrMask ## CONSUMES gEfiMdeModulePkgTokenSpaceGuid.PcdCpuStackGuard ## CONSUMES + gEfiMdeModulePkgTokenSpaceGuid.PcdHeapGuardPropertyMask ## CONSUMES + gEfiMdeModulePkgTokenSpaceGuid.PcdNullPointerDetectionPropertyMask ## CONSUMES gUefiCpuPkgTokenSpaceGuid.PcdCpuStackSwitchExceptionList ## CONSUMES gUefiCpuPkgTokenSpaceGuid.PcdCpuKnownGoodStackSize ## CONSUMES diff --git a/UefiCpuPkg/CpuDxe/CpuMp.c b/UefiCpuPkg/CpuDxe/CpuMp.c index 82145e7624..5b3c87d244 100644 --- a/UefiCpuPkg/CpuDxe/CpuMp.c +++ b/UefiCpuPkg/CpuDxe/CpuMp.c @@ -673,10 +673,6 @@ InitializeMpExceptionStackSwitchHandlers ( UINT8 *GdtBuffer; UINT8 *StackTop; - if (!PcdGetBool (PcdCpuStackGuard)) { - return; - } - ExceptionNumber = FixedPcdGetSize (PcdCpuStackSwitchExceptionList); NewStackSize = FixedPcdGet32 (PcdCpuKnownGoodStackSize) * ExceptionNumber; @@ -790,6 +786,32 @@ InitializeMpExceptionStackSwitchHandlers ( } } +/** + Initializes MP exceptions handlers for special features, such as Heap Guard + and Stack Guard. +**/ +VOID +InitializeMpExceptionHandlers ( + VOID + ) +{ + // + // Enable non-stop mode for #PF triggered by Heap Guard or NULL Pointer + // Detection. + // + if (HEAP_GUARD_NONSTOP_MODE || NULL_DETECTION_NONSTOP_MODE) { + RegisterCpuInterruptHandler (EXCEPT_IA32_DEBUG, DebugExceptionHandler); + RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, PageFaultExceptionHandler); + } + + // + // Setup stack switch for Stack Guard feature. + // + if (PcdGetBool (PcdCpuStackGuard)) { + InitializeMpExceptionStackSwitchHandlers (); + } +} + /** Initialize Multi-processor support. @@ -814,9 +836,9 @@ InitializeMpSupport ( DEBUG ((DEBUG_INFO, "Detect CPU count: %d\n", mNumberOfProcessors)); // - // Initialize exception stack switch handlers for each logic processor. + // Initialize special exception handlers for each logic processor. // - InitializeMpExceptionStackSwitchHandlers (); + InitializeMpExceptionHandlers (); // // Update CPU healthy information from Guided HOB diff --git a/UefiCpuPkg/CpuDxe/CpuPageTable.c b/UefiCpuPkg/CpuDxe/CpuPageTable.c index df021798c0..97257fbe48 100644 --- a/UefiCpuPkg/CpuDxe/CpuPageTable.c +++ b/UefiCpuPkg/CpuDxe/CpuPageTable.c @@ -22,6 +22,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -73,6 +77,10 @@ #define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull #define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull +#define MAX_PF_ENTRY_COUNT 10 +#define MAX_DEBUG_MESSAGE_LENGTH 0x100 +#define IA32_PF_EC_ID BIT4 + typedef enum { PageNone, Page4K, @@ -102,6 +110,12 @@ PAGE_TABLE_POOL *mPageTablePool = NULL; PAGE_TABLE_LIB_PAGING_CONTEXT mPagingContext; EFI_SMM_BASE2_PROTOCOL *mSmmBase2 = NULL; +// +// Record the page fault exception count for one instruction execution. +// +UINTN *mPFEntryCount; +UINT64 *(*mLastPFEntryPointer)[MAX_PF_ENTRY_COUNT]; + /** Check if current execution environment is in SMM mode or not, via EFI_SMM_BASE2_PROTOCOL. @@ -1135,6 +1149,150 @@ AllocatePageTableMemory ( return Buffer; } +/** + Special handler for #DB exception, which will restore the page attributes + (not-present). It should work with #PF handler which will set pages to + 'present'. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. + +**/ +VOID +EFIAPI +DebugExceptionHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ) +{ + UINTN CpuIndex; + UINTN PFEntry; + BOOLEAN IsWpEnabled; + + MpInitLibWhoAmI (&CpuIndex); + + // + // Clear last PF entries + // + IsWpEnabled = IsReadOnlyPageWriteProtected (); + if (IsWpEnabled) { + DisableReadOnlyPageWriteProtect (); + } + + for (PFEntry = 0; PFEntry < mPFEntryCount[CpuIndex]; PFEntry++) { + if (mLastPFEntryPointer[CpuIndex][PFEntry] != NULL) { + *mLastPFEntryPointer[CpuIndex][PFEntry] &= ~IA32_PG_P; + } + } + + if (IsWpEnabled) { + EnableReadOnlyPageWriteProtect (); + } + + // + // Reset page fault exception count for next page fault. + // + mPFEntryCount[CpuIndex] = 0; + + // + // Flush TLB + // + CpuFlushTlb (); + + // + // Clear TF in EFLAGS + // + if (mPagingContext.MachineType == IMAGE_FILE_MACHINE_I386) { + SystemContext.SystemContextIa32->Eflags &= (UINT32)~BIT8; + } else { + SystemContext.SystemContextX64->Rflags &= (UINT64)~BIT8; + } +} + +/** + Special handler for #PF exception, which will set the pages which caused + #PF to be 'present'. The attribute of those pages should be restored in + the subsequent #DB handler. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. + +**/ +VOID +EFIAPI +PageFaultExceptionHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ) +{ + EFI_STATUS Status; + UINT64 PFAddress; + PAGE_TABLE_LIB_PAGING_CONTEXT PagingContext; + PAGE_ATTRIBUTE PageAttribute; + UINT64 Attributes; + UINT64 *PageEntry; + UINTN Index; + UINTN CpuIndex; + UINTN PageNumber; + BOOLEAN NonStopMode; + + PFAddress = AsmReadCr2 () & ~EFI_PAGE_MASK; + if (PFAddress < BASE_4KB) { + NonStopMode = NULL_DETECTION_NONSTOP_MODE ? TRUE : FALSE; + } else { + NonStopMode = HEAP_GUARD_NONSTOP_MODE ? TRUE : FALSE; + } + + if (NonStopMode) { + MpInitLibWhoAmI (&CpuIndex); + GetCurrentPagingContext (&PagingContext); + // + // Memory operation cross page boundary, like "rep mov" instruction, will + // cause infinite loop between this and Debug Trap handler. We have to make + // sure that current page and the page followed are both in PRESENT state. + // + PageNumber = 2; + while (PageNumber > 0) { + PageEntry = GetPageTableEntry (&PagingContext, PFAddress, &PageAttribute); + ASSERT(PageEntry != NULL); + + if (PageEntry != NULL) { + Attributes = GetAttributesFromPageEntry (PageEntry); + if ((Attributes & EFI_MEMORY_RP) != 0) { + Attributes &= ~EFI_MEMORY_RP; + Status = AssignMemoryPageAttributes (&PagingContext, PFAddress, + EFI_PAGE_SIZE, Attributes, NULL); + if (!EFI_ERROR(Status)) { + Index = mPFEntryCount[CpuIndex]; + // + // Re-retrieve page entry because above calling might update page + // table due to table split. + // + PageEntry = GetPageTableEntry (&PagingContext, PFAddress, &PageAttribute); + mLastPFEntryPointer[CpuIndex][Index++] = PageEntry; + mPFEntryCount[CpuIndex] = Index; + } + } + } + + PFAddress += EFI_PAGE_SIZE; + --PageNumber; + } + } + + // + // Initialize the serial port before dumping. + // + SerialPortInitialize (); + // + // Display ExceptionType, CPU information and Image information + // + DumpCpuContext (ExceptionType, SystemContext); + if (!NonStopMode) { + CpuDeadLoop (); + } +} + /** Initialize the Page Table lib. **/ @@ -1158,6 +1316,15 @@ InitializePageTableLib ( EnableReadOnlyPageWriteProtect (); } + if (HEAP_GUARD_NONSTOP_MODE || NULL_DETECTION_NONSTOP_MODE) { + mPFEntryCount = (UINTN *)AllocateZeroPool (sizeof (UINTN) * mNumberOfProcessors); + ASSERT (mPFEntryCount != NULL); + + mLastPFEntryPointer = (UINT64 *(*)[MAX_PF_ENTRY_COUNT]) + AllocateZeroPool (sizeof (mLastPFEntryPointer[0]) * mNumberOfProcessors); + ASSERT (mLastPFEntryPointer != NULL); + } + DEBUG ((DEBUG_INFO, "CurrentPagingContext:\n", CurrentPagingContext.MachineType)); DEBUG ((DEBUG_INFO, " MachineType - 0x%x\n", CurrentPagingContext.MachineType)); DEBUG ((DEBUG_INFO, " PageTableBase - 0x%x\n", CurrentPagingContext.ContextData.X64.PageTableBase));