[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