[RFC PATCH] arm64: Bring back linear map randomization using PArange override

Seth Jenkins sethjenkins at google.com
Tue Dec 16 10:13:49 PST 2025


> This is posted as an RFC because there are obvious shortcomings to this
> approach. However, before I spend more time on this, I'd like to gauge
> if there is any consensus that bringing this back is a good idea.
I may be a minority, but I do think bringing this back is a good idea.
It has now been 9+ years since hardware side channels for bypassing
KASLR were discovered, and it still appears that real-world attackers
overwhelmingly prefer alternative strategies - such as this one.

I'm under no illusion that if we just do this one thing KASLR will be
secure again, but I think this patch would still have a meaningful
real-world impact in terms of making exploits significantly harder to
write.


On Wed, Dec 10, 2025 at 11:10 PM Ard Biesheuvel <ardb at kernel.org> wrote:
>
> Commit
>
>   1db780bafa4ce ("arm64/mm: Remove randomization of the linear map")
>
> removed linear map randomization from the arm64 port, on the basis that
> a prior change to the logic rendered it non-functional on the majority
> of relevant CPU implementations.
>
> As has been reported numerous times now, the upshot of this is that the
> virtual addresses of statically allocated kernel data structures are
> highly predictable if the kernel is loaded at a known physical address.
> Any bootloader that still adheres to the original arm64 boot protocol,
> which stipulated that the kernel should be loaded at the lowest
> available physical address, is affected by this.
>
> So bring back the most recent version of linear map randomization, which
> is based on the CPU's physical address range, but this time, allow this
> PA range to be overridden on the kernel command line.
>
> E.g., by passing
>
>   id_aa64mmfr0.parange=1 # 36 bits
>   id_aa64mmfr0.parange=2 # 40 bits
>
> the CPU's supported physical range can be reduced to the point where
> linear map randomization becomes feasible again. It also means that
> nothing else is permitted to appear in that physical window, i.e.,
> hotplug memory but also non-memory peripherals, or stage-2 mappings on
> behalf of KVM guests.
>
> Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
> ---
> Cc: Liz Prucka <lizprucka at google.com>
> Cc: Seth Jenkins <sethjenkins at google.com>
>
> This is posted as an RFC because there are obvious shortcomings to this
> approach. However, before I spend more time on this, I'd like to gauge
> if there is any consensus that bringing this back is a good idea.
>
>  arch/arm64/include/asm/cpufeature.h   | 13 +++++++++++++
>  arch/arm64/kernel/image-vars.h        |  1 +
>  arch/arm64/kernel/kaslr.c             |  2 ++
>  arch/arm64/kernel/pi/idreg-override.c |  1 +
>  arch/arm64/kernel/pi/kaslr_early.c    |  4 ++++
>  arch/arm64/mm/init.c                  | 16 ++++++++++++++++
>  6 files changed, 37 insertions(+)
>
> diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
> index 4de51f8d92cb..fdb1331c406d 100644
> --- a/arch/arm64/include/asm/cpufeature.h
> +++ b/arch/arm64/include/asm/cpufeature.h
> @@ -1078,6 +1078,19 @@ static inline bool cpu_has_lpa2(void)
>  #endif
>  }
>
> +static inline u64 cpu_get_phys_range(void)
> +{
> +       u64 mmfr0 = read_sysreg(id_aa64mmfr0_el1);
> +
> +       mmfr0 &= ~id_aa64mmfr0_override.mask;
> +       mmfr0 |= id_aa64mmfr0_override.val;
> +
> +       int parange = cpuid_feature_extract_unsigned_field(mmfr0,
> +                                               ID_AA64MMFR0_EL1_PARANGE_SHIFT);
> +
> +       return BIT(id_aa64mmfr0_parange_to_phys_shift(parange));
> +}
> +
>  #endif /* __ASSEMBLER__ */
>
>  #endif
> diff --git a/arch/arm64/kernel/image-vars.h b/arch/arm64/kernel/image-vars.h
> index 85bc629270bd..263543ad6155 100644
> --- a/arch/arm64/kernel/image-vars.h
> +++ b/arch/arm64/kernel/image-vars.h
> @@ -58,6 +58,7 @@ PI_EXPORT_SYM(id_aa64zfr0_override);
>  PI_EXPORT_SYM(arm64_sw_feature_override);
>  PI_EXPORT_SYM(arm64_use_ng_mappings);
>  PI_EXPORT_SYM(_ctype);
> +PI_EXPORT_SYM(memstart_offset_seed);
>
>  PI_EXPORT_SYM(swapper_pg_dir);
>
> diff --git a/arch/arm64/kernel/kaslr.c b/arch/arm64/kernel/kaslr.c
> index c9503ed45a6c..1da3e25f9d9e 100644
> --- a/arch/arm64/kernel/kaslr.c
> +++ b/arch/arm64/kernel/kaslr.c
> @@ -10,6 +10,8 @@
>  #include <asm/cpufeature.h>
>  #include <asm/memory.h>
>
> +u16 __initdata memstart_offset_seed;
> +
>  bool __ro_after_init __kaslr_is_enabled = false;
>
>  void __init kaslr_init(void)
> diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c
> index bc57b290e5e7..a8351ba70300 100644
> --- a/arch/arm64/kernel/pi/idreg-override.c
> +++ b/arch/arm64/kernel/pi/idreg-override.c
> @@ -43,6 +43,7 @@ static const struct ftr_set_desc mmfr0 __prel64_initconst = {
>         .override       = &id_aa64mmfr0_override,
>         .fields         = {
>                 FIELD("ecv", ID_AA64MMFR0_EL1_ECV_SHIFT, NULL),
> +               FIELD("parange", ID_AA64MMFR0_EL1_PARANGE_SHIFT, NULL),
>                 {}
>         },
>  };
> diff --git a/arch/arm64/kernel/pi/kaslr_early.c b/arch/arm64/kernel/pi/kaslr_early.c
> index e0e018046a46..0257b43819db 100644
> --- a/arch/arm64/kernel/pi/kaslr_early.c
> +++ b/arch/arm64/kernel/pi/kaslr_early.c
> @@ -18,6 +18,8 @@
>
>  #include "pi.h"
>
> +extern u16 memstart_offset_seed;
> +
>  static u64 __init get_kaslr_seed(void *fdt, int node)
>  {
>         static char const seed_str[] __initconst = "kaslr-seed";
> @@ -51,6 +53,8 @@ u64 __init kaslr_early_init(void *fdt, int chosen)
>                         return 0;
>         }
>
> +       memstart_offset_seed = seed & U16_MAX;
> +
>         /*
>          * OK, so we are proceeding with KASLR enabled. Calculate a suitable
>          * kernel image offset from the seed. Let's place the kernel in the
> diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
> index 524d34a0e921..6c55eca6ccad 100644
> --- a/arch/arm64/mm/init.c
> +++ b/arch/arm64/mm/init.c
> @@ -275,6 +275,22 @@ void __init arm64_memblock_init(void)
>                 }
>         }
>
> +       if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
> +               extern u16 memstart_offset_seed;
> +               s64 range = linear_region_size - cpu_get_phys_range();
> +
> +               /*
> +                * If the size of the linear region exceeds, by a sufficient
> +                * margin, the size of the region that the physical memory can
> +                * span, randomize the linear region as well.
> +                */
> +               if (memstart_offset_seed > 0 && range >= (s64)ARM64_MEMSTART_ALIGN) {
> +                       range /= ARM64_MEMSTART_ALIGN;
> +                       memstart_addr -= ARM64_MEMSTART_ALIGN *
> +                                        ((range * memstart_offset_seed) >> 16);
> +               }
> +       }
> +
>         /*
>          * Register the kernel text, kernel data, initrd, and initial
>          * pagetables with memblock.
> --
> 2.47.3
>



More information about the linux-arm-kernel mailing list