[PATCH v2 03/11] arm64: Add logic to fully remove features from sanitised id registers

Fuad Tabba tabba at google.com
Mon Mar 2 06:57:21 PST 2026


Hi Marc,

On Mon, 2 Mar 2026 at 11:57, Marc Zyngier <maz at kernel.org> wrote:
>
> We currently make support for some features such as Pointer Auth,
> SVE or S1POE a compile time decision.
>
> However, while we hide that feature from userspace when such support
> is disabled, we still leave the value provided by the HW visible to
> the rest of the kernel, including KVM.
>
> This has the potential to result in ugly state leakage, as half of
> the kernel knows about the feature, and the other doesn't.
>
> Short of completely banning such compilation options and restore
> universal knowledge, introduce the possibility to fully remove such
> knowledge from the sanitised id registers.
>
> This has more or less the same effect as the idreg override that
> a user can pass on the command-line, only defined at build-time.
>
> For that purpose, we provide a new macro (FTR_CONFIG()) that defines
> the behaviour of a feature, both when enabled and disabled.
>
> At this stage, nothing is making use of this anti-feature.
>
> Signed-off-by: Marc Zyngier <maz at kernel.org>
> ---
>  arch/arm64/include/asm/cpufeature.h | 17 +++++++++------
>  arch/arm64/kernel/cpufeature.c      | 32 ++++++++++++++++++++++-------
>  2 files changed, 36 insertions(+), 13 deletions(-)
>
> diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
> index 4de51f8d92cba..e853a0ac7db38 100644
> --- a/arch/arm64/include/asm/cpufeature.h
> +++ b/arch/arm64/include/asm/cpufeature.h
> @@ -53,17 +53,22 @@ enum ftr_type {
>  #define FTR_SIGNED     true    /* Value should be treated as signed */
>  #define FTR_UNSIGNED   false   /* Value should be treated as unsigned */
>
> -#define FTR_VISIBLE    true    /* Feature visible to the user space */
> -#define FTR_HIDDEN     false   /* Feature is hidden from the user */
> +enum ftr_visibility {
> +       FTR_HIDDEN,             /* Feature hidden from the user */
> +       FTR_ALL_HIDDEN,         /* Feature hidden from kernel, user and KVM */
> +       FTR_VISIBLE,            /* Feature visible to all observers */
> +};
> +
> +#define FTR_CONFIG(c, e, d)                            \
> +       (IS_ENABLED(c) ? FTR_ ## e : FTR_ ## d)
>
> -#define FTR_VISIBLE_IF_IS_ENABLED(config)              \
> -       (IS_ENABLED(config) ? FTR_VISIBLE : FTR_HIDDEN)
> +#define FTR_VISIBLE_IF_IS_ENABLED(c)   FTR_CONFIG(c, VISIBLE, HIDDEN)
>
>  struct arm64_ftr_bits {
>         bool            sign;   /* Value is signed ? */
> -       bool            visible;
> +       enum ftr_visibility visibility:8;
>         bool            strict; /* CPU Sanity check: strict matching required ? */
> -       enum ftr_type   type;
> +       enum ftr_type   type:8;
>         u8              shift;
>         u8              width;
>         s64             safe_val; /* safe value for FTR_EXACT features */
> diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
> index 102c5bac4d502..965dd2acf0640 100644
> --- a/arch/arm64/kernel/cpufeature.c
> +++ b/arch/arm64/kernel/cpufeature.c
> @@ -192,7 +192,7 @@ void dump_cpu_features(void)
>  #define __ARM64_FTR_BITS(SIGNED, VISIBLE, STRICT, TYPE, SHIFT, WIDTH, SAFE_VAL) \
>         {                                               \
>                 .sign = SIGNED,                         \
> -               .visible = VISIBLE,                     \
> +               .visibility = VISIBLE,                  \
>                 .strict = STRICT,                       \
>                 .type = TYPE,                           \
>                 .shift = SHIFT,                         \
> @@ -1063,16 +1063,33 @@ static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
>                                 ftrp->shift);
>                 }
>
> -               val = arm64_ftr_set_value(ftrp, val, ftr_new);
> -
>                 valid_mask |= ftr_mask;
>                 if (!ftrp->strict)
>                         strict_mask &= ~ftr_mask;
> -               if (ftrp->visible)
> +
> +               switch (ftrp->visibility) {
> +               case FTR_VISIBLE:
> +                       val = arm64_ftr_set_value(ftrp, val, ftr_new);
>                         user_mask |= ftr_mask;
> -               else
> +                       break;
> +               case FTR_ALL_HIDDEN:
> +                       /*
> +                        * ALL_HIDDEN and HIGHER_SAFE are incompatible.
> +                        * Only hide from userspace, and log the oddity.
> +                        */
> +                       if (WARN_ON(ftrp->type == FTR_HIGHER_SAFE))

What about FTR_HIGHER_OR_ZERO_SAFE? It's not actually being used for
any feature id register, only for CTR_EL0. Even there, it's a
(theoretical at this point) performance issue, not a correctness one,
as 0 assumes the largest possible size. I still think that it's safer
to include it here.

That said:

Reviewed-by: Fuad Tabba <tabba at google.com>

Cheers,
/fuad



> +                               val = arm64_ftr_set_value(ftrp, val, ftr_new);
> +                       else
> +                               val = arm64_ftr_set_safe_value(ftrp, val);
>                         reg->user_val = arm64_ftr_set_safe_value(ftrp,
>                                                                  reg->user_val);
> +                       break;
> +               case FTR_HIDDEN:
> +                       val = arm64_ftr_set_value(ftrp, val, ftr_new);
> +                       reg->user_val = arm64_ftr_set_safe_value(ftrp,
> +                                                                reg->user_val);
> +                       break;
> +               }
>         }
>
>         val &= valid_mask;
> @@ -1230,9 +1247,10 @@ static void update_cpu_ftr_reg(struct arm64_ftr_reg *reg, u64 new)
>
>                 /*
>                  * Don't alter the initial value that has been forced
> -                * by an override.
> +                * by an override or a disabled feature.
>                  */
> -               if ((reg->override->mask & arm64_ftr_mask(ftrp)) == arm64_ftr_mask(ftrp))
> +               if (ftrp->visibility == FTR_ALL_HIDDEN ||
> +                   (reg->override->mask & arm64_ftr_mask(ftrp)) == arm64_ftr_mask(ftrp))
>                         continue;
>
>                 if (ftr_cur == ftr_new)
> --
> 2.47.3
>



More information about the linux-arm-kernel mailing list