ia64/xen-unstable
changeset 12337:8fc5abda5200
[VMXASSIST] Fix mishandling of segment registers on entry into VM86_PROTECTED mode.
The bug occurs when the guest is entering protected mode and reaches
the far jump to set up the 32-bit segment registers for the virtual
VMENTER we're about to perform. vmxassist, in protected_mode(), looks
at the segment registers, which might be 16-bit segments or might be
32-bit segment selectors at this stage (depending on whether the OS
has reloaded them since entering protected mode); and it tries to load
the segments from the GDT. Unconditionally. Even if the segment
register still actually contains a real mode 16-bit segment. Whoops.
vmxassist already has code to detect 16-bit segments that survived
unmodified from a transition into and out of protected mode, and to
save and restore those appropriately. It does this using
"saved_rm_regs", which get cleared on entry to protected mode, and
then set to the old segment value if we fail to set a given 32-bit
segment correctly.
The fix is to save the 16-bit segments *always*, on entry to protected
mode when %CR0(PE) is first set; and to clear the saved 16-bit segment
and set the 32-bit variant in oldctx whenever a 32-bit segment
descriptor is set during the transition to 32-bit CS. Then, when we
finally do the VMENTER, we will set up the VMCS from only the 32-bit
segments, clearing the VMCS entries for segments that have not been
assigned valid 32-bit segments yet.
Tested on various RHEL-5 boot.isos, including ones which worked before
and ones which triggered the bug; all now boot correctly.
Signed-off-by: Stephen Tweedie <sct@redhat.com>
The bug occurs when the guest is entering protected mode and reaches
the far jump to set up the 32-bit segment registers for the virtual
VMENTER we're about to perform. vmxassist, in protected_mode(), looks
at the segment registers, which might be 16-bit segments or might be
32-bit segment selectors at this stage (depending on whether the OS
has reloaded them since entering protected mode); and it tries to load
the segments from the GDT. Unconditionally. Even if the segment
register still actually contains a real mode 16-bit segment. Whoops.
vmxassist already has code to detect 16-bit segments that survived
unmodified from a transition into and out of protected mode, and to
save and restore those appropriately. It does this using
"saved_rm_regs", which get cleared on entry to protected mode, and
then set to the old segment value if we fail to set a given 32-bit
segment correctly.
The fix is to save the 16-bit segments *always*, on entry to protected
mode when %CR0(PE) is first set; and to clear the saved 16-bit segment
and set the 32-bit variant in oldctx whenever a 32-bit segment
descriptor is set during the transition to 32-bit CS. Then, when we
finally do the VMENTER, we will set up the VMCS from only the 32-bit
segments, clearing the VMCS entries for segments that have not been
assigned valid 32-bit segments yet.
Tested on various RHEL-5 boot.isos, including ones which worked before
and ones which triggered the bug; all now boot correctly.
Signed-off-by: Stephen Tweedie <sct@redhat.com>
author | kfraser@localhost.localdomain |
---|---|
date | Thu Nov 09 14:00:40 2006 +0000 (2006-11-09) |
parents | d173b32f54ab |
children | f54e29a3be1f |
files | tools/firmware/vmxassist/vm86.c |
line diff
1.1 --- a/tools/firmware/vmxassist/vm86.c Thu Nov 09 13:43:21 2006 +0000 1.2 +++ b/tools/firmware/vmxassist/vm86.c Thu Nov 09 14:00:40 2006 +0000 1.3 @@ -867,6 +867,18 @@ load_seg(unsigned long sel, uint32_t *ba 1.4 } 1.5 1.6 /* 1.7 + * Emulate a protected mode segment load, falling back to clearing it if 1.8 + * the descriptor was invalid. 1.9 + */ 1.10 +static void 1.11 +load_or_clear_seg(unsigned long sel, uint32_t *base, uint32_t *limit, union vmcs_arbytes *arbytes) 1.12 +{ 1.13 + if (!load_seg(sel, base, limit, arbytes)) 1.14 + load_seg(0, base, limit, arbytes); 1.15 +} 1.16 + 1.17 + 1.18 +/* 1.19 * Transition to protected mode 1.20 */ 1.21 static void 1.22 @@ -878,63 +890,22 @@ protected_mode(struct regs *regs) 1.23 oldctx.esp = regs->uesp; 1.24 oldctx.eflags = regs->eflags; 1.25 1.26 - memset(&saved_rm_regs, 0, sizeof(struct regs)); 1.27 - 1.28 /* reload all segment registers */ 1.29 if (!load_seg(regs->cs, &oldctx.cs_base, 1.30 &oldctx.cs_limit, &oldctx.cs_arbytes)) 1.31 panic("Invalid %%cs=0x%x for protected mode\n", regs->cs); 1.32 oldctx.cs_sel = regs->cs; 1.33 1.34 - if (load_seg(regs->ves, &oldctx.es_base, 1.35 - &oldctx.es_limit, &oldctx.es_arbytes)) 1.36 - oldctx.es_sel = regs->ves; 1.37 - else { 1.38 - load_seg(0, &oldctx.es_base, 1.39 - &oldctx.es_limit, &oldctx.es_arbytes); 1.40 - oldctx.es_sel = 0; 1.41 - saved_rm_regs.ves = regs->ves; 1.42 - } 1.43 - 1.44 - if (load_seg(regs->uss, &oldctx.ss_base, 1.45 - &oldctx.ss_limit, &oldctx.ss_arbytes)) 1.46 - oldctx.ss_sel = regs->uss; 1.47 - else { 1.48 - load_seg(0, &oldctx.ss_base, 1.49 - &oldctx.ss_limit, &oldctx.ss_arbytes); 1.50 - oldctx.ss_sel = 0; 1.51 - saved_rm_regs.uss = regs->uss; 1.52 - } 1.53 - 1.54 - if (load_seg(regs->vds, &oldctx.ds_base, 1.55 - &oldctx.ds_limit, &oldctx.ds_arbytes)) 1.56 - oldctx.ds_sel = regs->vds; 1.57 - else { 1.58 - load_seg(0, &oldctx.ds_base, 1.59 - &oldctx.ds_limit, &oldctx.ds_arbytes); 1.60 - oldctx.ds_sel = 0; 1.61 - saved_rm_regs.vds = regs->vds; 1.62 - } 1.63 - 1.64 - if (load_seg(regs->vfs, &oldctx.fs_base, 1.65 - &oldctx.fs_limit, &oldctx.fs_arbytes)) 1.66 - oldctx.fs_sel = regs->vfs; 1.67 - else { 1.68 - load_seg(0, &oldctx.fs_base, 1.69 - &oldctx.fs_limit, &oldctx.fs_arbytes); 1.70 - oldctx.fs_sel = 0; 1.71 - saved_rm_regs.vfs = regs->vfs; 1.72 - } 1.73 - 1.74 - if (load_seg(regs->vgs, &oldctx.gs_base, 1.75 - &oldctx.gs_limit, &oldctx.gs_arbytes)) 1.76 - oldctx.gs_sel = regs->vgs; 1.77 - else { 1.78 - load_seg(0, &oldctx.gs_base, 1.79 - &oldctx.gs_limit, &oldctx.gs_arbytes); 1.80 - oldctx.gs_sel = 0; 1.81 - saved_rm_regs.vgs = regs->vgs; 1.82 - } 1.83 + load_or_clear_seg(oldctx.es_sel, &oldctx.es_base, 1.84 + &oldctx.es_limit, &oldctx.es_arbytes); 1.85 + load_or_clear_seg(oldctx.ss_sel, &oldctx.ss_base, 1.86 + &oldctx.ss_limit, &oldctx.ss_arbytes); 1.87 + load_or_clear_seg(oldctx.ds_sel, &oldctx.ds_base, 1.88 + &oldctx.ds_limit, &oldctx.ds_arbytes); 1.89 + load_or_clear_seg(oldctx.fs_sel, &oldctx.fs_base, 1.90 + &oldctx.fs_limit, &oldctx.fs_arbytes); 1.91 + load_or_clear_seg(oldctx.gs_sel, &oldctx.gs_base, 1.92 + &oldctx.gs_limit, &oldctx.gs_arbytes); 1.93 1.94 /* initialize jump environment to warp back to protected mode */ 1.95 regs->cs = CODE_SELECTOR; 1.96 @@ -1022,6 +993,16 @@ set_mode(struct regs *regs, enum vm86_mo 1.97 case VM86_REAL_TO_PROTECTED: 1.98 if (mode == VM86_REAL) { 1.99 regs->eflags |= EFLAGS_TF; 1.100 + saved_rm_regs.vds = regs->vds; 1.101 + saved_rm_regs.ves = regs->ves; 1.102 + saved_rm_regs.vfs = regs->vfs; 1.103 + saved_rm_regs.vgs = regs->vgs; 1.104 + saved_rm_regs.uss = regs->uss; 1.105 + oldctx.ds_sel = 0; 1.106 + oldctx.es_sel = 0; 1.107 + oldctx.fs_sel = 0; 1.108 + oldctx.gs_sel = 0; 1.109 + oldctx.ss_sel = 0; 1.110 break; 1.111 } else if (mode == VM86_REAL_TO_PROTECTED) { 1.112 break; 1.113 @@ -1282,6 +1263,10 @@ opcode(struct regs *regs) 1.114 else 1.115 regs->ves = pop16(regs); 1.116 TRACE((regs, regs->eip - eip, "pop %%es")); 1.117 + if (mode == VM86_REAL_TO_PROTECTED) { 1.118 + saved_rm_regs.ves = 0; 1.119 + oldctx.es_sel = regs->ves; 1.120 + } 1.121 return OPC_EMULATED; 1.122 1.123 case 0x0F: /* two byte opcode */