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

From: Frederic Weisbecker

Date: Fri Sep 11 2026 - 08:32:23 EST


Le Thu, Sep 10, 2026 at 11:26:38AM -0400, Alan Stern a écrit :
> 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.

Ok that matches my understanding.

>
> > 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?

So in explanations.txt, the different kinds of dependencies are defined
without diving much into properties.

Properties of ordering enforcement tools are typically described in
Documentation/memory-barriers.txt. Control dependencies has its own
section and I suspect there is enough to say about data dependencies
to deserve its own section there.

What I would love to see documented for example is our case: acquire semantics,
which are described to apply one-way from a single memory target, are also
transferrable to other memory targets when there is a data dependency
involved between them.

> 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.

Interesting way of seeing it and I *think* I understand :-)

>
> 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

Got it.

Thanks a lot for taking the time to explain me all that!

--
Frederic Weisbecker
SUSE Labs