| From 76eeedf91c3ceb21b12e94105ce379f6b97e4ca7 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Mon, 22 Nov 2021 16:28:43 +0100 |
| Subject: ARM: 9159/1: decompressor: Avoid UNPREDICTABLE NOP encoding |
| |
| From: Andre Przywara <andre.przywara@arm.com> |
| |
| [ Upstream commit a92882a4d270fbcc021ee6848de5e48b7f0d27f3 ] |
| |
| In the decompressor's head.S we need to start with an instruction that |
| is some kind of NOP, but also mimics as the PE/COFF header, when the |
| kernel is linked as an UEFI application. The clever solution here is |
| "tstne r0, #0x4d000", which in the worst case just clobbers the |
| condition flags, and bears the magic "MZ" signature in the lowest 16 bits. |
| |
| However the encoding used (0x13105a4d) is actually not valid, since bits |
| [15:12] are supposed to be 0 (written as "(0)" in the ARM ARM). |
| Violating this is UNPREDICTABLE, and *can* trigger an UNDEFINED |
| exception. Common Cortex cores seem to ignore those bits, but QEMU |
| chooses to trap, so the code goes fishing because of a missing exception |
| handler at this point. We are just saved by the fact that commonly (with |
| -kernel or when running from U-Boot) the "Z" bit is set, so the |
| instruction is never executed. See [0] for more details. |
| |
| To make things more robust and avoid UNPREDICTABLE behaviour in the |
| kernel code, lets replace this with a "two-instruction NOP": |
| The first instruction is an exclusive OR, the effect of which the second |
| instruction reverts. This does not leave any trace, neither in a |
| register nor in the condition flags. Also it's a perfectly valid |
| encoding. Kudos to Peter Maydell for coming up with this gem. |
| |
| [0] https://lore.kernel.org/qemu-devel/YTPIdbUCmwagL5%2FD@os.inf.tu-dresden.de/T/ |
| |
| Link: https://lore.kernel.org/linux-arm-kernel/20210908162617.104962-1-andre.przywara@arm.com/T/ |
| |
| Fixes: 81a0bc39ea19 ("ARM: add UEFI stub support") |
| Signed-off-by: Andre Przywara <andre.przywara@arm.com> |
| Reported-by: Adam Lackorzynski <adam@l4re.org> |
| Suggested-by: Peter Maydell <peter.maydell@linaro.org> |
| Reviewed-by: Ard Biesheuvel <ardb@kernel.org> |
| Reviewed-by: Linus Walleij <linus.walleij@linaro.org> |
| Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| arch/arm/boot/compressed/efi-header.S | 22 ++++++++++++++-------- |
| arch/arm/boot/compressed/head.S | 3 ++- |
| 2 files changed, 16 insertions(+), 9 deletions(-) |
| |
| diff --git a/arch/arm/boot/compressed/efi-header.S b/arch/arm/boot/compressed/efi-header.S |
| index c0e7a745103e2..230030c130853 100644 |
| --- a/arch/arm/boot/compressed/efi-header.S |
| +++ b/arch/arm/boot/compressed/efi-header.S |
| @@ -9,16 +9,22 @@ |
| #include <linux/sizes.h> |
| |
| .macro __nop |
| -#ifdef CONFIG_EFI_STUB |
| - @ This is almost but not quite a NOP, since it does clobber the |
| - @ condition flags. But it is the best we can do for EFI, since |
| - @ PE/COFF expects the magic string "MZ" at offset 0, while the |
| - @ ARM/Linux boot protocol expects an executable instruction |
| - @ there. |
| - .inst MZ_MAGIC | (0x1310 << 16) @ tstne r0, #0x4d000 |
| -#else |
| AR_CLASS( mov r0, r0 ) |
| M_CLASS( nop.w ) |
| + .endm |
| + |
| + .macro __initial_nops |
| +#ifdef CONFIG_EFI_STUB |
| + @ This is a two-instruction NOP, which happens to bear the |
| + @ PE/COFF signature "MZ" in the first two bytes, so the kernel |
| + @ is accepted as an EFI binary. Booting via the UEFI stub |
| + @ will not execute those instructions, but the ARM/Linux |
| + @ boot protocol does, so we need some NOPs here. |
| + .inst MZ_MAGIC | (0xe225 << 16) @ eor r5, r5, 0x4d000 |
| + eor r5, r5, 0x4d000 @ undo previous insn |
| +#else |
| + __nop |
| + __nop |
| #endif |
| .endm |
| |
| diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S |
| index 247ce90559901..7a38c63b62bf0 100644 |
| --- a/arch/arm/boot/compressed/head.S |
| +++ b/arch/arm/boot/compressed/head.S |
| @@ -190,7 +190,8 @@ start: |
| * were patching the initial instructions of the kernel, i.e |
| * had started to exploit this "patch area". |
| */ |
| - .rept 7 |
| + __initial_nops |
| + .rept 5 |
| __nop |
| .endr |
| #ifndef CONFIG_THUMB2_KERNEL |
| -- |
| 2.34.1 |
| |