On 06/01/16 14:41, Paolo Bonzini wrote:
>
>
> On 01/06/2016 14:26, Laszlo Ersek wrote:
>> On 06/01/16 13:30, Paolo Bonzini wrote:
>>>
>>>
>>> On 01/06/2016 12:50, Laszlo Ersek wrote:
>>>> In other words, the fault is raised and delivered (== the handler
>>>> is entered) entirely within non-root operation, inside the VM. I
>>>> find this amazing. (Amazingly annoying.)
>>>
>>> It is indeed.  You can try using ept=0 to see the page faults.
>>
>> Given that it reproduces with TCG, I added the following option to
>> the QEMU command line:
>>
>>   -d int,pcall
>>
>> It absolutely spams the output, but the last two state dumps
>> preceding the fault are:
>>
>> RIP (7feee48d) and CR2 (ffe00020) are consistent with the guest's own
>> report:
>>
>>> ~SmiPFHandler: PFAddress=0xFFE00020 Rip=0x7FEEE48D
>>> PageTable = 7FFA7000, PTIndex = 1FF, PageTable[PTIndex] = 7FFF31BD
>>> New page table overlapped with old page table!
>>> ASSERT .../UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c(618): ((BOOLEAN)(0==1))
>>
>> Is this useful information?
>
> Yes, it is.  If the PT address is constant, perhaps you can attach gdb
> to QEMU and set a hardware watchpoint on the PTE?

Thank you. This tip allowed me to find the bug. :)

It is a stack overflow in the SMM Core that tramples the top-most PTE in
the SMM page table (the one that covers the last 2MB of the 4GB address
space, that is, where the pflash chips are mapped).

The haphazard behavior I've been seeing with my debug instrumentation
must have been due to the unpredictable time and location of the stack
overflow.

The details:

(1) I added some debug code to Gen4GPageTable()
[UefiCpuPkg/PiSmmCpuDxeSmm/MpService.c] for dumping the initially
generated page tables. They looked all good.

(2) I appended the "-s" parameter (= start builtin GDB server on TCP
port 1234) to the QEMU command line. It's important to use TCG
acceleration for this; with KVM the watchpoints are not reliable.

(3) Once I was standing at the UEFI shell with QEMU, I connected to QEMU
with gdb (the client). I tried the symbolic debugging stuff we sometimes
use otherwise
<https://edk2.bluestop.org/w/tianocore/debugging-with-gdb/>, but it has
no support for SMM drivers, so it wasn't helpful.

Nonetheless, I calculated the address of the PTE that was always being
corrupted. It was 0x7FFA7FF8 -- the very last entry in the page table
area that Gen4GPageTable() allocates and pre-fills.

Then, I set a (write-only) watchpoint on it, as you suggested:

> (gdb) watch *(UINT64*)0x7FFA7FF8
> Hardware watchpoint 1: *(UINT64*)0x7FFA7FF8
> (gdb) cont
> Continuing.

(4) I ran EnrollDefaultKeys.efi from the UEFI shell, in the guest. The
watchpoint fired in gdb:

> Hardware watchpoint 1: *(UINT64*)0x7FFA7FF8
>
> Old value = 18446744073709551615
> New value = 2147430300
> 0x000000007fff38fd in ?? ()

and I collected the following information:

* stack dump:

> (gdb) where
> #0  0x000000007fff38fd in ?? ()
> #1  0x000000007fff2f9c in ?? ()
> #2  0x0000000000000000 in ?? ()

* registers:

> (gdb) info registers
> rax            0x7fff38fd       2147432701
> rbx            0x1fc4   8132
> rcx            0x7fe77010       2145873936
> rdx            0x7fe77010       2145873936
> rsi            0x7ffa8140       2147123520
> rdi            0x4      4
> rbp            0x7ffa8030       0x7ffa8030
> rsp            0x7ffa7ff8       0x7ffa7ff8
> r8             0x0      0
> r9             0x7ff69fa0       2146869152
> r10            0xffffffff       4294967295
> r11            0x0      0
> r12            0xc4d    3149
> r13            0x0      0
> r14            0x0      0
> r15            0x0      0
> rip            0x7fff38fd       0x7fff38fd
> eflags         0x6      [ PF ]
> cs             0x38     56
> ss             0x20     32
> ds             0x20     32
> es             0x20     32
> fs             0x20     32
> gs             0x20     32

* Some disassembly at RIP:

> (gdb) disassemble /r 0x07fff38fd,+20
> Dump of assembler code from 0x7fff38fd to 0x7fff3911:
> => 0x000000007fff38fd:  55      push   %rbp
>    0x000000007fff38fe:  48 89 e5        mov    %rsp,%rbp
>    0x000000007fff3901:  48 b8 88 9d ff 7f 00 00 00 00   movabs 
> $0x7fff9d88,%rax
>    0x000000007fff390b:  0f b6 00        movzbl (%rax),%eax
>    0x000000007fff390e:  0f b6 c0        movzbl %al,%eax
> End of assembler dump.

Now, on x86_64 the stack grows down. We can see that RSP is 7ffa7ff8,
that is, the address of the PTE that gets corrupted.

The value being pushed into it (as if onto the stack) is supposed to be
RBP (0x7ffa8030). We're not seeing that in the watchpoint's "New value"
-- I'm unsure why --, but what we *are* seeing as New value makes sense
too: 2147430300 decimal is 0x7fff2f9c, which is exactly what the "where"
command above listed for frame #1!

So, it seems to me that we are just after a function call. Frame #1 has
been pushed onto the "stack", corrupting the topmost PTE at 0x7FFA7FF8,
and we are now in frame #0, at 0x7fff38fd.

(5) Here I got stuck for quite some time, but after staring at the OVMF
debug log, I found the following snippet (I was looking for base
addresses slightly below the values visible on the "stack"):

> Loading driver at 0x0007E5DC000 EntryPoint=0x0007E5DD04F PiSmmIpl.efi
> InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7EB64018
> SMM IPL opened SMRAM window
> SMM IPL found SMRAM window 7F801000 - 7FFFFFFF
> SMM IPL loading SMM Core at SMRAM address 7FFE7000
> SMM IPL calling SMM Core at SMRAM address 7FFE728F
> PiSmmCoreImageBase - 0x000000007FFE7000
> PiSmmCoreImageSize - 0x0000000000018D60
> mSmmMemLibInternalMaximumSupportAddress = 0xFFFFFFFFF

Aha!

#0  0x000000007fff38fd - PiSmmCoreImageBase [0x7FFE7000] = 0xc8fd
#1  0x000000007fff2f9c - PiSmmCoreImageBase [0x7FFE7000] = 0xbf9c

Equipped with these offsets into the SMM Core, enter "objdump":

> objdump -S --all-headers \
>   
> Build/Ovmf3264/DEBUG_GCC48/X64/MdeModulePkg/Core/PiSmmCore/PiSmmCore/DEBUG/PiSmmCore.debug

First, let's verify:

> start address 0x000000000000028f

This matches 7FFE728F-7FFE7000 from the OVMF debug log (SMM Core entry
point minus image base), okay.

Second, lo and behold those offsets in the disassembly:

* Frame #0, offset c8fd:

> 000000000000c8fd <DebugAssertEnabled>:
> BOOLEAN
> EFIAPI
> DebugAssertEnabled (
>   VOID
>   )
> {
>     c8fd:       55                      push   %rbp
>     c8fe:       48 89 e5                mov    %rsp,%rbp
>   return (BOOLEAN) ((PcdGet8(PcdDebugPropertyMask) & 
> DEBUG_PROPERTY_DEBUG_ASSERT_ENABLED) != 0);
>     c901:       48 b8 88 2d 01 00 00    movabs $0x12d88,%rax
>     c908:       00 00 00
>                         c903: R_X86_64_64       
> _gPcd_FixedAtBuild_PcdDebugPropertyMask
>     c90b:       0f b6 00                movzbl (%rax),%eax
>     c90e:       0f b6 c0                movzbl %al,%eax

This is identical to out disassembly in step (1), modulo a relocation!

* Frame #1, offset bf9c:

> 000000000000bf7a <InternalBaseLibIsNodeInList>:
> InternalBaseLibIsNodeInList (
>   IN CONST LIST_ENTRY  *List,
>   IN CONST LIST_ENTRY  *Node,
>   IN BOOLEAN           VerifyNodeInList
>   )
> {
>     bf7a:       55                      push   %rbp
>     bf7b:       48 89 e5                mov    %rsp,%rbp
>     bf7e:       48 83 ec 30             sub    $0x30,%rsp
>     bf82:       48 89 4d 10             mov    %rcx,0x10(%rbp)
>     bf86:       48 89 55 18             mov    %rdx,0x18(%rbp)
>     bf8a:       44 89 c0                mov    %r8d,%eax
>     bf8d:       88 45 20                mov    %al,0x20(%rbp)
>   CONST LIST_ENTRY  *Ptr;
>
>   //
>   // Test the validity of List and Node
>   //
>   ASSERT (List != NULL);
>     bf90:       48 b8 fd c8 00 00 00    movabs $0xc8fd,%rax
>     bf97:       00 00 00
>                         bf92: R_X86_64_64       DebugAssertEnabled
>     bf9a:       ff d0                   callq  *%rax
>     bf9c:       84 c0                   test   %al,%al

So, some list-handling BaseLib code got executed as part of the SMM
Core, and at some point an ASSERT() -> DebugAssertEnabled() function
call caused the SMM stack to overflow.

(6) Given that I had been staring at SmiPFHandler() for a day or more
now [UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c]:

>   //
>   // If a page fault occurs in SMRAM range, it should be in a SMM stack guard 
> page.
>   //
>   if ((FeaturePcdGet (PcdCpuSmmStackGuard)) &&
>       (PFAddress >= mCpuHotPlugData.SmrrBase) &&
>       (PFAddress < (mCpuHotPlugData.SmrrBase + mCpuHotPlugData.SmrrSize))) {
>     DEBUG ((EFI_D_ERROR, "SMM stack overflow!\n"));
>     CpuDeadLoop ();
>   }

I immediately checked the declaration and default value of
"PcdCpuSmmStackGuard" in "UefiCpuPkg.dec":

>   ## Indicates if SMM Stack Guard will be enabled.
>   #  If enabled, stack overflow in SMM can be caught which eases 
> debugging.<BR><BR>
>   #   TRUE  - SMM Stack Guard will be enabled.<BR>
>   #   FALSE - SMM Stack Guard will be disabled.<BR>
>   # @Prompt Enable SMM Stack Guard.
>   gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmStackGuard|FALSE|BOOLEAN|0x1000001C

Are you *kidding* me? "Eases debugging"? Why on earth does this
FeaturePCD not default to TRUE?

Okay, okay, let's see the SMM stack size -- there must be such a
tuneable, right? Certainly:

>   ## Specifies stack size in bytes for each processor in SMM.
>   # @Prompt Processor stack size in SMM.
>   gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmStackSize|0x2000|UINT32|0x32132105

A measly 8KB. :(

(7) At this point I changed PcdCpuSmmStackGuard to TRUE in the
"OvmfPkg/OvmfPkgIa32X64.dsc" file, rebuilt OVMF, and re-ran the test.
Sure enough, the SMM fault handler logged the following, under both TCG
and KVM:

> SMM stack overflow!

Then I doubled "PcdCpuSmmStackSize" too, in
"OvmfPkg/OvmfPkgIa32X64.dsc", and now everything works fine. :)


I will submit a patch for UefiCpuPkg.dec, to change the default of
PcdCpuSmmStackGuard to TRUE. Such critical safe-guards must be opt-out,
not opt-in, for platforms! (Note that when it fires, it calls
CpuDeadLoop(), which is never compiled out.)

I will also submit a patch for the OVMF DSC files, setting
PcdCpuSmmStackSize to 0x4000 (16KB).

Thanks
Laszlo
_______________________________________________
edk2-devel mailing list
[email protected]
https://lists.01.org/mailman/listinfo/edk2-devel

Reply via email to