arm64: simplify restrictions on bootloaders
Subsystems (2)
Mark Rutland
reviewercover arm64: simplify restrictions on bootloaders
Currently bootloaders have an extremely difficult time protecting memory from the kernel, as the kernel may clobber memory below TEXT_OFFSET with pagetables, and above the end of the kernel binary with the BSS.
This series attempts to ameliorate matters by adding a mechanism for bootloaders to discover the minimum runtime footprint of the kernel image, including the BSS and any other dynamically initialised data, and moving the initial page tables into this region.
The currently ill-described image load offset variable is coerced to always be little-endian. This means that bootloader can actually make use of the field for any kernel (wither LE or BE), and as the field does not yet seem to be used anywhere taking endianness into account I hope this is not problematic. Documentation is updated with recommendations on handling the field. To aid in encouraging bootloader authors to respect the field, an option is added to randomize the text_offset field at link time, which may be used in test and/or distribution kernels. So as to not break existing (but arguably broken) loaders immediately, this option is hidden under kernel hacking and disabled by default.
The documentation is updated to cover how to use the new image_size field and what to do if it is zero, and how to use the image_size field to determine whether the text_offset field is guaranteed to be little-endian. The recommended fallback reservation of 1MB is an arbitrary large value; for me _end - _edata was ~190k for a defconfig build on v3.14 with patch 1 applied. I'm happy to increase this to a larger arbitrary value if this seems too small.
A BE conditional 64-bit endianness swapping routine (DATA_LE64) is added to vmlinux.lds.S, as the linker is the only place we can endianness swap a value calulated from two symbols known only at link time. There are several existing headers that do almost the same thing but due to use of C prototypes and/or casts are not suitable for use in a linker script. A separate series may be able to unify that.
This series applies to v3.15-rc5, and is not based on the EFI stub patches. However I believe the field I've chosen to use is available even with the EFI stub patches and shouldn't need to be moved around. I'm happy to rebase as necessary.
I've given some light testing to text_offset fuzzing with an updated bootwrapper [1] which reads the text_offset field at build time to ensure the kernel gets loaded at the right address. Nothing else is yet moved however, so this may explode if this location happens to overlap the bootwrapper code, DTB, or spin-table mbox. I'll try to teach the bootwrapper how to deal with that shortly.
Cheers,
Mark.
[1] http://linux-arm.org/git?p=boot-wrapper-aarch64.git;a=shortlog;h=refs/heads/unstable/text-offset
Mark Rutland (4):
arm64: head.S: remove unnecessary function alignment
arm64: place initial page tables above the kernel
arm64: export effective Image size to bootloaders
arm64: Enable TEXT_OFFSET fuzzing
Documentation/arm64/booting.txt | 28 +++++++++++++++++++----- arch/arm64/Kconfig.debug | 31 +++++++++++++++++++++++++++ arch/arm64/Makefile | 6 +++++- arch/arm64/include/asm/page.h | 9 ++++++++ arch/arm64/kernel/head.S | 47 +++++++++++++++++++---------------------- arch/arm64/kernel/vmlinux.lds.S | 40 +++++++++++++++++++++++++++++++++++ arch/arm64/mm/init.c | 12 ++++------- 7 files changed, 134 insertions(+), 39 deletions(-)
Signature · 1 line
1.9.1
Mark Rutland
reviewerPATCH 1/4 arm64: head.S: remove unnecessary function alignment
Currently __turn_mmu_on is aligned to 64 bytes to ensure that it doesn't span any page boundary, which simplifies the idmap and spares us requiring an additional page table to map half of the function. In keeping with other important requirements in architecture code, this fact is undocumented.
Additionally, as the function consists of three instructions totalling 12 bytes with no literal pool data, a smaller alignment of 16 bytes would be sufficient.
This patch reduces the alignment to 16 bytes and documents the underlying reason for the alignment. This reduces the required alignment of the entire .head.text section from 64 bytes to 16 bytes, though it may still be aligned to a larger value depending on TEXT_OFFSET.
--- arch/arm64/kernel/head.S | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
arch/arm64/kernel/head.S +6 -1
@@ -346,8 +346,13 @@ ENDPROC(__enable_mmu) * x27 = *virtual* address to jump to upon completion * * other registers depend on the function called upon completion + * + * We align the entire function to the smallest power of two larger than it to + * ensure it fits within a single block map entry. Otherwise were PHYS_OFFSET + * close to the end of a 512MB or 1GB block we might require an additional + * table to map the entire function. */ - .align 6 + .align 4 __turn_mmu_on: msr sctlr_el1, x0 isb
Signature · 1 line
1.9.1
PATCH 1/4 arm64: head.S: remove unnecessary function alignment
Hi Mark,
Quoted 29 lines · Mark Rutland
Currently
__turn_mmu_onis aligned to 64 bytes to ensure that it doesn't span any page boundary, which simplifies the idmap and spares us requiring an additional page table to map half of the function. In keeping with other important requirements in architecture code, this fact is undocumented.Additionally, as the function consists of three instructions totalling 12 bytes with no literal pool data, a smaller alignment of 16 bytes would be sufficient.
This patch reduces the alignment to 16 bytes and documents the underlying reason for the alignment. This reduces the required alignment of the entire .head.text section from 64 bytes to 16 bytes, though it may still be aligned to a larger value depending on
TEXT_OFFSET.Signed-off-by Mark Rutland <[email protected]>--- arch/arm64/kernel/head.S | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)arch/arm64/kernel/head.S +1
-0@@ -346,8 +346,13 @@ ENDPROC(__enable_mmu) * x27 = *virtual* address to jump to upon completion * * other registers depend on the function called upon completion + *
arch/arm64/kernel/head.S ENDPROC(__enable_mmu) · line 358
Quoted 10 lines
arch/arm64/kernel/head.S +5
-1+ * We align the entire function to the smallest power of two larger than it to + * ensure it fits within a single block map entry. Otherwise were PHYS_OFFSET + * close to the end of a 512MB or 1GB block we might require an additional + * table to map the entire function. */ - .align 6 + .align 4 __turn_mmu_on: msr sctlr_el1, x0 isb
If you're feeling ambitious, this requirement could probably be enforced by some kind of BUILD_BUG_ON((__turn_mmu_on_end - __turn_mmu_on) / 4 > TURN_MMU_ON_ALIGN). I don't know if this code will really need to grow in practice, but if it does, forgetting to update the alignment (in spite of your helpful comment) seems like an easy mistake to make.
Christopher
Signature · 3 lines
Employee of Qualcomm Innovation Center, Inc. Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by the Linux Foundation.
PATCH 1/4 arm64: head.S: remove unnecessary function alignment
In reply to Mark RutlandQuoted 30 lines · Mark Rutland
Currently
__turn_mmu_onis aligned to 64 bytes to ensure that it doesn't span any page boundary, which simplifies the idmap and spares us requiring an additional page table to map half of the function. In keeping with other important requirements in architecture code, this fact is undocumented.Additionally, as the function consists of three instructions totalling 12 bytes with no literal pool data, a smaller alignment of 16 bytes would be sufficient.
This patch reduces the alignment to 16 bytes and documents the underlying reason for the alignment. This reduces the required alignment of the entire .head.text section from 64 bytes to 16 bytes, though it may still be aligned to a larger value depending on
TEXT_OFFSET.Signed-off-by Mark Rutland <[email protected]>--- arch/arm64/kernel/head.S | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)arch/arm64/kernel/head.S +1
-0@@ -346,8 +346,13 @@ ENDPROC(__enable_mmu) * x27 = *virtual* address to jump to upon completion * * other registers depend on the function called upon completion + *
arch/arm64/kernel/head.S ENDPROC(__enable_mmu) · line 358
Quoted 10 lines
arch/arm64/kernel/head.S +5
-1+ * We align the entire function to the smallest power of two larger than it to + * ensure it fits within a single block map entry. Otherwise were PHYS_OFFSET + * close to the end of a 512MB or 1GB block we might require an additional + * table to map the entire function. */ - .align 6 + .align 4 __turn_mmu_on: msr sctlr_el1, x0 isb
Both 4K and 64K pages
Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
Mark Rutland
reviewerPATCH 2/4 arm64: place initial page tables above the kernel
In reply to Mark RutlandCurrently we place swapper_pg_dir and idmap_pg_dir below the kernel image, between PHYS_OFFSET and (PHYS_OFFSET + TEXT_OFFSET). However, bootloaders may use portions of this memory below the kernel and we do not parse the memory reservation list until after the MMU has been enabled. As such we may clobber some memory a bootloader wishes to have preserved.
To enable the use of all of this memory by bootloaders (when the required memory reservations are communicated to the kernel) it is necessary to move our initial page tables elsewhere. As we currently have an effectively unbound requirement for memory at the end of the kernel image for .bss, we can place the page tables here.
This patch moves the initial page table to the end of the kernel image, after the BSS. As they do not consist of any initialised data they will be stripped from the kernel Image as with the BSS. The BSS clearing routine is updated to stop at __bss_stop rather than _end so as to not clobber the page tables, and memory reservations made redundant by the new organisation are removed.
--- arch/arm64/include/asm/page.h | 9 +++++++++ arch/arm64/kernel/head.S | 28 ++++++++-------------------- arch/arm64/kernel/vmlinux.lds.S | 7 +++++++ arch/arm64/mm/init.c | 12 ++++-------- 4 files changed, 28 insertions(+), 28 deletions(-)
arch/arm64/include/asm/page.h +9 -0
@@ -31,6 +31,15 @@/* We do define AT_SYSINFO_EHDR but don't use the gate mechanism */ #define __HAVE_ARCH_GATE_AREA 1 +/* +* The idmap and swapper page tables need some space reserved in the kernel +* image. The idmap only requires a pgd and a next level table to (section) map +* the kernel, while the swapper also maps the FDT and requires an additional +* table to map an early UART. See __create_page_tables for more information. +*/ +#define SWAPPER_DIR_SIZE (3 * PAGE_SIZE) +#define IDMAP_DIR_SIZE (2 * PAGE_SIZE) +#ifndef __ASSEMBLY__#ifdef CONFIG_ARM64_64K_PAGES
arch/arm64/kernel/head.S +8 -20
@@ -35,29 +35,17 @@ #include <asm/page.h> #include <asm/virt.h> -/* - * swapper_pg_dir is the virtual address of the initial page table. We place - * the page tables 3 * PAGE_SIZE below KERNEL_RAM_VADDR. The idmap_pg_dir has - * 2 pages and is placed below swapper_pg_dir. - */ #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) #if (KERNEL_RAM_VADDR & 0xfffff) != 0x80000 #error KERNEL_RAM_VADDR must start at 0xXXX80000 #endif -#define SWAPPER_DIR_SIZE (3 * PAGE_SIZE) -#define IDMAP_DIR_SIZE (2 * PAGE_SIZE) - - .globl swapper_pg_dir - .equ swapper_pg_dir, KERNEL_RAM_VADDR - SWAPPER_DIR_SIZE - - .globl idmap_pg_dir - .equ idmap_pg_dir, swapper_pg_dir - IDMAP_DIR_SIZE - - .macro pgtbl, ttb0, ttb1, phys - add \ttb1, \phys, #TEXT_OFFSET - SWAPPER_DIR_SIZE - sub \ttb0, \ttb1, #IDMAP_DIR_SIZE + .macro pgtbl, ttb0, ttb1, virt_to_phys + ldr \ttb1, =swapper_pg_dir + ldr \ttb0, =idmap_pg_dir + add \ttb1, \ttb1, \virt_to_phys + add \ttb0, \ttb0, \virt_to_phys .endm #ifdef CONFIG_ARM64_64K_PAGES
@@ -304,7 +292,7 @@ ENTRY(secondary_startup) mov x23, x0 // x23=current cpu_table cbz x23, __error_p // invalid processor (x23=0)? - pgtbl x25, x26, x24 // x25=TTBR0, x26=TTBR1 + pgtbl x25, x26, x28 // x25=TTBR0, x26=TTBR1 ldr x12, [x23, #CPU_INFO_SETUP] add x12, x12, x28 // __virt_to_phys blr x12 // initialise processor
@@ -418,7 +406,7 @@ ENDPROC(__calc_phys_offset) * - pgd entry for fixed mappings (TTBR1) */ __create_page_tables: - pgtbl x25, x26, x24 // idmap_pg_dir and swapper_pg_dir addresses + pgtbl x25, x26, x28 // idmap_pg_dir and swapper_pg_dir addresses mov x27, lr /*
@@ -507,7 +495,7 @@ ENDPROC(__create_page_tables) __switch_data: .quad __mmap_switched .quad __bss_start // x6 - .quad _end // x7 + .quad __bss_stop // x7 .quad processor_id // x4 .quad __fdt_pointer // x5 .quad memstart_addr // x6
arch/arm64/kernel/vmlinux.lds.S +7 -0
@@ -104,6 +104,13 @@ SECTIONS _edata = .; BSS_SECTION(0, 0, 0) + + . = ALIGN(PAGE_SIZE); + idmap_pg_dir = .; + . += IDMAP_DIR_SIZE; + swapper_pg_dir = .; + . += SWAPPER_DIR_SIZE; + _end = .; STABS_DEBUG
arch/arm64/mm/init.c +4 -8
@@ -128,20 +128,16 @@ void __init arm64_memblock_init(void) {u64 *reserve_map, base, size; -/* Register the kernel text, kernel data and initrd with memblock */ +/* +* Register the kernel text, kernel data, initrd, and initial +* pagetables with memblock. +*/ memblock_reserve (__pa (_text), _end - _text);#ifdef CONFIG_BLK_DEV_INITRD if (initrd_start)memblock_reserve (__virt_to_phys (initrd_start), initrd_end - initrd_start);#endif -/* -* Reserve the page tables. These are already in use, -* and can only be in node 0. -*/ -memblock_reserve (__pa (swapper_pg_dir),SWAPPER_DIR_SIZE ); -memblock_reserve (__pa (idmap_pg_dir),IDMAP_DIR_SIZE ); -/* Reserve the dtb region */ memblock_reserve (virt_to_phys (initial_boot_params),be32_to_cpu (initial_boot_params->totalsize));
Signature · 1 line
1.9.1
PATCH 2/4 arm64: place initial page tables above the kernel
Quoted 152 lines · Mark Rutland
Currently we place
swapper_pg_dirandidmap_pg_dirbelow the kernel image, betweenPHYS_OFFSETand (PHYS_OFFSET+TEXT_OFFSET). However, bootloaders may use portions of this memory below the kernel and we do not parse the memory reservation list until after the MMU has been enabled. As such we may clobber some memory a bootloader wishes to have preserved.To enable the use of all of this memory by bootloaders (when the required memory reservations are communicated to the kernel) it is necessary to move our initial page tables elsewhere. As we currently have an effectively unbound requirement for memory at the end of the kernel image for .bss, we can place the page tables here.
This patch moves the initial page table to the end of the kernel image, after the BSS. As they do not consist of any initialised data they will be stripped from the kernel Image as with the BSS. The BSS clearing routine is updated to stop at
__bss_stoprather than_endso as to not clobber the page tables, and memory reservations made redundant by the new organisation are removed.Signed-off-by Mark Rutland <[email protected]>--- arch/arm64/include/asm/page.h | 9 +++++++++ arch/arm64/kernel/head.S | 28 ++++++++-------------------- arch/arm64/kernel/vmlinux.lds.S | 7 +++++++ arch/arm64/mm/init.c | 12 ++++-------- 4 files changed, 28 insertions(+), 28 deletions(-)arch/arm64/include/asm/page.h +9
-0@@ -31,6 +31,15 @@/* We do define AT_SYSINFO_EHDR but don't use the gate mechanism */ #define __HAVE_ARCH_GATE_AREA 1 +/* +* The idmap and swapper page tables need some space reserved in the kernel +* image. The idmap only requires a pgd and a next level table to (section) map +* the kernel, while the swapper also maps the FDT and requires an additional +* table to map an early UART. See __create_page_tables for more information. +*/ +#define SWAPPER_DIR_SIZE (3 * PAGE_SIZE) +#define IDMAP_DIR_SIZE (2 * PAGE_SIZE) +#ifndef __ASSEMBLY__#ifdef CONFIG_ARM64_64K_PAGES arch/arm64/kernel/head.S +8
-20@@ -35,29 +35,17 @@ #include <asm/page.h> #include <asm/virt.h> -/* - * swapper_pg_dir is the virtual address of the initial page table. We place - * the page tables 3 * PAGE_SIZE below KERNEL_RAM_VADDR. The idmap_pg_dir has - * 2 pages and is placed below swapper_pg_dir. - */ #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) #if (KERNEL_RAM_VADDR & 0xfffff) != 0x80000 #error KERNEL_RAM_VADDR must start at 0xXXX80000 #endif -#define SWAPPER_DIR_SIZE (3 * PAGE_SIZE) -#define IDMAP_DIR_SIZE (2 * PAGE_SIZE) - - .globl swapper_pg_dir - .equ swapper_pg_dir, KERNEL_RAM_VADDR - SWAPPER_DIR_SIZE - - .globl idmap_pg_dir - .equ idmap_pg_dir, swapper_pg_dir - IDMAP_DIR_SIZE - - .macro pgtbl, ttb0, ttb1, phys - add \ttb1, \phys, #TEXT_OFFSET - SWAPPER_DIR_SIZE - sub \ttb0, \ttb1, #IDMAP_DIR_SIZE + .macro pgtbl, ttb0, ttb1, virt_to_phys + ldr \ttb1, =swapper_pg_dir + ldr \ttb0, =idmap_pg_dir + add \ttb1, \ttb1, \virt_to_phys + add \ttb0, \ttb0, \virt_to_phys .endm #ifdef CONFIG_ARM64_64K_PAGES@@ -304,7 +292,7 @@ ENTRY(secondary_startup) mov x23, x0 // x23=current cpu_table cbz x23, __error_p // invalid processor (x23=0)? - pgtbl x25, x26, x24 // x25=TTBR0, x26=TTBR1 + pgtbl x25, x26, x28 // x25=TTBR0, x26=TTBR1 ldr x12, [x23, #CPU_INFO_SETUP] add x12, x12, x28 // __virt_to_phys blr x12 // initialise processor@@ -418,7 +406,7 @@ ENDPROC(__calc_phys_offset) * - pgd entry for fixed mappings (TTBR1) */ __create_page_tables: - pgtbl x25, x26, x24 // idmap_pg_dir and swapper_pg_dir addresses + pgtbl x25, x26, x28 // idmap_pg_dir and swapper_pg_dir addresses mov x27, lr /*@@ -507,7 +495,7 @@ ENDPROC(__create_page_tables) __switch_data: .quad __mmap_switched .quad __bss_start // x6 - .quad _end // x7 + .quad __bss_stop // x7 .quad processor_id // x4 .quad __fdt_pointer // x5 .quad memstart_addr // x6arch/arm64/kernel/vmlinux.lds.S +7
-0@@ -104,6 +104,13 @@ SECTIONS _edata = .; BSS_SECTION(0, 0, 0) + + . = ALIGN(PAGE_SIZE); + idmap_pg_dir = .; + . += IDMAP_DIR_SIZE; + swapper_pg_dir = .; + . += SWAPPER_DIR_SIZE; + _end = .; STABS_DEBUGarch/arm64/mm/init.c +4
-1@@ -128,20 +128,16 @@ void __init arm64_memblock_init(void) {u64 *reserve_map, base, size; -/* Register the kernel text, kernel data and initrd with memblock */ +/* +* Register the kernel text, kernel data, initrd, and initial +* pagetables with memblock. +*/ memblock_reserve (__pa (_text), _end - _text);#ifdef CONFIG_BLK_DEV_INITRD if (initrd_start)memblock_reserve (__virt_to_phys (initrd_start), initrd_end - initrd_start);#endif
arch/arm64/mm/init.c void __init arm64_memblock_init(void) · line 143
Quoted 10 lines
arch/arm64/mm/init.c +0
-7-/* -* Reserve the page tables. These are already in use, -* and can only be in node 0. -*/ -memblock_reserve (__pa (swapper_pg_dir),SWAPPER_DIR_SIZE ); -memblock_reserve (__pa (idmap_pg_dir),IDMAP_DIR_SIZE ); -/* Reserve the dtb region */ memblock_reserve (virt_to_phys (initial_boot_params),be32_to_cpu (initial_boot_params->totalsize));
Both 4K and 64K pages
Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
Mark Rutland
reviewerPATCH 3/4 arm64: export effective Image size to bootloaders
In reply to Mark RutlandCurrently the kernel Image is stripped of everything past the initial stack, and at runtime the memory is initialised and used by the kernel. This makes the effective minimum memory footprint of the kernel larger than the size of the loaded binary, though bootloaders have no mechanism to identify how large this minimum memory footprint is. This makes it difficult to choose safe locations to place both the kernel and other binaries required at boot (DTB, initrd, etc), such that the kernel won't clobber said binaries or other reserved memory during initialisation.
Additionally when big endian support was added the image load offset was overlooked, and is currently of an arbitrary endianness, which makes it difficult for bootloaders to make use of it. It seems that bootloaders aren't respecting the image load offset at present anyway, and are assuming that offset 0x80000 will always be correct.
This patch adds an effective image size to the kernel header which describes the amount of memory from the start of the kernel Image binary which the kernel expects to use before detecting memory and handling any memory reservations. This can be used by bootloaders to choose suitable locations to load the kernel and/or other binaries such that the kernel will not clobber any memory unexpectedly. As before, memory reservations are required to prevent the kernel from clobbering these locations later.
Both the image load offset and the effective image size are forced to be little-endian regardless of the native endianness of the kernel to enable bootloaders to load a kernel of arbitrary endianness. Bootloaders which wish to make use of the load offset can inspect the effective image size field for a non-zero value to determine if the offset is of a known endianness.
The documentation is updated to clarify these details. To discourage future assumptions regarding the value of text_offset, the value at this point in time is removed from the main flow of the documentation (though kept as a compatibility note).
--- Documentation/arm64/booting.txt | 28 +++++++++++++++++++++++----- arch/arm64/kernel/head.S | 4 ++-- arch/arm64/kernel/vmlinux.lds.S | 28 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-)
Documentation/arm64/booting.txt +23 -5
@@ -72,8 +72,8 @@ The decompressed kernel image contains a 64-byte header as follows: u32 code0; /* Executable code */ u32 code1; /* Executable code */ - u64 text_offset; /* Image load offset */ - u64 res0 = 0; /* reserved */ + u64 text_offset; /* Image load offset, little endian */ + u64 image_size; /* Effective Image size, little endian */ u64 res1 = 0; /* reserved */ u64 res2 = 0; /* reserved */ u64 res3 = 0; /* reserved */
@@ -86,9 +86,27 @@ Header notes: - code0/code1 are responsible for branching to stext. -The image must be placed at the specified offset (currently 0x80000) -from the start of the system RAM and called there. The start of the -system RAM must be aligned to 2MB. +- Older kernel versions did not define the endianness of text_offset. + In these cases image_size is zero and text_offset is 0x80000 in the + endianness of the kernel. Where image_size is non-zero image_size is + little-endian and must be respected. + +- When image_size is zero, a bootloader should attempt to keep as much + memory as possible free for use by the kernel immediately after the + end of the kernel image. Typically 1MB should be sufficient for this + case. + +The Image must be placed text_offset bytes from a 2MB aligned base +address near the start of usable system RAM and called there. Memory +below that base address is currently unusable by Linux, and therefore it +is strongly recommended that this location is the start of system RAM. +At least image_size bytes from the start of the image must be free for +use by the kernel. + +Any memory described to the kernel (even that below the 2MB aligned base +address) which is not marked as reserved from the kernel e.g. with a +memreserve region in the device tree) will be considered as available to +the kernel. Before jumping into the kernel, the following conditions must be met:
arch/arm64/kernel/head.S +2 -2
@@ -98,8 +98,8 @@ */ b stext // branch to kernel start, magic .long 0 // reserved - .quad TEXT_OFFSET // Image load offset from start of RAM - .quad 0 // reserved + .quad _kernel_offset_le // Image load offset from start of RAM, little-endian + .quad _kernel_size_le // Effective size of kernel image, little-endian .quad 0 // reserved .quad 0 // reserved .quad 0 // reserved
arch/arm64/kernel/vmlinux.lds.S +28 -0
@@ -30,6 +30,25 @@ jiffies = jiffies_64; *(.hyp.text) \ VMLINUX_SYMBOL(__hyp_text_end) = .; +/* + * There aren't any ELF relocations we can use to endian-swap values known only + * at link time (e.g. the subtraction of two symbol addresses), so we must get + * the linker to endian-swap certain values before emitting them. + */ +#ifdef CONFIG_CPU_BIG_ENDIAN +#define DATA_LE64(data) \ + ((((data) & 0x00000000000000ff) << 56) | \ + (((data) & 0x000000000000ff00) << 40) | \ + (((data) & 0x0000000000ff0000) << 24) | \ + (((data) & 0x00000000ff000000) << 8) | \ + (((data) & 0x000000ff00000000) >> 8) | \ + (((data) & 0x0000ff0000000000) >> 24) | \ + (((data) & 0x00ff000000000000) >> 40) | \ + (((data) & 0xff00000000000000) >> 56)) +#else +#define DATA_LE64(data) ((data) & 0xffffffffffffffff) +#endif + SECTIONS { /*
@@ -114,6 +133,15 @@ SECTIONS _end = .; STABS_DEBUG + + /* + * These will output as part of the Image header, which should be + * little-endian regardless of the endianness of the kernel. While + * constant values could be endian swapped in head.S, all are done here + * for consistency. + */ + _kernel_size_le = DATA_LE64(_end - _text); + _kernel_offset_le = DATA_LE64(TEXT_OFFSET); } /*
Signature · 1 line
1.9.1
PATCH 3/4 arm64: export effective Image size to bootloaders
Quoted 31 lines · Mark Rutland
Currently the kernel Image is stripped of everything past the initial stack, and at runtime the memory is initialised and used by the kernel. This makes the effective minimum memory footprint of the kernel larger than the size of the loaded binary, though bootloaders have no mechanism to identify how large this minimum memory footprint is. This makes it difficult to choose safe locations to place both the kernel and other binaries required at boot (DTB, initrd, etc), such that the kernel won't clobber said binaries or other reserved memory during initialisation.
Additionally when big endian support was added the image load offset was overlooked, and is currently of an arbitrary endianness, which makes it difficult for bootloaders to make use of it. It seems that bootloaders aren't respecting the image load offset at present anyway, and are assuming that offset
0x80000will always be correct.This patch adds an effective image size to the kernel header which describes the amount of memory from the start of the kernel Image binary which the kernel expects to use before detecting memory and handling any memory reservations. This can be used by bootloaders to choose suitable locations to load the kernel and/or other binaries such that the kernel will not clobber any memory unexpectedly. As before, memory reservations are required to prevent the kernel from clobbering these locations later.
Both the image load offset and the effective image size are forced to be little-endian regardless of the native endianness of the kernel to enable bootloaders to load a kernel of arbitrary endianness. Bootloaders which wish to make use of the load offset can inspect the effective image size field for a non-zero value to determine if the offset is of a known endianness.
Quoted 6 lines
The documentation is updated to clarify these details. To discourage future assumptions regarding the value of
text_offset, the value at this point in time is removed from the main flow of the documentation (though kept as a compatibility note).Signed-off-by Mark Rutland <[email protected]>
Putting on my U-Boot hat, this looks sensible and implementable so:
Signature · 1 line
Tom
PATCH 3/4 arm64: export effective Image size to bootloaders
In reply to Mark RutlandQuoted 141 lines · Mark Rutland
Currently the kernel Image is stripped of everything past the initial stack, and at runtime the memory is initialised and used by the kernel. This makes the effective minimum memory footprint of the kernel larger than the size of the loaded binary, though bootloaders have no mechanism to identify how large this minimum memory footprint is. This makes it difficult to choose safe locations to place both the kernel and other binaries required at boot (DTB, initrd, etc), such that the kernel won't clobber said binaries or other reserved memory during initialisation.
Additionally when big endian support was added the image load offset was overlooked, and is currently of an arbitrary endianness, which makes it difficult for bootloaders to make use of it. It seems that bootloaders aren't respecting the image load offset at present anyway, and are assuming that offset
0x80000will always be correct.This patch adds an effective image size to the kernel header which describes the amount of memory from the start of the kernel Image binary which the kernel expects to use before detecting memory and handling any memory reservations. This can be used by bootloaders to choose suitable locations to load the kernel and/or other binaries such that the kernel will not clobber any memory unexpectedly. As before, memory reservations are required to prevent the kernel from clobbering these locations later.
Both the image load offset and the effective image size are forced to be little-endian regardless of the native endianness of the kernel to enable bootloaders to load a kernel of arbitrary endianness. Bootloaders which wish to make use of the load offset can inspect the effective image size field for a non-zero value to determine if the offset is of a known endianness.
The documentation is updated to clarify these details. To discourage future assumptions regarding the value of
text_offset, the value at this point in time is removed from the main flow of the documentation (though kept as a compatibility note).Signed-off-by Mark Rutland <[email protected]>--- Documentation/arm64/booting.txt | 28 +++++++++++++++++++++++----- arch/arm64/kernel/head.S | 4 ++-- arch/arm64/kernel/vmlinux.lds.S | 28 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-)Documentation/arm64/booting.txt +23
-5@@ -72,8 +72,8 @@ The decompressed kernel image contains a 64-byte header as follows: u32 code0; /* Executable code */ u32 code1; /* Executable code */ - u64 text_offset; /* Image load offset */ - u64 res0 = 0; /* reserved */ + u64 text_offset; /* Image load offset, little endian */ + u64 image_size; /* Effective Image size, little endian */ u64 res1 = 0; /* reserved */ u64 res2 = 0; /* reserved */ u64 res3 = 0; /* reserved */@@ -86,9 +86,27 @@ Header notes: - code0/code1 are responsible for branching to stext. -The image must be placed at the specified offset (currently 0x80000) -from the start of the system RAM and called there. The start of the -system RAM must be aligned to 2MB. +- Older kernel versions did not define the endianness of text_offset. + In these cases image_size is zero and text_offset is 0x80000 in the + endianness of the kernel. Where image_size is non-zero image_size is + little-endian and must be respected. + +- When image_size is zero, a bootloader should attempt to keep as much + memory as possible free for use by the kernel immediately after the + end of the kernel image. Typically 1MB should be sufficient for this + case. + +The Image must be placed text_offset bytes from a 2MB aligned base +address near the start of usable system RAM and called there. Memory +below that base address is currently unusable by Linux, and therefore it +is strongly recommended that this location is the start of system RAM. +At least image_size bytes from the start of the image must be free for +use by the kernel. + +Any memory described to the kernel (even that below the 2MB aligned base +address) which is not marked as reserved from the kernel e.g. with a +memreserve region in the device tree) will be considered as available to +the kernel. Before jumping into the kernel, the following conditions must be met:arch/arm64/kernel/head.S +2
-2@@ -98,8 +98,8 @@ */ b stext // branch to kernel start, magic .long 0 // reserved - .quad TEXT_OFFSET // Image load offset from start of RAM - .quad 0 // reserved + .quad _kernel_offset_le // Image load offset from start of RAM, little-endian + .quad _kernel_size_le // Effective size of kernel image, little-endian .quad 0 // reserved .quad 0 // reserved .quad 0 // reservedarch/arm64/kernel/vmlinux.lds.S +21
-0@@ -30,6 +30,25 @@ jiffies = jiffies_64; *(.hyp.text) \ VMLINUX_SYMBOL(__hyp_text_end) = .; +/* + * There aren't any ELF relocations we can use to endian-swap values known only + * at link time (e.g. the subtraction of two symbol addresses), so we must get + * the linker to endian-swap certain values before emitting them. + */ +#ifdef CONFIG_CPU_BIG_ENDIAN +#define DATA_LE64(data) \ + ((((data) & 0x00000000000000ff) << 56) | \ + (((data) & 0x000000000000ff00) << 40) | \ + (((data) & 0x0000000000ff0000) << 24) | \ + (((data) & 0x00000000ff000000) << 8) | \ + (((data) & 0x000000ff00000000) >> 8) | \ + (((data) & 0x0000ff0000000000) >> 24) | \ + (((data) & 0x00ff000000000000) >> 40) | \ + (((data) & 0xff00000000000000) >> 56)) +#else +#define DATA_LE64(data) ((data) & 0xffffffffffffffff) +#endif + SECTIONS { /*@@ -114,6 +133,15 @@ SECTIONS _end = .; STABS_DEBUG + + /*
arch/arm64/kernel/vmlinux.lds.S SECTIONS · line 147
Quoted 10 lines
arch/arm64/kernel/vmlinux.lds.S +7
-0+ * These will output as part of the Image header, which should be + * little-endian regardless of the endianness of the kernel. While + * constant values could be endian swapped in head.S, all are done here + * for consistency. + */ + _kernel_size_le = DATA_LE64(_end - _text); + _kernel_offset_le = DATA_LE64(TEXT_OFFSET); } /*
Both 4K and 64K pages
Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH 3/4 arm64: export effective Image size to bootloaders
In reply to Mark RutlandHi Mark,
Sorry for such a delay in my reply.
Quoted 6 lines · Mark Rutland
Both the image load offset and the effective image size are forced to be little-endian regardless of the native endianness of the kernel to enable bootloaders to load a kernel of arbitrary endianness. Bootloaders which wish to make use of the load offset can inspect the effective image size field for a non-zero value to determine if the offset is of a known endianness.
Doing this conversion in the linker script seems complicated. My thought was to just have an image header field that specifies the byte order, in the same way that the EI_DATA part of the ELF e_ident field does.
Another advantage of having the byte order in the header is that tools other than a boot loader that need to know the byte order can get that info from the header, otherwise they would need to guess the order with some kind of inspection.
A patch I had planned to post for this is below.
Documentation/arm64/booting.txt The decompressed kernel image contains a 64-byte header as follows: · line 76
Quoted 10 lines
Documentation/arm64/booting.txt +2
-2@@ -72,8 +72,8 @@ The decompressed kernel image contains a 64-byte header as follows: u32 code0; /* Executable code */ u32 code1; /* Executable code */ - u64 text_offset; /* Image load offset */ - u64 res0 = 0; /* reserved */ + u64 text_offset; /* Image load offset, little endian */ + u64 image_size; /* Effective Image size, little endian */
It seems __le64 would be a better type to use here, if the value is decided to be little endian.
Quoted 7 lines
Documentation/arm64/booting.txt +0
-0u64 res1 = 0; /* reserved */ u64 res2 = 0; /* reserved */ u64 res3 = 0; /* reserved */@@ -86,9 +86,27 @@ Header notes:From
a63dd2f24105c55734238efaacdac5048bbedf39Mon Sep 17 00:00:00 2001
From: Geoff Levand <[email protected]>
Date: Thu, 12 Jun 2014 11:23:23 -0700
Subject: [PATCH] arm64: Add byte order to image header
When working with a raw arm64 image one needs to know the byte order of the image header to properly interpret the multi-byte values of the header. Add a character value to the image header indicating the byte order the image was built with:
1=LSB (little endian), 2=MSB (big endian), 0=no support.
A zero value will indicate a kernel that pre-dates this change.
--- arch/arm64/kernel/head.S | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
arch/arm64/kernel/head.S +5 -1
@@ -109,7 +109,11 @@ * DO NOT MODIFY. Image header expected by Linux boot-loaders. */ b stext // branch to kernel start, magic - .long 0 // reserved + CPU_LE(.byte 1) // 1=LSB (little endian) + CPU_BE(.byte 2) // 2=MSB (big endian) + .byte 0 // reserved + .byte 0 // reserved + .byte 0 // reserved .quad TEXT_OFFSET // Image load offset from start of RAM .quad 0 // reserved .quad 0 // reserved
Signature · 1 line
1.9.1
Mark Rutland
reviewerPATCH 3/4 arm64: export effective Image size to bootloaders
Quoted 1 line · Geoff Levand
Hi Mark,
Hi Geoff,
Quoted 10 lines
Sorry for such a delay in my reply.
Quoted 6 lines · Mark Rutland
Both the image load offset and the effective image size are forced to be little-endian regardless of the native endianness of the kernel to enable bootloaders to load a kernel of arbitrary endianness. Bootloaders which wish to make use of the load offset can inspect the effective image size field for a non-zero value to determine if the offset is of a known endianness.
Quoted 4 lines
Doing this conversion in the linker script seems complicated. My thought was to just have an image header field that specifies the byte order, in the same way that the
EI_DATApart of the ELFe_identfield does.
While the conversion in the linker script is a little ugly, it does work, and that complexity is hidden behind the macro I added.
While I initially considered having a field to specify byte order, it's incredibly likely that bootloaders will not use it. Maintaining a fixed endianness everywhere makes it simpler for bootloaders to do the right thing, and matches what existing bootloaders are already doing. That's less pain for loaders and less pain for the kernel, as things are less likely to go wrong.
To me it makes more sense to ensure these fields have a consistent endianness, rather than adding more room for possible mistakes.
Quoted 4 lines
Another advantage of having the byte order in the header is that tools other than a boot loader that need to know the byte order can get that info from the header, otherwise they would need to guess the order with some kind of inspection.
What kind of tools do you envision which would need to know the endianness of the kernel but would be looking at the Image rather than the vmlinux?
Mark.
PATCH 3/4 arm64: export effective Image size to bootloaders
Quoted 33 lines · Mark Rutland
Quoted 1 line · Geoff Levand
Hi Mark,
Hi Geoff,
Quoted 10 lines
Sorry for such a delay in my reply.
Quoted 6 lines · Mark Rutland
Both the image load offset and the effective image size are forced to be little-endian regardless of the native endianness of the kernel to enable bootloaders to load a kernel of arbitrary endianness. Bootloaders which wish to make use of the load offset can inspect the effective image size field for a non-zero value to determine if the offset is of a known endianness.
Quoted 4 lines
Doing this conversion in the linker script seems complicated. My thought was to just have an image header field that specifies the byte order, in the same way that the
EI_DATApart of the ELFe_identfield does.While the conversion in the linker script is a little ugly, it does work, and that complexity is hidden behind the macro I added.
While I initially considered having a field to specify byte order, it's incredibly likely that bootloaders will not use it. Maintaining a fixed endianness everywhere makes it simpler for bootloaders to do the right thing, and matches what existing bootloaders are already doing. That's less pain for loaders and less pain for the kernel, as things are less likely to go wrong.
To me it makes more sense to ensure these fields have a consistent endianness, rather than adding more room for possible mistakes.
Quoted 8 lines
Quoted 4 lines
Another advantage of having the byte order in the header is that tools other than a boot loader that need to know the byte order can get that info from the header, otherwise they would need to guess the order with some kind of inspection.
What kind of tools do you envision which would need to know the endianness of the kernel but would be looking at the Image rather than the vmlinux?
Seems like this has the same problem as being discussed for arm32:
http://permalink.gmane.org/gmane.linux.kernel/1727993
Rob
PATCH 3/4 arm64: export effective Image size to bootloaders
In reply to Mark RutlandHi Mark,
Quoted 10 lines · Mark Rutland
On Mon, Jun 16, 2014 at 09:27:12PM +0100, Geoff Levand wrote:
While I initially considered having a field to specify byte order, it's incredibly likely that bootloaders will not use it. Maintaining a fixed endianness everywhere makes it simpler for bootloaders to do the right thing, and matches what existing bootloaders are already doing. That's less pain for loaders and less pain for the kernel, as things are less likely to go wrong.To me it makes more sense to ensure these fields have a consistent endianness, rather than adding more room for possible mistakes.
I guess my view is that any software expected to support arm64 in both big and little endian configurations should be tested with such, and with this it is pretty simple to test and fix, so we shouldn't over engineer it, but I don't really care how we fix it.
Quoted 8 lines
Quoted 4 lines
Another advantage of having the byte order in the header is that tools other than a boot loader that need to know the byte order can get that info from the header, otherwise they would need to guess the order with some kind of inspection.
What kind of tools do you envision which would need to know the endianness of the kernel but would be looking at the Image rather than the vmlinux?
I have no idea, but I can imagine it may be of use to someone who is trying to figure out why things don't work and would like to know what kind of image they have.
-Geoff
Mark Rutland
reviewerPATCH 3/4 arm64: export effective Image size to bootloaders
Quoted 1 line · Geoff Levand
Hi Mark,
Hi Geoff,
Thanks for raising the issue. I can see from Kevin's comments that the kernel endianness is a useful piece of information, so I'll add a field to the header to export that for v3.
Quoted 9 lines · Mark Rutland
Quoted 8 lines · Geoff Levand
While I initially considered having a field to specify byte order, it's incredibly likely that bootloaders will not use it. Maintaining a fixed endianness everywhere makes it simpler for bootloaders to do the right thing, and matches what existing bootloaders are already doing. That's less pain for loaders and less pain for the kernel, as things are less likely to go wrong.
Quoted 7 lines
Quoted 2 lines
To me it makes more sense to ensure these fields have a consistent endianness, rather than adding more room for possible mistakes.
I guess my view is that any software expected to support arm64 in both big and little endian configurations should be tested with such, and with this it is pretty simple to test and fix, so we shouldn't over engineer it, but I don't really care how we fix it.
I disagree that the bootloader _must_ have to deal with the endianness of the kernel. If the user knows the endianness of the kernel and provides a filesystem of the appropriate endianness, I don't see why the bootloader should have to do anything differently. In most cases the bootloader has no need to care.
IMO It's far better to export the fields in the kernel header in a consistent endianness than it is to force every boot loader to inspect the endianness of the kernel image. I can imagine there are going to be plenty of bootloaders which will not attempt to determine the endianness and will blindly assume a particular case. Exporting the kernel endianness does seem to make sense, but I think this should be in addition to the information bootloaders require rather than being part of the information bootloaders require.
Thanks,
Mark.
PATCH 3/4 arm64: export effective Image size to bootloaders
Hi Mark,
Quoted 5 lines · Mark Rutland
I disagree that the bootloader _must_ have to deal with the endianness of the kernel. If the user knows the endianness of the kernel and provides a filesystem of the appropriate endianness, I don't see why the bootloader should have to do anything differently. In most cases the bootloader has no need to care.
As I mentioned, I don't really care how this is fixed, but there will be bootloaders built as both big and little endian (kexec based ones for example), and so no matter what, bootloaders will need to deal with converting values.
-Geoff
Mark Rutland
reviewerPATCH 3/4 arm64: export effective Image size to bootloaders
Quoted 9 lines · Geoff Levand
Hi Mark,
Quoted 5 lines · Mark Rutland
I disagree that the bootloader _must_ have to deal with the endianness of the kernel. If the user knows the endianness of the kernel and provides a filesystem of the appropriate endianness, I don't see why the bootloader should have to do anything differently. In most cases the bootloader has no need to care.
Quoted 4 lines
As I mentioned, I don't really care how this is fixed, but there will be bootloaders built as both big and little endian (kexec based ones for example), and so no matter what, bootloaders will need to deal with converting values.
Sure, bootloaders will need to convert values in some cases.
By having a fixed endian header format, whether or not you need to byteswap values becomes a fixed property of the bootloader (i.e. whether it is BE or not) rather than a dynamic property of the kernel Image. So you only require a single path through your loader for BE and LE kernels.
If you have constructs like LE64_TO_CPU available to your loader, then it becomes trivial to write an endian-clean loader. The only pain is having to test if image_size is non-zero first, but that test is independent of endiannness.
Mark.
PATCH 3/4 arm64: export effective Image size to bootloaders
In reply to Mark RutlandQuoted 3 lines · Mark Rutland
What kind of tools do you envision which would need to know the endianness of the kernel but would be looking at the Image rather than the vmlinux?
Automated boot infrastructure which has access to the [zu]Image but not the vmlinux and wants to know which endian rootfs to pass to the kernel.
Kevin
PATCH 1/1 arm64: Add byte order to image header
In reply to Mark RutlandWhen working with a raw arm64 image one needs to know the byte order of the image header to properly interpret the multi-byte values of the header. Add a character value to the image header indicating the byte order the image was built with:
1=LSB (little endian), 2=MSB (big endian), 0=no support.
A zero value will indicate a kernel that pre-dates this change.
---
Hi,
I noticed there was a change to the image header for COFF compatibility that conflicted with my old patch. Here's an update that also changes booting.txt.
I'll also post a patch that adds a new file asm/image.h to describe the header.
-Geoff
Documentation/arm64/booting.txt | 10 ++++++---- arch/arm64/kernel/head.S | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-)
Documentation/arm64/booting.txt +6 -4
@@ -73,13 +73,15 @@ The decompressed kernel image contains a 64-byte header as follows: u32 code0; /* Executable code */ u32 code1; /* Executable code */ u64 text_offset; /* Image load offset */ - u64 res0 = 0; /* reserved */ - u64 res1 = 0; /* reserved */ - u64 res2 = 0; /* reserved */ + u8 byte_order; /* 1=LSB (little endian), 2=MSB (big endian) */ + u8[3] res1; /* reserved */ + u32 res2 = 0; /* reserved */ u64 res3 = 0; /* reserved */ u64 res4 = 0; /* reserved */ + u64 res5 = 0; /* reserved */ + u64 res6 = 0; /* reserved */ u32 magic = 0x644d5241; /* Magic number, little endian, "ARM\x64" */ - u32 res5 = 0; /* reserved */ + u32 res7 = 0; /* reserved */ Header notes:
arch/arm64/kernel/head.S +6 -1
@@ -111,7 +111,12 @@ b stext // branch to kernel start, magic .long 0 // reserved .quad TEXT_OFFSET // Image load offset from start of RAM - .quad 0 // reserved + CPU_LE(.byte 1) // 1=LSB (little endian) + CPU_BE(.byte 2) // 2=MSB (big endian) + .byte 0 // reserved + .byte 0 // reserved + .byte 0 // reserved + .word 0 // reserved .quad 0 // reserved .quad 0 // reserved .quad 0 // reserved
Signature · 1 line
1.9.1
PATCH 1/1 arm64: Add new file asm/image.h
Add the new file asm/image.h that exports a structure arm64_image_header and some constants to user space that describe the arm64 image header.
--- Documentation/arm64/booting.txt | 21 ++-------------- arch/arm64/include/asm/Kbuild | 2 ++ arch/arm64/include/asm/image.h | 53 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 arch/arm64/include/asm/image.h
Documentation/arm64/booting.txt +2 -19
@@ -68,25 +68,8 @@ Image target is available instead. Requirement: MANDATORY -The decompressed kernel image contains a 64-byte header as follows: - - u32 code0; /* Executable code */ - u32 code1; /* Executable code */ - u64 text_offset; /* Image load offset */ - u8 byte_order; /* 1=LSB (little endian), 2=MSB (big endian) */ - u8[3] res1; /* reserved */ - u32 res2 = 0; /* reserved */ - u64 res3 = 0; /* reserved */ - u64 res4 = 0; /* reserved */ - u64 res5 = 0; /* reserved */ - u64 res6 = 0; /* reserved */ - u32 magic = 0x644d5241; /* Magic number, little endian, "ARM\x64" */ - u32 res7 = 0; /* reserved */ - - -Header notes: - -- code0/code1 are responsible for branching to stext. +The decompressed kernel image contains a 64-byte header as described in +arch/arm64/include/asm/image.h. The image must be placed at the specified offset (currently 0x80000) from the start of the system RAM and called there. The start of the
arch/arm64/include/asm/Kbuild +2 -0
@@ -55,3 +55,5 @@ generic-y += unaligned.h generic-y += user.h generic-y += vga.h generic-y += xor.h + +header-y += image.h
arch/arm64/include/asm/image.h new file +53 -0
53 lines not on this page · raw message
Signature · 1 line
1.9.1
Mark Rutland
reviewerPATCH 4/4 arm64: Enable TEXT_OFFSET fuzzing
In reply to Mark RutlandThe arm64 Image header contains a text_offset field which bootloaders are supposed to read to determine the offset (from a 2MB aligned "start of memory" per booting.txt) at which to load the kernel. The offset is not well respected by bootloaders at present, and due to the lack of variation there is little incentive to support it. This is unfortunate for the sake of future kernels where we may wish to vary the text offset (even zeroing it).
This patch adds options to arm64 to enable fuzz-testing of text_offset. CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET forces the text offset to a random value upon a build of the kernel, and CONFIG_ARM64_TEXT_OFFSET can be used to set the text offset to a specific value (between 16B and 2MB). It is recommended that distribution kernels enable randomization to test bootloaders such that any compliance issues can be fixed early.
--- arch/arm64/Kconfig.debug | 31 +++++++++++++++++++++++++++++++ arch/arm64/Makefile | 6 +++++- arch/arm64/kernel/head.S | 8 ++++++-- arch/arm64/kernel/vmlinux.lds.S | 5 +++++ 4 files changed, 47 insertions(+), 3 deletions(-)
arch/arm64/Kconfig.debug +31 -0
@@ -37,4 +37,35 @@ config PID_IN_CONTEXTIDR instructions during context switch. Say Y here only if you are planning to use hardware trace tools with this kernel. +config ARM64_RANDOMIZE_TEXT_OFFSET + bool "Randomize TEXT_OFFSET at build time (EXPERIMENTAL)" + default N + help + Say Y here if you want the image load offset (AKA TEXT_OFFSET) + of the kernel to be randomized at build-time. When selected, + this option will cause TEXT_OFFSET to be randomized upon any + build of the kernel, and the offset will be reflected in the + text_offset field of the resulting Image. This can be used to + fuzz-test bootloaders which respect text_offset. + + This option is intended for bootloader and/or kernel testing + only. Bootloaders must make no assumptions regarding the value + of TEXT_OFFSET and platforms must not require a specific + value. + +config ARM64_TEXT_OFFSET + hex "Required image load offset" + depends on !ARM64_RANDOMIZE_TEXT_OFFSET + default "0x0000000000080000" + help + The image load offset (AKA TEXT_OFFSET) of the kernel. The + load offset will be reflected in the text_offset field of the + Image. TEXT_OFFSET must be a 16-byte aligned value less than + 2MB. + + This option is intended for bootloader and/or kernel testing + only. Bootloaders must make no assumptions regarding the value + of TEXT_OFFSET and platforms must not require a specific + value. + endmenu
arch/arm64/Makefile +5 -1
@@ -38,7 +38,11 @@ CHECKFLAGS += -D__aarch64__ head-y := arch/arm64/kernel/head.o # The byte offset of the kernel image in RAM from the start of RAM. -TEXT_OFFSET := 0x00080000 +ifeq ($(CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET), y) +TEXT_OFFSET := $(shell awk 'BEGIN {srand(); printf "0x%05x\n", and(int(0xfffff * rand()), 0xffff0)}') +else +TEXT_OFFSET := $(CONFIG_ARM64_TEXT_OFFSET) +endif export TEXT_OFFSET GZFLAGS
arch/arm64/kernel/head.S +6 -2
@@ -37,8 +37,12 @@ #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) -#if (KERNEL_RAM_VADDR & 0xfffff) != 0x80000 -#error KERNEL_RAM_VADDR must start at 0xXXX80000 +#if (TEXT_OFFSET & 0xf) != 0 +#error TEXT_OFFSET must be at least 16B aligned +#elif (PAGE_OFFSET & 0xfffff) != 0 +#error PAGE_OFFSET must be at least 2MB aligned +#elif TEXT_OFFSET > 0xfffff +#error TEXT_OFFSET must be less than 2MB #endif .macro pgtbl, ttb0, ttb1, virt_to_phys
arch/arm64/kernel/vmlinux.lds.S +5 -0
@@ -149,3 +149,8 @@ SECTIONS */ ASSERT(((__hyp_idmap_text_start + PAGE_SIZE) > __hyp_idmap_text_end), "HYP init code too big") + +/* + * If padding is applied before .head.text, virt<->phys conversions will fail. + */ +ASSERT(_text == (PAGE_OFFSET + TEXT_OFFSET), "HEAD is misaligned")
Signature · 1 line
1.9.1
Catalin Marinas
maintainerPATCH 4/4 arm64: Enable TEXT_OFFSET fuzzing
arch/arm64/Kconfig.debug config PID_IN_CONTEXTIDR · line 42
Quoted 9 lines · Mark Rutland
arch/arm64/Kconfig.debug +3
-0@@ -37,4 +37,35 @@ config PID_IN_CONTEXTIDR instructions during context switch. Say Y here only if you are planning to use hardware trace tools with this kernel. +config ARM64_RANDOMIZE_TEXT_OFFSET + bool "Randomize TEXT_OFFSET at build time (EXPERIMENTAL)" + default N
(nitpick: no need for default n)
I think that's good for testing. It would have been nice to be able to set some limits for the random offset but I can't figure out an easy way to do this via Kconfig (maybe with additional options).
arch/arm64/Kconfig.debug config PID_IN_CONTEXTIDR · line 59
Quoted 4 lines
arch/arm64/Kconfig.debug +4
-0+config ARM64_TEXT_OFFSET + hex "Required image load offset" + depends on !ARM64_RANDOMIZE_TEXT_OFFSET + default "0x0000000000080000"
I don't think we should include this. It encourages people to set specific offsets for their SoCs.
Signature · 1 line
Catalin
Mark Rutland
reviewerPATCH 4/4 arm64: Enable TEXT_OFFSET fuzzing
Quoted 7 lines · Catalin Marinas
Quoted 6 lines · Mark Rutland
arch/arm64/Kconfig.debug +0
-0@@ -37,4 +37,35 @@ config PID_IN_CONTEXTIDR instructions during context switch. Say Y here only if you are planning to use hardware trace tools with this kernel.
Quoted 5 lines
arch/arm64/Kconfig.debug config PID_IN_CONTEXTIDR · line 42
Quoted 3 lines
arch/arm64/Kconfig.debug +3
-0+config ARM64_RANDOMIZE_TEXT_OFFSET + bool "Randomize TEXT_OFFSET at build time (EXPERIMENTAL)" + default N(nitpick: no need for default n)
Thanks for pointing that out, I'll remove it :)
Quoted 3 lines
I think that's good for testing. It would have been nice to be able to set some limits for the random offset but I can't figure out an easy way to do this via
Kconfig(maybe with additional options).
There are hard-coded limits implicit in the randomization -- between 0B and 2MB in 16B increments:
TEXT_OFFSET := $(shell awk 'BEGIN {srand(); printf "0x%05x\n", and(int(0xfffff * rand()), 0xffff0)}')
The 16B increment is required due to some code in head.S (__turn_mmu_on) requiring a minimum 16B alignment for the object.
The 2MB maximum comes from the fact we rely on the start of memory being 2MB aligned. I'm not sure there's a compelling reason to limit the randomization if enabled at all -- either you can handle it or you can't. Are we ever likely to want an offset larger than the memory alignment?
Quoted 7 lines
arch/arm64/Kconfig.debug config PID_IN_CONTEXTIDR · line 59
Quoted 4 lines
arch/arm64/Kconfig.debug +4
-0+config ARM64_TEXT_OFFSET + hex "Required image load offset" + depends on !ARM64_RANDOMIZE_TEXT_OFFSET + default "0x0000000000080000"I don't think we should include this. It encourages people to set specific offsets for their SoCs.
Sure, I was worried about potential abuse also (hence the warning in the help text). I'll drop this portion.
Cheers,
Mark.
PATCH 4/4 arm64: Enable TEXT_OFFSET fuzzing
Quoted 16 lines · Mark Rutland
Quoted 7 lines · Catalin Marinas
Quoted 6 lines · Mark Rutland
arch/arm64/Kconfig.debug +0
-0@@ -37,4 +37,35 @@ config PID_IN_CONTEXTIDR instructions during context switch. Say Y here only if you are planning to use hardware trace tools with this kernel.Quoted 5 lines
arch/arm64/Kconfig.debug config PID_IN_CONTEXTIDR · line 42
Quoted 3 lines
arch/arm64/Kconfig.debug +3
-0+config ARM64_RANDOMIZE_TEXT_OFFSET + bool "Randomize TEXT_OFFSET at build time (EXPERIMENTAL)" + default N(nitpick: no need for default n)
Thanks for pointing that out, I'll remove it :)
Quoted 8 lines
Quoted 3 lines
I think that's good for testing. It would have been nice to be able to set some limits for the random offset but I can't figure out an easy way to do this via
Kconfig(maybe with additional options).There are hard-coded limits implicit in the randomization -- between 0B and 2MB in 16B increments:
TEXT_OFFSET:= $(shell awk 'BEGIN {srand(); printf "0x%05x\n",and(int(0xfffff * rand()), 0xffff0)}')
Note, is mawk expected to be able to be used for kernel building? It doesn't have 'and' as a function.
Quoted 8 lines
The 16B increment is required due to some code in
head.S(__turn_mmu_on) requiring a minimum 16B alignment for the object.The 2MB maximum comes from the fact we rely on the start of memory being 2MB aligned. I'm not sure there's a compelling reason to limit the randomization if enabled at all -- either you can handle it or you can't. Are we ever likely to want an offset larger than the memory alignment?
A reason to limit the randomization is to make it easier on the bootloaders to be able to rule of thumb initial loads. It's not a big deal with Image if it gets loaded lower than the offset, we can start shifting data at the end. But when we start looking at Image.gz (or xz or ...), in some cases we'll get the whole image read into memory (network booting for example), uncompress the first block so we can confirm a good Image header and see about text_offset/image_size. If we know that text_offset is somewhere random inside of 2MB (or some other documented max), we can then go with saying an initial load should be at say +32MB (to mirror Documentation/arm/Booting). If it can be anywhere, then things get hard, or at least annoying (error out and tell the user to re-load things because of a random value? I can see testing frameworks being annoyed about that).
And we should document the range of the offset inDocumentation/arm64/booting.txt as well.
Signature · 1 line
Tom
Mark Rutland
reviewerPATCH 4/4 arm64: Enable TEXT_OFFSET fuzzing
Quoted 21 lines · Tom Rini
Quoted 20 lines · Mark Rutland
Quoted 7 lines · Catalin Marinas
Quoted 6 lines · Mark Rutland
arch/arm64/Kconfig.debug +0
-0@@ -37,4 +37,35 @@ config PID_IN_CONTEXTIDR instructions during context switch. Say Y here only if you are planning to use hardware trace tools with this kernel.Quoted 5 lines
arch/arm64/Kconfig.debug config PID_IN_CONTEXTIDR · line 42
Quoted 3 lines
arch/arm64/Kconfig.debug +3
-0+config ARM64_RANDOMIZE_TEXT_OFFSET + bool "Randomize TEXT_OFFSET at build time (EXPERIMENTAL)" + default N(nitpick: no need for default n)
Thanks for pointing that out, I'll remove it :)
Quoted 3 lines
I think that's good for testing. It would have been nice to be able to set some limits for the random offset but I can't figure out an easy way to do this via
Kconfig(maybe with additional options).
Quoted 7 lines
Quoted 4 lines
There are hard-coded limits implicit in the randomization -- between 0B and 2MB in 16B increments:
TEXT_OFFSET:= $(shell awk 'BEGIN {srand(); printf "0x%05x\n",and(int(0xfffff * rand()), 0xffff0)}')Note, is mawk expected to be able to be used for kernel building? It doesn't have 'and' as a function.
Ouch. I hadn't realised this was gawk-only. My new local machine only has mawk and builds the kernel fine otherwise, so it seems like we can't just expect gawk. It also seems that mawk doesn't like inline hex values...
The masking is only there to ensure the value is a multiple of 16 (i.e. the last digit is 0), so we can just handle that in the print instead. Then 0xffff is 65535, so:
TEXT_OFFSET := $(shell awk 'BEGIN {srand(); printf "0x%04x0\n", int(65535 * rand())}')
That seems to work in gawk and mawk.
Quoted 10 lines
Quoted 9 lines
The 16B increment is required due to some code in
head.S(__turn_mmu_on) requiring a minimum 16B alignment for the object.The 2MB maximum comes from the fact we rely on the start of memory being 2MB aligned. I'm not sure there's a compelling reason to limit the randomization if enabled at all -- either you can handle it or you can't. Are we ever likely to want an offset larger than the memory alignment?
Quoted 13 lines
A reason to limit the randomization is to make it easier on the bootloaders to be able to rule of thumb initial loads. It's not a big deal with Image if it gets loaded lower than the offset, we can start shifting data at the end. But when we start looking at Image.gz (or xz or ...), in some cases we'll get the whole image read into memory (network booting for example), uncompress the first block so we can confirm a good Image header and see about text_offset/image_size. If we know that
text_offsetis somewhere random inside of 2MB (or some other documented max), we can then go with saying an initial load should be at say +32MB (to mirrorDocumentation/arm/Booting). If it can be anywhere, then things get hard, or at least annoying (error out and tell the user to re-load things because of a random value? I can see testing frameworks being annoyed about that).
Ouch, that is somewhat painful.
I don't think we expect to see a text_offset larger than 2MB, and I can't immediately see why we'd care about any particular text offset assuming the page tables are at the end of the runtime image. That said, I'm not completely clear on the history of the text_offset.
Quoted 2 lines
And we should document the range of the offset in
Documentation/arm64/booting.txtas well.
As far as I am aware, we have a 64-bit field specifically to allow for an arbitrarily large value, so placing limitations on that may be a little difficult.
As stated above, I don't think there'd be a reason for having a text_offset larger than our memory alignment restriction (2MB), but there may be something I've missed. If others are reasonably confident with an upper limit, I'd be happy to go with it.
Cheers,
Mark.
Mark Rutland
reviewerPATCH 4/4 arm64: Enable TEXT_OFFSET fuzzing
On Tue, May 20, 2014 at 05:08:27PM +0100, Mark Rutland wrote:
[...]
Quoted 33 lines
Quoted 9 lines
Quoted 8 lines
The 16B increment is required due to some code in
head.S(__turn_mmu_on) requiring a minimum 16B alignment for the object.The 2MB maximum comes from the fact we rely on the start of memory being 2MB aligned. I'm not sure there's a compelling reason to limit the randomization if enabled at all -- either you can handle it or you can't. Are we ever likely to want an offset larger than the memory alignment?
Quoted 13 lines
A reason to limit the randomization is to make it easier on the bootloaders to be able to rule of thumb initial loads. It's not a big deal with Image if it gets loaded lower than the offset, we can start shifting data at the end. But when we start looking at Image.gz (or xz or ...), in some cases we'll get the whole image read into memory (network booting for example), uncompress the first block so we can confirm a good Image header and see about text_offset/image_size. If we know that
text_offsetis somewhere random inside of 2MB (or some other documented max), we can then go with saying an initial load should be at say +32MB (to mirrorDocumentation/arm/Booting). If it can be anywhere, then things get hard, or at least annoying (error out and tell the user to re-load things because of a random value? I can see testing frameworks being annoyed about that).Ouch, that is somewhat painful.
I don't think we expect to see a
text_offsetlarger than 2MB, and I can't immediately see why we'd care about any particular text offset assuming the page tables are at the end of the runtime image. That said, I'm not completely clear on the history of thetext_offset.Quoted 2 lines
And we should document the range of the offset in
Documentation/arm64/booting.txtas well.
Quoted 8 lines
As far as I am aware, we have a 64-bit field specifically to allow for an arbitrarily large value, so placing limitations on that may be a little difficult.
As stated above, I don't think there'd be a reason for having a
text_offsetlarger than our memory alignment restriction (2MB), but there may be something I've missed. If others are reasonably confident with an upper limit, I'd be happy to go with it.
Having thought about it a little more, the primary reason for having text_offset is to allow for memory below the kernel to be addressable. If we decouple the linear mapping from the kernel text mapping this would not be a problem -- we'd be able to load the kernel at any 2MB-aligned address + text_offset and use memory below it.
So I think we could get away with limiting text_offset to 2MB.
Cheers,
Mark.
cover arm64: simplify restrictions on bootloaders
In reply to Mark RutlandQuoted 3 lines · Mark Rutland
Currently bootloaders have an extremely difficult time protecting memory from the kernel, as the kernel may clobber memory below
TEXT_OFFSETwith pagetables, and above the end of the kernel binary with the BSS.
I took a look through this partly with my Xen hat on (Xen effectively contains a bootloader) and partly with my general noseybeak hat on and it all looked pretty sensible to me. So I don't think this change will cause any problems for Xen.
Cheers,
Ian.