Skip to content

Commit 5d15d2a

Browse files
JingwiwPaul Walmsley
authored andcommitted
riscv: hwprobe: Fix stale vDSO data for late-initialized keys at boot
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 <[email protected]> Closes: https://lore.kernel.org/linux-riscv/[email protected]/ Fixes: e7c9d66 ("RISC-V: Report vector unaligned access speed hwprobe") Cc: Palmer Dabbelt <[email protected]> Cc: Alexandre Ghiti <[email protected]> Cc: Olof Johansson <[email protected]> Cc: [email protected] Reviewed-by: Alexandre Ghiti <[email protected]> Co-developed-by: Palmer Dabbelt <[email protected]> Signed-off-by: Palmer Dabbelt <[email protected]> Signed-off-by: Jingwei Wang <[email protected]> Link: https://lore.kernel.org/r/[email protected] [[email protected]: fix checkpatch issues] Signed-off-by: Paul Walmsley <[email protected]>
1 parent 492c513 commit 5d15d2a

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
@@ -42,4 +42,11 @@ static inline bool riscv_hwprobe_pair_cmp(struct riscv_hwprobe *pair,
4242
return pair->value == other_pair->value;
4343
}
4444

45+
#ifdef CONFIG_MMU
46+
void riscv_hwprobe_register_async_probe(void);
47+
void riscv_hwprobe_complete_async_probe(void);
48+
#else
49+
static inline void riscv_hwprobe_register_async_probe(void) {}
50+
static inline void riscv_hwprobe_complete_async_probe(void) {}
51+
#endif
4552
#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>
@@ -454,28 +457,32 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs,
454457
return 0;
455458
}
456459

457-
static int do_riscv_hwprobe(struct riscv_hwprobe __user *pairs,
458-
size_t pair_count, size_t cpusetsize,
459-
unsigned long __user *cpus_user,
460-
unsigned int flags)
461-
{
462-
if (flags & RISCV_HWPROBE_WHICH_CPUS)
463-
return hwprobe_get_cpus(pairs, pair_count, cpusetsize,
464-
cpus_user, flags);
460+
#ifdef CONFIG_MMU
465461

466-
return hwprobe_get_values(pairs, pair_count, cpusetsize,
467-
cpus_user, flags);
462+
static DECLARE_COMPLETION(boot_probes_done);
463+
static atomic_t pending_boot_probes = ATOMIC_INIT(1);
464+
465+
void riscv_hwprobe_register_async_probe(void)
466+
{
467+
atomic_inc(&pending_boot_probes);
468468
}
469469

470-
#ifdef CONFIG_MMU
470+
void riscv_hwprobe_complete_async_probe(void)
471+
{
472+
if (atomic_dec_and_test(&pending_boot_probes))
473+
complete(&boot_probes_done);
474+
}
471475

472-
static int __init init_hwprobe_vdso_data(void)
476+
static int complete_hwprobe_vdso_data(void)
473477
{
474478
struct vdso_arch_data *avd = vdso_k_arch_data;
475479
u64 id_bitsmash = 0;
476480
struct riscv_hwprobe pair;
477481
int key;
478482

483+
if (unlikely(!atomic_dec_and_test(&pending_boot_probes)))
484+
wait_for_completion(&boot_probes_done);
485+
479486
/*
480487
* Initialize vDSO data with the answers for the "all CPUs" case, to
481488
* save a syscall in the common case.
@@ -503,13 +510,52 @@ static int __init init_hwprobe_vdso_data(void)
503510
* vDSO should defer to the kernel for exotic cpu masks.
504511
*/
505512
avd->homogeneous_cpus = id_bitsmash != 0 && id_bitsmash != -1;
513+
514+
/*
515+
* Make sure all the VDSO values are visible before we look at them.
516+
* This pairs with the implicit "no speculativly visible accesses"
517+
* barrier in the VDSO hwprobe code.
518+
*/
519+
smp_wmb();
520+
avd->ready = true;
521+
return 0;
522+
}
523+
524+
static int __init init_hwprobe_vdso_data(void)
525+
{
526+
struct vdso_arch_data *avd = vdso_k_arch_data;
527+
528+
/*
529+
* Prevent the vDSO cached values from being used, as they're not ready
530+
* yet.
531+
*/
532+
avd->ready = false;
506533
return 0;
507534
}
508535

509536
arch_initcall_sync(init_hwprobe_vdso_data);
510537

538+
#else
539+
540+
static int complete_hwprobe_vdso_data(void) { return 0; }
541+
511542
#endif /* CONFIG_MMU */
512543

544+
static int do_riscv_hwprobe(struct riscv_hwprobe __user *pairs,
545+
size_t pair_count, size_t cpusetsize,
546+
unsigned long __user *cpus_user,
547+
unsigned int flags)
548+
{
549+
DO_ONCE_SLEEPABLE(complete_hwprobe_vdso_data);
550+
551+
if (flags & RISCV_HWPROBE_WHICH_CPUS)
552+
return hwprobe_get_cpus(pairs, pair_count, cpusetsize,
553+
cpus_user, flags);
554+
555+
return hwprobe_get_values(pairs, pair_count, cpusetsize,
556+
cpus_user, flags);
557+
}
558+
513559
SYSCALL_DEFINE5(riscv_hwprobe, struct riscv_hwprobe __user *, pairs,
514560
size_t, pair_count, size_t, cpusetsize, unsigned long __user *,
515561
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)