/** @file Page table manipulation functions for IA-32 processors Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.
Copyright (c) 2017, AMD Incorporated. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "PiSmmCpuDxeSmm.h" /** Disable CET. **/ VOID EFIAPI DisableCet ( VOID ); /** Enable CET. **/ VOID EFIAPI EnableCet ( VOID ); /** Create PageTable for SMM use. @return PageTable Address **/ UINT32 SmmInitPageTable ( VOID ) { UINTN PageFaultHandlerHookAddress; IA32_IDT_GATE_DESCRIPTOR *IdtEntry; EFI_STATUS Status; // // Initialize spin lock // InitializeSpinLock (mPFLock); mPhysicalAddressBits = 32; if (FeaturePcdGet (PcdCpuSmmProfileEnable) || HEAP_GUARD_NONSTOP_MODE || NULL_DETECTION_NONSTOP_MODE) { // // Set own Page Fault entry instead of the default one, because SMM Profile // feature depends on IRET instruction to do Single Step // PageFaultHandlerHookAddress = (UINTN)PageFaultIdtHandlerSmmProfile; IdtEntry = (IA32_IDT_GATE_DESCRIPTOR *) gcSmiIdtr.Base; IdtEntry += EXCEPT_IA32_PAGE_FAULT; IdtEntry->Bits.OffsetLow = (UINT16)PageFaultHandlerHookAddress; IdtEntry->Bits.Reserved_0 = 0; IdtEntry->Bits.GateType = IA32_IDT_GATE_TYPE_INTERRUPT_32; IdtEntry->Bits.OffsetHigh = (UINT16)(PageFaultHandlerHookAddress >> 16); } else { // // Register SMM Page Fault Handler // Status = SmmRegisterExceptionHandler (&mSmmCpuService, EXCEPT_IA32_PAGE_FAULT, SmiPFHandler); ASSERT_EFI_ERROR (Status); } // // Additional SMM IDT initialization for SMM stack guard // if (FeaturePcdGet (PcdCpuSmmStackGuard)) { InitializeIDTSmmStackGuard (); } return Gen4GPageTable (TRUE); } /** Page Fault handler for SMM use. **/ VOID SmiDefaultPFHandler ( VOID ) { CpuDeadLoop (); } /** ThePage Fault handler wrapper for SMM use. @param InterruptType Defines the type of interrupt or exception that occurred on the processor.This parameter is processor architecture specific. @param SystemContext A pointer to the processor context when the interrupt occurred on the processor. **/ VOID EFIAPI SmiPFHandler ( IN EFI_EXCEPTION_TYPE InterruptType, IN EFI_SYSTEM_CONTEXT SystemContext ) { UINTN PFAddress; UINTN GuardPageAddress; UINTN CpuIndex; ASSERT (InterruptType == EXCEPT_IA32_PAGE_FAULT); AcquireSpinLock (mPFLock); PFAddress = AsmReadCr2 (); // // If a page fault occurs in SMRAM range, it might be in a SMM stack guard page, // or SMM page protection violation. // if ((PFAddress >= mCpuHotPlugData.SmrrBase) && (PFAddress < (mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize))) { DumpCpuContext (InterruptType, SystemContext); CpuIndex = GetCpuIndex (); GuardPageAddress = (mSmmStackArrayBase + EFI_PAGE_SIZE + CpuIndex * mSmmStackSize); if ((FeaturePcdGet (PcdCpuSmmStackGuard)) && (PFAddress >= GuardPageAddress) && (PFAddress < (GuardPageAddress + EFI_PAGE_SIZE))) { DEBUG ((DEBUG_ERROR, "SMM stack overflow!\n")); } else { if ((SystemContext.SystemContextIa32->ExceptionData & IA32_PF_EC_ID) != 0) { DEBUG ((DEBUG_ERROR, "SMM exception at execution (0x%x)\n", PFAddress)); DEBUG_CODE ( DumpModuleInfoByIp (*(UINTN *)(UINTN)SystemContext.SystemContextIa32->Esp); ); } else { DEBUG ((DEBUG_ERROR, "SMM exception at access (0x%x)\n", PFAddress)); DEBUG_CODE ( DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextIa32->Eip); ); } if (HEAP_GUARD_NONSTOP_MODE) { GuardPagePFHandler (SystemContext.SystemContextIa32->ExceptionData); goto Exit; } } CpuDeadLoop (); goto Exit; } // // If a page fault occurs in non-SMRAM range. // if ((PFAddress < mCpuHotPlugData.SmrrBase) || (PFAddress >= mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize)) { if ((SystemContext.SystemContextIa32->ExceptionData & IA32_PF_EC_ID) != 0) { DumpCpuContext (InterruptType, SystemContext); DEBUG ((DEBUG_ERROR, "Code executed on IP(0x%x) out of SMM range after SMM is locked!\n", PFAddress)); DEBUG_CODE ( DumpModuleInfoByIp (*(UINTN *)(UINTN)SystemContext.SystemContextIa32->Esp); ); CpuDeadLoop (); goto Exit; } // // If NULL pointer was just accessed // if ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & BIT1) != 0 && (PFAddress < EFI_PAGE_SIZE)) { DumpCpuContext (InterruptType, SystemContext); DEBUG ((DEBUG_ERROR, "!!! NULL pointer access !!!\n")); DEBUG_CODE ( DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextIa32->Eip); ); if (NULL_DETECTION_NONSTOP_MODE) { GuardPagePFHandler (SystemContext.SystemContextIa32->ExceptionData); goto Exit; } CpuDeadLoop (); goto Exit; } if (IsSmmCommBufferForbiddenAddress (PFAddress)) { DumpCpuContext (InterruptType, SystemContext); DEBUG ((DEBUG_ERROR, "Access SMM communication forbidden address (0x%x)!\n", PFAddress)); DEBUG_CODE ( DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextIa32->Eip); ); CpuDeadLoop (); goto Exit; } } if (FeaturePcdGet (PcdCpuSmmProfileEnable)) { SmmProfilePFHandler ( SystemContext.SystemContextIa32->Eip, SystemContext.SystemContextIa32->ExceptionData ); } else { DumpCpuContext (InterruptType, SystemContext); SmiDefaultPFHandler (); } Exit: ReleaseSpinLock (mPFLock); } /** This function sets memory attribute for page table. **/ VOID SetPageTableAttributes ( VOID ) { UINTN Index2; UINTN Index3; UINT64 *L1PageTable; UINT64 *L2PageTable; UINT64 *L3PageTable; BOOLEAN IsSplitted; BOOLEAN PageTableSplitted; BOOLEAN CetEnabled; // // Don't mark page table to read-only if heap guard is enabled. // // BIT2: SMM page guard enabled // BIT3: SMM pool guard enabled // if ((PcdGet8 (PcdHeapGuardPropertyMask) & (BIT3 | BIT2)) != 0) { DEBUG ((DEBUG_INFO, "Don't mark page table to read-only as heap guard is enabled\n")); return ; } // // Don't mark page table to read-only if SMM profile is enabled. // if (FeaturePcdGet (PcdCpuSmmProfileEnable)) { DEBUG ((DEBUG_INFO, "Don't mark page table to read-only as SMM profile is enabled\n")); return ; } DEBUG ((DEBUG_INFO, "SetPageTableAttributes\n")); // // Disable write protection, because we need mark page table to be write protected. // We need *write* page table memory, to mark itself to be *read only*. // CetEnabled = ((AsmReadCr4() & CR4_CET_ENABLE) != 0) ? TRUE : FALSE; if (CetEnabled) { // // CET must be disabled if WP is disabled. // DisableCet(); } AsmWriteCr0 (AsmReadCr0() & ~CR0_WP); do { DEBUG ((DEBUG_INFO, "Start...\n")); PageTableSplitted = FALSE; L3PageTable = (UINT64 *)GetPageTableBase (); SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L3PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted); PageTableSplitted = (PageTableSplitted || IsSplitted); for (Index3 = 0; Index3 < 4; Index3++) { L2PageTable = (UINT64 *)(UINTN)(L3PageTable[Index3] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64); if (L2PageTable == NULL) { continue; } SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L2PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted); PageTableSplitted = (PageTableSplitted || IsSplitted); for (Index2 = 0; Index2 < SIZE_4KB/sizeof(UINT64); Index2++) { if ((L2PageTable[Index2] & IA32_PG_PS) != 0) { // 2M continue; } L1PageTable = (UINT64 *)(UINTN)(L2PageTable[Index2] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64); if (L1PageTable == NULL) { continue; } SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L1PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted); PageTableSplitted = (PageTableSplitted || IsSplitted); } } } while (PageTableSplitted); // // Enable write protection, after page table updated. // AsmWriteCr0 (AsmReadCr0() | CR0_WP); if (CetEnabled) { // // re-enable CET. // EnableCet(); } return ; } /** This function returns with no action for 32 bit. @param[out] *Cr2 Pointer to variable to hold CR2 register value. **/ VOID SaveCr2 ( OUT UINTN *Cr2 ) { return ; } /** This function returns with no action for 32 bit. @param[in] Cr2 Value to write into CR2 register. **/ VOID RestoreCr2 ( IN UINTN Cr2 ) { return ; } /** Return whether access to non-SMRAM is restricted. @retval TRUE Access to non-SMRAM is restricted. @retval FALSE Access to non-SMRAM is not restricted. **/ BOOLEAN IsRestrictedMemoryAccess ( VOID ) { return TRUE; }