From 16b21287ee99c4f79d49f1840dddb8cc050abee1 Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Tue, 23 Jun 2026 14:01:58 -0400 Subject: [PATCH] urcu-pointer: Fix missing memory ordering in rcu_dereference_sym() The non-LGPL rcu_dereference() macro reads the RCU-protected pointer with a plain (non-atomic, non-volatile) load at the call site and passes the resulting value to rcu_dereference_sym(). Because the load happens in the caller, before the library function is entered, two distinct guarantees of rcu_dereference() are lost: - Atomicity of the load. The pointer is read with an ordinary C access instead of a volatile/atomic one, so the compiler is free to tear, fuse, duplicate, refetch or value-speculate it; the single-access (READ_ONCE-style) guarantee is gone. The memory_order_consume load inside rcu_dereference_sym() merely operates on a private stack copy of the already-loaded value and changes nothing. - Ordering. Once the address dependency is broken (value speculation, the cmm_ptr_eq() scenario documented in urcu/static/pointer.h, or LTO inlining of this trivial wrapper), a weakly-ordered CPU (e.g. arm64) can reorder the dependent dereference ahead of the pointer load and observe pre-publication data. This patch is a workaround that addresses the *ordering* aspect only: issue a cmm_smp_mb() in rcu_dereference_sym() to order the caller's pointer load (sequenced before the call) before any dependent access performed after the function returns, regardless of whether the address dependency survived. The barrier is elided on x86 (URCU_ARCH_X86): TSO preserves load-to-load and load-to-store ordering, so the pointer load is already ordered before any dependent access, whether that access is a read or a write. The only reordering TSO permits is store-to-load, which is irrelevant here because the pointer access is a load, not a store. The common TSO case therefore pays no overhead. It does NOT restore the atomicity of the load, which still happens as a plain access in the caller; that also remains a data race reported by ThreadSanitizer. Both are fixed by moving the load into the library function, done in the following commit. Signed-off-by: Mathieu Desnoyers Change-Id: I8d91474f25e0ed6ab0ccc7e576027b3f75d0a74b --- src/urcu-pointer.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/urcu-pointer.c b/src/urcu-pointer.c index a42fe87be..0b42f20c1 100644 --- a/src/urcu-pointer.c +++ b/src/urcu-pointer.c @@ -17,9 +17,33 @@ extern void synchronize_rcu(void); +/* + * This wrapper receives the RCU-protected pointer by *value*: the load of + * the pointer therefore already happened in the caller as a plain + * (non-atomic) access, before this function is even entered. As a result + * the compiler may have broken the address dependency between the pointer + * load and its later dereference (e.g. through value speculation, or LTO + * inlining of this trivial wrapper), which lets a weakly-ordered CPU + * reorder the dependent access ahead of the pointer load and observe + * pre-publication data, defeating the memory_order_consume guarantee of + * rcu_dereference(). + * + * Issue a full barrier to order the caller's pointer load (sequenced + * before this call) before any dependent access performed after this + * function returns, restoring the publication ordering regardless of + * whether the dependency survived. This is unneeded on x86 (TSO): the + * hardware preserves load-to-load and load-to-store ordering, so the + * pointer load is already ordered before any dependent access, and the + * bug cannot manifest. (TSO permits only store-to-load reordering, which + * is irrelevant here since the pointer access is a load.) Elide the + * barrier there to avoid penalising the common case. + */ void *rcu_dereference_sym(void *p) { - return _rcu_dereference(p); +#ifndef URCU_ARCH_X86 + cmm_smp_mb(); +#endif + return p; } void *rcu_set_pointer_sym(void **p, void *v)