1 /** @file
2 Page table manipulation functions for IA-32 processors
3 
4 Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.<BR>
5 Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR>
6 
7 SPDX-License-Identifier: BSD-2-Clause-Patent
8 
9 **/
10 
11 #include "PiSmmCpuDxeSmm.h"
12 
13 /**
14   Disable CET.
15 **/
16 VOID
17 EFIAPI
18 DisableCet (
19   VOID
20   );
21 
22 /**
23   Enable CET.
24 **/
25 VOID
26 EFIAPI
27 EnableCet (
28   VOID
29   );
30 
31 /**
32   Create PageTable for SMM use.
33 
34   @return     PageTable Address
35 
36 **/
37 UINT32
SmmInitPageTable(VOID)38 SmmInitPageTable (
39   VOID
40   )
41 {
42   UINTN                             PageFaultHandlerHookAddress;
43   IA32_IDT_GATE_DESCRIPTOR          *IdtEntry;
44   EFI_STATUS                        Status;
45 
46   //
47   // Initialize spin lock
48   //
49   InitializeSpinLock (mPFLock);
50 
51   mPhysicalAddressBits = 32;
52 
53   if (FeaturePcdGet (PcdCpuSmmProfileEnable) ||
54       HEAP_GUARD_NONSTOP_MODE ||
55       NULL_DETECTION_NONSTOP_MODE) {
56     //
57     // Set own Page Fault entry instead of the default one, because SMM Profile
58     // feature depends on IRET instruction to do Single Step
59     //
60     PageFaultHandlerHookAddress = (UINTN)PageFaultIdtHandlerSmmProfile;
61     IdtEntry  = (IA32_IDT_GATE_DESCRIPTOR *) gcSmiIdtr.Base;
62     IdtEntry += EXCEPT_IA32_PAGE_FAULT;
63     IdtEntry->Bits.OffsetLow      = (UINT16)PageFaultHandlerHookAddress;
64     IdtEntry->Bits.Reserved_0     = 0;
65     IdtEntry->Bits.GateType       = IA32_IDT_GATE_TYPE_INTERRUPT_32;
66     IdtEntry->Bits.OffsetHigh     = (UINT16)(PageFaultHandlerHookAddress >> 16);
67   } else {
68     //
69     // Register SMM Page Fault Handler
70     //
71     Status = SmmRegisterExceptionHandler (&mSmmCpuService, EXCEPT_IA32_PAGE_FAULT, SmiPFHandler);
72     ASSERT_EFI_ERROR (Status);
73   }
74 
75   //
76   // Additional SMM IDT initialization for SMM stack guard
77   //
78   if (FeaturePcdGet (PcdCpuSmmStackGuard)) {
79     InitializeIDTSmmStackGuard ();
80   }
81   return Gen4GPageTable (TRUE);
82 }
83 
84 /**
85   Page Fault handler for SMM use.
86 
87 **/
88 VOID
SmiDefaultPFHandler(VOID)89 SmiDefaultPFHandler (
90   VOID
91   )
92 {
93   CpuDeadLoop ();
94 }
95 
96 /**
97   ThePage Fault handler wrapper for SMM use.
98 
99   @param  InterruptType    Defines the type of interrupt or exception that
100                            occurred on the processor.This parameter is processor architecture specific.
101   @param  SystemContext    A pointer to the processor context when
102                            the interrupt occurred on the processor.
103 **/
104 VOID
105 EFIAPI
SmiPFHandler(IN EFI_EXCEPTION_TYPE InterruptType,IN EFI_SYSTEM_CONTEXT SystemContext)106 SmiPFHandler (
107   IN EFI_EXCEPTION_TYPE   InterruptType,
108   IN EFI_SYSTEM_CONTEXT   SystemContext
109   )
110 {
111   UINTN             PFAddress;
112   UINTN             GuardPageAddress;
113   UINTN             CpuIndex;
114 
115   ASSERT (InterruptType == EXCEPT_IA32_PAGE_FAULT);
116 
117   AcquireSpinLock (mPFLock);
118 
119   PFAddress = AsmReadCr2 ();
120 
121   //
122   // If a page fault occurs in SMRAM range, it might be in a SMM stack guard page,
123   // or SMM page protection violation.
124   //
125   if ((PFAddress >= mCpuHotPlugData.SmrrBase) &&
126       (PFAddress < (mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize))) {
127     DumpCpuContext (InterruptType, SystemContext);
128     CpuIndex = GetCpuIndex ();
129     GuardPageAddress = (mSmmStackArrayBase + EFI_PAGE_SIZE + CpuIndex * mSmmStackSize);
130     if ((FeaturePcdGet (PcdCpuSmmStackGuard)) &&
131         (PFAddress >= GuardPageAddress) &&
132         (PFAddress < (GuardPageAddress + EFI_PAGE_SIZE))) {
133       DEBUG ((DEBUG_ERROR, "SMM stack overflow!\n"));
134     } else {
135       if ((SystemContext.SystemContextIa32->ExceptionData & IA32_PF_EC_ID) != 0) {
136         DEBUG ((DEBUG_ERROR, "SMM exception at execution (0x%x)\n", PFAddress));
137         DEBUG_CODE (
138           DumpModuleInfoByIp (*(UINTN *)(UINTN)SystemContext.SystemContextIa32->Esp);
139         );
140       } else {
141         DEBUG ((DEBUG_ERROR, "SMM exception at access (0x%x)\n", PFAddress));
142         DEBUG_CODE (
143           DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextIa32->Eip);
144         );
145       }
146 
147       if (HEAP_GUARD_NONSTOP_MODE) {
148         GuardPagePFHandler (SystemContext.SystemContextIa32->ExceptionData);
149         goto Exit;
150       }
151     }
152     CpuDeadLoop ();
153     goto Exit;
154   }
155 
156   //
157   // If a page fault occurs in non-SMRAM range.
158   //
159   if ((PFAddress < mCpuHotPlugData.SmrrBase) ||
160       (PFAddress >= mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize)) {
161     if ((SystemContext.SystemContextIa32->ExceptionData & IA32_PF_EC_ID) != 0) {
162       DumpCpuContext (InterruptType, SystemContext);
163       DEBUG ((DEBUG_ERROR, "Code executed on IP(0x%x) out of SMM range after SMM is locked!\n", PFAddress));
164       DEBUG_CODE (
165         DumpModuleInfoByIp (*(UINTN *)(UINTN)SystemContext.SystemContextIa32->Esp);
166       );
167       CpuDeadLoop ();
168       goto Exit;
169     }
170 
171     //
172     // If NULL pointer was just accessed
173     //
174     if ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & BIT1) != 0 &&
175         (PFAddress < EFI_PAGE_SIZE)) {
176       DumpCpuContext (InterruptType, SystemContext);
177       DEBUG ((DEBUG_ERROR, "!!! NULL pointer access !!!\n"));
178       DEBUG_CODE (
179         DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextIa32->Eip);
180       );
181 
182       if (NULL_DETECTION_NONSTOP_MODE) {
183         GuardPagePFHandler (SystemContext.SystemContextIa32->ExceptionData);
184         goto Exit;
185       }
186 
187       CpuDeadLoop ();
188       goto Exit;
189     }
190 
191     if (IsSmmCommBufferForbiddenAddress (PFAddress)) {
192       DumpCpuContext (InterruptType, SystemContext);
193       DEBUG ((DEBUG_ERROR, "Access SMM communication forbidden address (0x%x)!\n", PFAddress));
194       DEBUG_CODE (
195         DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextIa32->Eip);
196       );
197       CpuDeadLoop ();
198       goto Exit;
199     }
200   }
201 
202   if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {
203     SmmProfilePFHandler (
204       SystemContext.SystemContextIa32->Eip,
205       SystemContext.SystemContextIa32->ExceptionData
206       );
207   } else {
208     DumpCpuContext (InterruptType, SystemContext);
209     SmiDefaultPFHandler ();
210   }
211 
212 Exit:
213   ReleaseSpinLock (mPFLock);
214 }
215 
216 /**
217   This function sets memory attribute for page table.
218 **/
219 VOID
SetPageTableAttributes(VOID)220 SetPageTableAttributes (
221   VOID
222   )
223 {
224   UINTN                 Index2;
225   UINTN                 Index3;
226   UINT64                *L1PageTable;
227   UINT64                *L2PageTable;
228   UINT64                *L3PageTable;
229   BOOLEAN               IsSplitted;
230   BOOLEAN               PageTableSplitted;
231   BOOLEAN               CetEnabled;
232 
233   //
234   // Don't mark page table to read-only if heap guard is enabled.
235   //
236   //      BIT2: SMM page guard enabled
237   //      BIT3: SMM pool guard enabled
238   //
239   if ((PcdGet8 (PcdHeapGuardPropertyMask) & (BIT3 | BIT2)) != 0) {
240     DEBUG ((DEBUG_INFO, "Don't mark page table to read-only as heap guard is enabled\n"));
241     return ;
242   }
243 
244   //
245   // Don't mark page table to read-only if SMM profile is enabled.
246   //
247   if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {
248     DEBUG ((DEBUG_INFO, "Don't mark page table to read-only as SMM profile is enabled\n"));
249     return ;
250   }
251 
252   DEBUG ((DEBUG_INFO, "SetPageTableAttributes\n"));
253 
254   //
255   // Disable write protection, because we need mark page table to be write protected.
256   // We need *write* page table memory, to mark itself to be *read only*.
257   //
258   CetEnabled = ((AsmReadCr4() & CR4_CET_ENABLE) != 0) ? TRUE : FALSE;
259   if (CetEnabled) {
260     //
261     // CET must be disabled if WP is disabled.
262     //
263     DisableCet();
264   }
265   AsmWriteCr0 (AsmReadCr0() & ~CR0_WP);
266 
267   do {
268     DEBUG ((DEBUG_INFO, "Start...\n"));
269     PageTableSplitted = FALSE;
270 
271     L3PageTable = (UINT64 *)GetPageTableBase ();
272 
273     SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L3PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
274     PageTableSplitted = (PageTableSplitted || IsSplitted);
275 
276     for (Index3 = 0; Index3 < 4; Index3++) {
277       L2PageTable = (UINT64 *)(UINTN)(L3PageTable[Index3] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64);
278       if (L2PageTable == NULL) {
279         continue;
280       }
281 
282       SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L2PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
283       PageTableSplitted = (PageTableSplitted || IsSplitted);
284 
285       for (Index2 = 0; Index2 < SIZE_4KB/sizeof(UINT64); Index2++) {
286         if ((L2PageTable[Index2] & IA32_PG_PS) != 0) {
287           // 2M
288           continue;
289         }
290         L1PageTable = (UINT64 *)(UINTN)(L2PageTable[Index2] & ~mAddressEncMask & PAGING_4K_ADDRESS_MASK_64);
291         if (L1PageTable == NULL) {
292           continue;
293         }
294         SmmSetMemoryAttributesEx ((EFI_PHYSICAL_ADDRESS)(UINTN)L1PageTable, SIZE_4KB, EFI_MEMORY_RO, &IsSplitted);
295         PageTableSplitted = (PageTableSplitted || IsSplitted);
296       }
297     }
298   } while (PageTableSplitted);
299 
300   //
301   // Enable write protection, after page table updated.
302   //
303   AsmWriteCr0 (AsmReadCr0() | CR0_WP);
304   if (CetEnabled) {
305     //
306     // re-enable CET.
307     //
308     EnableCet();
309   }
310 
311   return ;
312 }
313 
314 /**
315   This function returns with no action for 32 bit.
316 
317   @param[out]  *Cr2  Pointer to variable to hold CR2 register value.
318 **/
319 VOID
SaveCr2(OUT UINTN * Cr2)320 SaveCr2 (
321   OUT UINTN  *Cr2
322   )
323 {
324   return ;
325 }
326 
327 /**
328   This function returns with no action for 32 bit.
329 
330   @param[in]  Cr2  Value to write into CR2 register.
331 **/
332 VOID
RestoreCr2(IN UINTN Cr2)333 RestoreCr2 (
334   IN UINTN  Cr2
335   )
336 {
337   return ;
338 }
339