https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126791
Bug ID: 126791
Summary: linux/ target use-after-free possibility with
`futex_wake` in `gomp_barrier_wait_last`
Product: gcc
Version: 16.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: libgomp
Assignee: unassigned at gcc dot gnu.org
Reporter: matmal01 at gcc dot gnu.org
CC: jakub at gcc dot gnu.org
Target Milestone: ---
`gomp_team_end` is used to end teams.
When it's ending a nested team it uses synchronization based on
`gomp_barrier_wait_last` and `gomp_barrier_wait` in order to ensure that all
secondary threads have entered the barrier before the primary thread running
this function frees the team (including the barrier).
A secondary thread calling `gomp_barrier_wait_last` may be the last thread to
enter the barrier.
In this case said secondary would be the thread to signal barrier completion by
incrementing `bar->generation`.
After it signals barrier completion it performs `futex_wake` on that same
location.
The gap between the increment of the generation and the `futex_wake` could
technically allow the primary thread to:
1) Notice that the barrier has completed.
2) Progress through `gomp_team_end`, and call `free` on the team (containing
the barrier).
In this case the secondary thread would call `futex_wake` on some memory that
has been freed.
------------
I suspect the cleanest way to fix this would be to introduce something like
`gomp_barrier_wait_until_last` to pair with `gomp_barrier_wait_last`. This
interface could avoids the race condition by having the `wait_until_last`
version not call `futex_wait` and correspondingly the code path where the
secondary could increment `bar->generation` from `wait_last` not call
`futex_wake`.
My rationale being that permanent spinning only on the primary thread and for
what should be a short time (the secondary threads not performing any work in
between the last barrier and the `_last` barrier) would be preferred to a racey
possibility of use-after-free.
A similar problem can be seen in `gomp_free_thread` and `gomp_pause_host` (that
use the `gomp_barrier_wait_last` sync).
------------
A slightly different version of the same problem can be seen with a worker
excluded by a team-shrink where the secondary increments the
`pool->threads_dock` generation and there is no synchronization with this
secondary and any other threads before the `futex_wake` that the secondary
could perform.
If the pool gets freed before the `futex_wake` (e.g. with `gomp_pause_host`),
then the secondary can call `futex_wake` on a freed piece of data.
This is a little more tricky to handle, since it's not using the existing
`gomp_barrier_wait_last` interface and the barrier wait is also in the
hot-path.
FWIW I suspect that with the `linux/futex_waitv` target that I'm working on the
fact that only threads starting or exiting use the `threads_dock` barrier would
make the fix more straight-forward for that target (I believe could use
something like the `wait_last` versions when we know the given thread is
exiting).