Skip to content

Commit 6dc7696

Browse files
Jingwiwgregkh
authored andcommitted
riscv: hwprobe: Fix stale vDSO data for late-initialized keys at boot
commit 5d15d2a upstream. The hwprobe vDSO data for some keys, like MISALIGNED_VECTOR_PERF, is determined by an asynchronous kthread. This can create a race condition where the kthread finishes after the vDSO data has already been populated, causing userspace to read stale values. To fix this race, a new 'ready' flag is added to the vDSO data, initialized to 'false' during arch_initcall_sync. This flag is checked by both the vDSO's user-space code and the riscv_hwprobe syscall. The syscall serves as a one-time gate, using a completion to wait for any pending probes before populating the data and setting the flag to 'true', thus ensuring userspace reads fresh values on its first request. Reported-by: Tsukasa OI <research_trasio@irq.a4lg.com> Closes: https://lore.kernel.org/linux-riscv/760d637b-b13b-4518-b6bf-883d55d44e7f@irq.a4lg.com/ Fixes: e7c9d66 ("RISC-V: Report vector unaligned access speed hwprobe") Cc: Palmer Dabbelt <palmer@dabbelt.com> Cc: Alexandre Ghiti <alexghiti@rivosinc.com> Cc: Olof Johansson <olof@lixom.net> Cc: stable@vger.kernel.org Reviewed-by: Alexandre Ghiti <alexghiti@rivosinc.com> Co-developed-by: Palmer Dabbelt <palmer@dabbelt.com> Signed-off-by: Palmer Dabbelt <palmer@dabbelt.com> Signed-off-by: Jingwei Wang <wangjingwei@iscas.ac.cn> Link: https://lore.kernel.org/r/20250811142035.105820-1-wangjingwei@iscas.ac.cn [pjw@kernel.org: fix checkpatch issues] Signed-off-by: Paul Walmsley <pjw@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 3a01b26 commit 6dc7696

File tree

5 files changed

+79
-15
lines changed

5 files changed

+79
-15
lines changed

arch/riscv/include/asm/hwprobe.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,11 @@ static inline bool riscv_hwprobe_pair_cmp(struct riscv_hwprobe *pair,
4141
return pair->value == other_pair->value;
4242
}
4343

44+
#ifdef CONFIG_MMU
45+
void riscv_hwprobe_register_async_probe(void);
46+
void riscv_hwprobe_complete_async_probe(void);
47+
#else
48+
static inline void riscv_hwprobe_register_async_probe(void) {}
49+
static inline void riscv_hwprobe_complete_async_probe(void) {}
50+
#endif
4451
#endif

arch/riscv/include/asm/vdso/arch_data.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ struct vdso_arch_data {
1212

1313
/* Boolean indicating all CPUs have the same static hwprobe values. */
1414
__u8 homogeneous_cpus;
15+
16+
/*
17+
* A gate to check and see if the hwprobe data is actually ready, as
18+
* probing is deferred to avoid boot slowdowns.
19+
*/
20+
__u8 ready;
1521
};
1622

1723
#endif /* __RISCV_ASM_VDSO_ARCH_DATA_H */

arch/riscv/kernel/sys_hwprobe.c

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
* more details.
66
*/
77
#include <linux/syscalls.h>
8+
#include <linux/completion.h>
9+
#include <linux/atomic.h>
10+
#include <linux/once.h>
811
#include <asm/cacheflush.h>
912
#include <asm/cpufeature.h>
1013
#include <asm/hwprobe.h>
@@ -450,28 +453,32 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs,
450453
return 0;
451454
}
452455

453-
static int do_riscv_hwprobe(struct riscv_hwprobe __user *pairs,
454-
size_t pair_count, size_t cpusetsize,
455-
unsigned long __user *cpus_user,
456-
unsigned int flags)
457-
{
458-
if (flags & RISCV_HWPROBE_WHICH_CPUS)
459-
return hwprobe_get_cpus(pairs, pair_count, cpusetsize,
460-
cpus_user, flags);
456+
#ifdef CONFIG_MMU
461457

462-
return hwprobe_get_values(pairs, pair_count, cpusetsize,
463-
cpus_user, flags);
458+
static DECLARE_COMPLETION(boot_probes_done);
459+
static atomic_t pending_boot_probes = ATOMIC_INIT(1);
460+
461+
void riscv_hwprobe_register_async_probe(void)
462+
{
463+
atomic_inc(&pending_boot_probes);
464464
}
465465

466-
#ifdef CONFIG_MMU
466+
void riscv_hwprobe_complete_async_probe(void)
467+
{
468+
if (atomic_dec_and_test(&pending_boot_probes))
469+
complete(&boot_probes_done);
470+
}
467471

468-
static int __init init_hwprobe_vdso_data(void)
472+
static int complete_hwprobe_vdso_data(void)
469473
{
470474
struct vdso_arch_data *avd = vdso_k_arch_data;
471475
u64 id_bitsmash = 0;
472476
struct riscv_hwprobe pair;
473477
int key;
474478

479+
if (unlikely(!atomic_dec_and_test(&pending_boot_probes)))
480+
wait_for_completion(&boot_probes_done);
481+
475482
/*
476483
* Initialize vDSO data with the answers for the "all CPUs" case, to
477484
* save a syscall in the common case.
@@ -499,13 +506,52 @@ static int __init init_hwprobe_vdso_data(void)
499506
* vDSO should defer to the kernel for exotic cpu masks.
500507
*/
501508
avd->homogeneous_cpus = id_bitsmash != 0 && id_bitsmash != -1;
509+
510+
/*
511+
* Make sure all the VDSO values are visible before we look at them.
512+
* This pairs with the implicit "no speculativly visible accesses"
513+
* barrier in the VDSO hwprobe code.
514+
*/
515+
smp_wmb();
516+
avd->ready = true;
517+
return 0;
518+
}
519+
520+
static int __init init_hwprobe_vdso_data(void)
521+
{
522+
struct vdso_arch_data *avd = vdso_k_arch_data;
523+
524+
/*
525+
* Prevent the vDSO cached values from being used, as they're not ready
526+
* yet.
527+
*/
528+
avd->ready = false;
502529
return 0;
503530
}
504531

505532
arch_initcall_sync(init_hwprobe_vdso_data);
506533

534+
#else
535+
536+
static int complete_hwprobe_vdso_data(void) { return 0; }
537+
507538
#endif /* CONFIG_MMU */
508539

540+
static int do_riscv_hwprobe(struct riscv_hwprobe __user *pairs,
541+
size_t pair_count, size_t cpusetsize,
542+
unsigned long __user *cpus_user,
543+
unsigned int flags)
544+
{
545+
DO_ONCE_SLEEPABLE(complete_hwprobe_vdso_data);
546+
547+
if (flags & RISCV_HWPROBE_WHICH_CPUS)
548+
return hwprobe_get_cpus(pairs, pair_count, cpusetsize,
549+
cpus_user, flags);
550+
551+
return hwprobe_get_values(pairs, pair_count, cpusetsize,
552+
cpus_user, flags);
553+
}
554+
509555
SYSCALL_DEFINE5(riscv_hwprobe, struct riscv_hwprobe __user *, pairs,
510556
size_t, pair_count, size_t, cpusetsize, unsigned long __user *,
511557
cpus, unsigned int, flags)

arch/riscv/kernel/unaligned_access_speed.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ static void check_vector_unaligned_access(struct work_struct *work __always_unus
379379
static int __init vec_check_unaligned_access_speed_all_cpus(void *unused __always_unused)
380380
{
381381
schedule_on_each_cpu(check_vector_unaligned_access);
382+
riscv_hwprobe_complete_async_probe();
382383

383384
return 0;
384385
}
@@ -473,8 +474,12 @@ static int __init check_unaligned_access_all_cpus(void)
473474
per_cpu(vector_misaligned_access, cpu) = unaligned_vector_speed_param;
474475
} else if (!check_vector_unaligned_access_emulated_all_cpus() &&
475476
IS_ENABLED(CONFIG_RISCV_PROBE_VECTOR_UNALIGNED_ACCESS)) {
476-
kthread_run(vec_check_unaligned_access_speed_all_cpus,
477-
NULL, "vec_check_unaligned_access_speed_all_cpus");
477+
riscv_hwprobe_register_async_probe();
478+
if (IS_ERR(kthread_run(vec_check_unaligned_access_speed_all_cpus,
479+
NULL, "vec_check_unaligned_access_speed_all_cpus"))) {
480+
pr_warn("Failed to create vec_unalign_check kthread\n");
481+
riscv_hwprobe_complete_async_probe();
482+
}
478483
}
479484

480485
/*

arch/riscv/kernel/vdso/hwprobe.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count,
2727
* homogeneous, then this function can handle requests for arbitrary
2828
* masks.
2929
*/
30-
if ((flags != 0) || (!all_cpus && !avd->homogeneous_cpus))
30+
if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready))
3131
return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags);
3232

3333
/* This is something we can handle, fill out the pairs. */

0 commit comments

Comments
 (0)