[RFC PATCH 1/3] ARM: kexec: Make kexec work with read-only kernel .text section
Kees Cook
keescook at chromium.org
Wed Apr 9 11:13:41 PDT 2014
On Wed, Apr 9, 2014 at 9:50 AM, Nikolay Borisov <Nikolay.Borisov at arm.com> wrote:
> Currently, as part of preparing the machine to boot a new kernel, Arm's kexec
> backend will patch some memory location which are allocated in the kernel's
> .text section. This will work only if the aforementioned section is not set
> to read-only, otherwise an OOPS will be produced and kexec won't work.
>
> This patch modifies the way those memory location are being patched by first
> copying them from the .text section (the template) to a special buffer and
> then patching the values in the already-copied buffer, thus obviating the
> need to touch the kernel's code section.
>
> Finally, given the fact that code now works irrespective whether the kexec stub
> is RO or not just move it to .rodata
>
> Signed-off-by: Nikolay Borisov <Nikolay.Borisov at arm.com>
Thanks for working on this!
Reviewed-by: Kees Cook <keescook at chromium.org>
-Kees
> ---
> arch/arm/kernel/machine_kexec.c | 58 +++++++++++++++++++++++++++++----------
> arch/arm/kernel/relocate_kernel.S | 3 ++
> 2 files changed, 46 insertions(+), 15 deletions(-)
>
> diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c
> index f0d180d..ee55e2e 100644
> --- a/arch/arm/kernel/machine_kexec.c
> +++ b/arch/arm/kernel/machine_kexec.c
> @@ -22,13 +22,15 @@
> extern void relocate_new_kernel(void);
> extern const unsigned int relocate_new_kernel_size;
>
> -extern unsigned long kexec_start_address;
> -extern unsigned long kexec_indirection_page;
> -extern unsigned long kexec_mach_type;
> -extern unsigned long kexec_boot_atags;
> +extern const unsigned long kexec_start_address;
> +extern const unsigned long kexec_indirection_page;
> +extern const unsigned long kexec_mach_type;
> +extern const unsigned long kexec_boot_atags;
>
> static atomic_t waiting_for_crash_ipi;
>
> +static unsigned long dt_mem;
> +
> /*
> * Provide a dummy crash_notes definition while crash dump arrives to arm.
> * This prevents breakage of crash_notes attribute in kernel/ksysfs.c.
> @@ -63,8 +65,13 @@ int machine_kexec_prepare(struct kimage *image)
> if (err)
> return err;
>
> + /*
> + * We used to write directly to kexec_atag_boots
> + * this is forbidden when .text section is RO, cache it
> + * in a static var instead
> + */
> if (be32_to_cpu(header) == OF_DT_HEADER)
> - kexec_boot_atags = current_segment->mem;
> + dt_mem = current_segment->mem;
> }
> return 0;
> }
> @@ -139,9 +146,36 @@ void machine_crash_shutdown(struct pt_regs *regs)
> */
> void (*kexec_reinit)(void);
>
> +/*
> + * Instead of patching the kernel .text (which might be Read-only by
> + * CONFIG_DEBUG_RODATA), patch the already-copied template.
> + */
> +static void patch_boot_parameters(char *copied_template, struct kimage *image) {
> +
> + unsigned long page_list = image->head & PAGE_MASK;
> + uintptr_t base = (uintptr_t)relocate_new_kernel & ~(uintptr_t)1;
> + uintptr_t start_addr_offset = (uintptr_t)&kexec_start_address - base;
> + uintptr_t indir_page_offset = (uintptr_t)&kexec_indirection_page - base;
> + uintptr_t mach_type_offset = (uintptr_t)&kexec_mach_type - base;
> + uintptr_t boot_atags_offset = (uintptr_t)&kexec_boot_atags - base;
> +
> +#define patch_value(offset,res) \
> + *(unsigned long *)(copied_template + (offset)) = (res)
> +
> + patch_value(start_addr_offset, image->start);
> + patch_value(indir_page_offset, page_list);
> + patch_value(mach_type_offset, machine_arch_type);
> +
> + if (!dt_mem)
> + patch_value(boot_atags_offset, image->start -
> + KEXEC_ARM_ZIMAGE_OFFSET +
> + KEXEC_ARM_ATAGS_OFFSET);
> + else
> + patch_value(boot_atags_offset, dt_mem);
> +}
> +
> void machine_kexec(struct kimage *image)
> {
> - unsigned long page_list;
> unsigned long reboot_code_buffer_phys;
> unsigned long reboot_entry = (unsigned long)relocate_new_kernel;
> unsigned long reboot_entry_phys;
> @@ -155,21 +189,12 @@ void machine_kexec(struct kimage *image)
> */
> BUG_ON(num_online_cpus() > 1);
>
> - page_list = image->head & PAGE_MASK;
>
> /* we need both effective and real address here */
> reboot_code_buffer_phys =
> page_to_pfn(image->control_code_page) << PAGE_SHIFT;
> reboot_code_buffer = page_address(image->control_code_page);
>
> - /* Prepare parameters for reboot_code_buffer*/
> - kexec_start_address = image->start;
> - kexec_indirection_page = page_list;
> - kexec_mach_type = machine_arch_type;
> - if (!kexec_boot_atags)
> - kexec_boot_atags = image->start - KEXEC_ARM_ZIMAGE_OFFSET + KEXEC_ARM_ATAGS_OFFSET;
> -
> -
> /* copy our kernel relocation code to the control code page */
> reboot_entry = fncpy(reboot_code_buffer,
> reboot_entry,
> @@ -177,6 +202,9 @@ void machine_kexec(struct kimage *image)
> reboot_entry_phys = (unsigned long)reboot_entry +
> (reboot_code_buffer_phys - (unsigned long)reboot_code_buffer);
>
> + /* Prepare parameters for reboot_code_buffer*/
> + patch_boot_parameters(reboot_code_buffer, image);
> +
> printk(KERN_INFO "Bye!\n");
>
> if (kexec_reinit)
> diff --git a/arch/arm/kernel/relocate_kernel.S b/arch/arm/kernel/relocate_kernel.S
> index 9585896..a8eb0e0 100644
> --- a/arch/arm/kernel/relocate_kernel.S
> +++ b/arch/arm/kernel/relocate_kernel.S
> @@ -5,6 +5,9 @@
> #include <linux/linkage.h>
> #include <asm/kexec.h>
>
> +/* Exectution of this template in place is a bug: make it non-executable: */
> + .section .rodata, "a"
> +
> .align 3 /* not needed for this code, but keeps fncpy() happy */
>
> ENTRY(relocate_new_kernel)
> --
> 1.8.1.5
>
>
--
Kees Cook
Chrome OS Security
More information about the linux-arm-kernel
mailing list