aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2022-02-02 03:26:20 +0000
committerMichael Brown <mcb30@ipxe.org>2022-02-02 13:34:50 +0000
commitbc35b24e3ebd2996b2484b7f9ceb96a3cf25823a (patch)
tree2ee74e8692e5a8bf88909e350bbdb70c056ee055
parent6ba671acd922ee046b257c5119b8a0f64d275473 (diff)
downloadipxe-fix486.zip
ipxe-fix486.tar.gz
ipxe-fix486.tar.bz2
[prefix] Fix use of writable code segment on 486 and earlier CPUsfix486
In real mode, code segments are always writable. In protected mode, code segments can never be writable. The precise implementation of this attribute differs between CPU generations, with subtly different behaviour arising on the transitions from protected mode to real mode. At the point of transition (when the PE bit is cleared in CR0) the hidden portion of the %cs descriptor will retain whatever attributes were in place for the protected-mode code segment, including the fact that the segment is not writable. The immediately following code will perform a far control flow transfer (such as ljmp or lret) in order to load a real-mode value into %cs. On the Pentium and later CPUs, the retained protected-mode attributes will be ignored for any accesses via %cs while the CPU is in real mode. A write via %cs will therefore be allowed even though the hidden portion of the %cs descriptor still describes a non-writable segment. On the 486 and earlier CPUs, the retained protected-mode attributes will not be ignored for accesses via %cs. A write via %cs will therefore cause a CPU fault. To obtain normal real-mode behaviour (i.e. a writable %cs descriptor), special logic is added to the ljmp instruction that populates the hidden portion of the %cs descriptor with real-mode attributes when a far jump is executed in real mode. The result is that writes via %cs will cause a CPU fault until the first ljmp instruction is executed, after which writes via %cs will be allowed as expected in real mode. The transition code in libprefix.S currently uses lret to load a real-mode value into %cs after clearing the PE bit. Experimentation shows that only the ljmp instruction will work to load real-mode attributes into the hidden portion of the %cs descriptor: other far control flow transfers (such as lret, lcall, or int) do not do so. When running on a 486 or earlier CPU, this results in code within libprefix.S running with a non-writable code segment after a mode transition, which in turn results in a CPU fault when real-mode code in liba20.S attempts to write to %cs:enable_a20_method. Fix by constructing and executing an ljmp instruction, to trigger the relevant descriptor population logic on 486 and earlier CPUs. This ljmp instruction is constructed on the stack, since the .prefix section may be executing directly from ROM (or from memory that the BIOS has write-protected in order to emulate an ISA ROM region) and so cannot be modified. Reported-by: Nikolai Zhubr <n-a-zhubr@yandex.ru> Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/arch/x86/prefix/libprefix.S14
1 files changed, 10 insertions, 4 deletions
diff --git a/src/arch/x86/prefix/libprefix.S b/src/arch/x86/prefix/libprefix.S
index ffb2110..d7f2619 100644
--- a/src/arch/x86/prefix/libprefix.S
+++ b/src/arch/x86/prefix/libprefix.S
@@ -380,6 +380,11 @@ process_bytes:
pushl %eax
pushl %ebp
+ /* Construct ljmp code on stack (since .prefix may not be writable) */
+ .equ LJMP_LEN, 0x06
+ pushw %cs /* "nop ; ljmp %cs, $2f" */
+ pushw $2f
+ pushw $0xea90
/* Construct GDT on stack (since .prefix may not be writable) */
.equ GDT_LEN, 0x20
.equ PM_DS, 0x18 /* Flat data segment */
@@ -410,8 +415,9 @@ process_bytes:
pushw %es
pushw %ds
pushw %ss
- pushw %cs
- pushw $2f
+ pushw %ss /* Far pointer to ljmp code on stack */
+ leaw (GDT_LEN + 1)(%bp), %ax
+ pushw %ax
cli
data32 lgdt (%bp)
movl %cr0, %eax
@@ -438,7 +444,7 @@ process_bytes:
popfw
movl %eax, %cr0
lret
-2: /* lret will ljmp to here */
+2: /* lret will ljmp to here (via constructed ljmp on stack) */
popw %ss
popw %ds
popw %es
@@ -461,7 +467,7 @@ process_bytes:
/* Restore GDT */
data32 lgdt -8(%bp)
- leaw GDT_LEN(%bp), %sp
+ leaw (GDT_LEN + LJMP_LEN)(%bp), %sp
/* Restore registers and return */
popl %ebp