Re: [PATCH 3/3] exec: Transform exec_update_mutex into a rw_semaphore

From: Linus Torvalds
Date: Fri Dec 04 2020 - 15:12:17 EST


On Fri, Dec 4, 2020 at 11:35 AM Eric W. Biederman <ebiederm@xxxxxxxxxxxx> wrote:
>
> From a deadlock perspective the change is strictly better than what we
> have today. The readers will no longer block on each other.

No, agreed, it's better regardless.

> For the specific case that syzbot reported it is readers who were
> blocking on each other so that specific case if fixed.

So the thing is, a reader can still block another reader if a writer
comes in between them. Which is why I was thinking that the same
deadlock could happen if somebody does an execve at just the right
point.

> On the write side of exec_update_lock we have:
>
> cred_guard_mutex -> exec_update_lock
>
> Which means that to get an ABBA deadlock cred_guard_mutex would need to
> be involved

No, see above: you can get a deadlock with

- first reader gets exec_update_lock

- writer on exec_update_lock blocks on first reader (this is exec)

- second reader of exec_update_lock now blocks on the writer.

So if that second reader holds something that the first one wants to
get (or is the same thread as the first one), you have a deadlock: the
first reader will never make any progress, will never release the
lock, and the writer will never get it, and the second reader will
forever wait for the writer that is ahead of it in the queue.

cred_guard_mutex is immaterial, it's not involved in the deadlock.
Yes, the writer holds it, but it's not relevant for anything else.

And that deadlock looks very much like what syzcaller detected, in
exactly that scenario:

Chain exists of:
&sig->exec_update_mutex --> sb_writers#4 --> &p->lock

Possible unsafe locking scenario:

CPU0 CPU1
---- ----
lock(&p->lock);
lock(sb_writers#4);
lock(&p->lock);
lock(&sig->exec_update_mutex);

*** DEADLOCK ***

No? The only thing that is missing is that writer that causes the
exec_update_mutex readers to block each other - exactly like they did
when it was a mutex.

But I may be missing something entirely obvious that keeps this from happening.

Linus