Re: [patch V2 1/8] signal: Prevent exec() race

From: Alan Stern

Date: Thu Sep 10 2026 - 11:55:41 EST


On Thu, Sep 10, 2026 at 03:21:54PM +0200, Frederic Weisbecker wrote:
> Alan, let me ask you something, because I'm the only one here puzzled by
> this data dependency.
>
> The following scenario works (the bad outcome never happens) because
> UNLOCK+LOCK pairs with smp_load_acquire():
>
> C MP+polocks
>
> {}
>
> P0(int *A, int *B, spinlock_t *mylock)
> {
> spin_lock(mylock);
> WRITE_ONCE(*A, 1);
> spin_unlock(mylock);
> spin_lock(mylock);
> WRITE_ONCE(*B, 1);
> spin_unlock(mylock);
> }
>
> P1(int *A, int *B)
> {
> int r0;
> int r1;
>
> r0 = smp_load_acquire(B);
> r1 = READ_ONCE(*A);
> }
>
> exists (1:r0=1 /\ 1:r1=0) (* Bad outcome. *)
>
>
> So I understand this one. Now unfortunately litmus doesn't support
> structures, but let's suppose it could. I'm taking the previous script
> and introduce a small change in P1:
>
> C MP+polocks
>
> {}
>
> P0(int *A, int *B, spinlock_t *mylock)
> {
> spin_lock(mylock);
> WRITE_ONCE(*A, 1);
> spin_unlock(mylock);
> spin_lock(mylock);
> WRITE_ONCE(*B, 1);
> spin_unlock(mylock);
> }
>
> P1(int *A, int *B)
> {
> int r0;
> int r1
>
> r0 = READ_ONCE(*B);
> spin_lock(r0->somelock)
> r1 = READ_ONCE(*A);
> spin_unlock(r0->somelock)
> }
>
> exists (1:r0=1 /\ 1:r1=0) (* Bad outcome. *)
>
>
> So instead of doing a LOAD-ACQUIRE on B, I do a plain READ but I also
> do a spin_lock right after on a data that depends on that READ. I can't
> run that on litmus but this is the same (simplified) pattern as what we
> had in this discussion and therefore I assume that it also works (ie: the
> bad outcome shouldn't happen), is that right?

Yes.

> Would it also work if spin_lock() was just a LOAD-ACQUIRE?

Yes.

> Does it mean that data dependency implies sufficient ordering such that
> a LOAD-ACQUIRE to a data that depends on B provides the same guarantees as
> a LOAD-ACQUIRE to B?

Indeed it does, with the obvious exception that a load-acquire of B
also provides ordering to any statements in between it and the load of
the data depending on B. That is:

r0 = smp_load_acquire(B);
X;
r1 = READ_ONCE(r0->A);

orders the load from B before everything that follows, including X,
whereas:

r0 = READ_ONCE(B);
X;
r1 = smp_load_acquire(r0->A);

orders the load from B before the load from r0->A and everything
following it, but not before X.

> The reason I'm asking that is because, unlike control dependency, data
> dependency and its guarantees are not well documented. It is defined in
> tools/memory-model/Documentation/explanation.txt but not really described
> in Documentation/memory-barriers.txt. There is a mention in a scenario within
> the section "MULTICOPY ATOMICITY" just to show that it's not as strong as
> a full memory barrier.
>
> So if data dependency can provide the guarantee above in my second script
> but it's not as strong as a full barrier, this suggests that data dependencies
> have their own specific properties that should probably be documentated.

Perhaps so. Can you suggest a place in explanations.txt that could be
improved?

Here's how I think about ordering guarantees in general. Not in terms
of pairing of memory barriers, since (as you pointed out) dependencies
aren't memory barriers, and also since ordering cycles can involve more
than two CPUs (so triples or higher, not just pairs).

Instead there's a hierarchy of ordering classes. The lowest level only
orders events on a single CPU; it includes dependencies, smp_rmb(), and
load-acquires.

The next level orders cross-CPU events (i.e., writes), but only in a way
that affects two CPUs at a time. It includes things like smp_wmb() and
store-releases, and it guarantees that if CPU 1 writes A first and B
second, then CPU 2 will observe the store to A before it observes the
store to B. Likewise for CPU 3, CPU 4, etc., but there is no guarantee
about the order in which differing CPUs will observe the stores.

The highest level orders events in a way that involves all CPUs. It
includes things like smp_mb() and synchronize_rcu(), and it says that if
CPU 1 writes A first and B second, then _every_ CPU will observe the
store to A before _any_ CPU (including CPU 1!) observes the store to B.

This is a little imprecise, and there are varying details within the
levels, but the overall idea is basically right.

At any rate, the point you're raising is that dependencies and
load-acquires both sit at the lowest level of this hierarchy, so they
provide pretty much the same ordering guarantees.

Alan Stern