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

From: Frederic Weisbecker

Date: Thu Sep 10 2026 - 09:47:54 EST


Le Wed, Sep 09, 2026 at 05:11:27PM -0400, Alan Stern a écrit :
> On Wed, Sep 09, 2026 at 10:49:30PM +0200, Thomas Gleixner wrote:
> > On Wed, Sep 09 2026 at 15:28, Alan Stern wrote:
> > > On Wed, Sep 09, 2026 at 04:45:15PM +0200, Frederic Weisbecker wrote:
> > > I can't tell what you're trying to do here. The UNLOCK-LOCK ordering
> > > in P0 means that P1 sees A=1 before it sees B=1. But nothing in this
> > > litmus test forces P1 to execute READ_ONCE(*b) before READ_ONCE(*A). If
> > > the reads are executed in the opposite order, you can see how P1 might
> > > get r0=1 and r1=0.
> >
> > The problem we are debating is:
> >
> > C = VAL1
> >
> > CPU0 CPU1 CPU2
> >
> > STORE(A0, 0)
> > STORE(A1, 0)
> >
> > LOCK(TLOCK)
> > STORE(B, 1) // 0 -> 1
> > UNLOCK(TLOCK)
> >
> > LOCK(TLOCK)
> > b = LOAD(B)
> > if (b)
> > STORE(C, VAL0)
> >
> > c = LOAD(C)
> > LOCK(c->lock)
> > a0 = LOAD(A0)
> > if (!a0)
> > STORE(A0, X1)
> > STORE(A1, X2)
> >
> > The question is whether CPU2 can observe C == VAL0 and A0 == NULL before
> > A1 has completed.
> >
> > My and Peter's argument is that the sequence
> >
> > UNLOCK(TLOCK) on CPU0 -> LOCK(TLOCK) on CPU1
> >
> > implies RCtso and therefore the stores to A0 and A1 on CPU0 must be
> > before the store to C on CPU1.
> >
> > Now because the LOAD(C) on CPU2 depends on that STORE(C) the
> > LOCK(c->lock) ensures that LOAD(A0) can't be reordered and because of
> > that STORE(A1, 0) has completed before that.
> >
> > CPU2 LOAD(C) observing VAL0 has a data dependency on the STORE(C, VAL0)
> > on CPU1, which as argued above can only happen after the UNLOCK/LOCK
> > sequence CPU1 observes the STORE(B).
> >
> > Subsequently LOCK(c->lock) has a data dependency on LOAD(C) and the LOCK
> > operation prevents that LOAD(A0) can be reordered before LOCK(c->lock).
> >
> > So despite the fact that c->lock != TLOCK the UNLOCK(TLOCK)/LOCK(TLOCK)
> > sequence, which implies RCtso, the following takes care of it:
> >
> > 1) the data dependency between the STORE(C, VAL0) on CPU1 and the
> > c = LOAD(C) on CPU2 observing VAL0
> >
> > 2) the data dependency of LOCK(c->lock) on #1
> >
> > 3) due to LOCK() in #2 LOAD(A0) cannot observe the STORE(A0, 0) on
> > CPU0 without the STORE(A1, 0) on CPU0 has completed.
> >
> > If #3 can happen then that would obviously cause undebuggable data
> > corruption.
> >
> > I hope this is understandable enough despite my brain having melted
> > several times by now while writing it up.
>
> I see. Yes, your analysis is right. And Frederic's latest LKML litmus
> test confirms the result.

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?

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

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?

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.

Thanks.