]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/commitdiff
locking/qrwlock: Fix ordering in queued_write_lock_slowpath()
authorAli Saidi <alisaidi@amazon.com>
Mon, 26 Apr 2021 21:41:00 +0000 (23:41 +0200)
committerStefan Bader <stefan.bader@canonical.com>
Fri, 7 May 2021 07:54:08 +0000 (09:54 +0200)
BugLink: https://bugs.launchpad.net/bugs/1926184
While this code is executed with the wait_lock held, a reader can
acquire the lock without holding wait_lock.  The writer side loops
checking the value with the atomic_cond_read_acquire(), but only truly
acquires the lock when the compare-and-exchange is completed
successfully which isn’t ordered. This exposes the window between the
acquire and the cmpxchg to an A-B-A problem which allows reads
following the lock acquisition to observe values speculatively before
the write lock is truly acquired.

We've seen a problem in epoll where the reader does a xchg while
holding the read lock, but the writer can see a value change out from
under it.

  Writer                                | Reader
  --------------------------------------------------------------------------------
  ep_scan_ready_list()                  |
  |- write_lock_irq()                   |
      |- queued_write_lock_slowpath()   |
|- atomic_cond_read_acquire()   |
        | read_lock_irqsave(&ep->lock, flags);
     --> (observes value before unlock) |  chain_epi_lockless()
     |                                  |    epi->next = xchg(&ep->ovflist, epi);
     |                                  | read_unlock_irqrestore(&ep->lock, flags);
     |                                  |
     |     atomic_cmpxchg_relaxed()     |
     |-- READ_ONCE(ep->ovflist);        |

A core can order the read of the ovflist ahead of the
atomic_cmpxchg_relaxed(). Switching the cmpxchg to use acquire
semantics addresses this issue at which point the atomic_cond_read can
be switched to use relaxed semantics.

Fixes: b519b56e378ee ("locking/qrwlock: Use atomic_cond_read_acquire() when spinning in qrwlock")
Signed-off-by: Ali Saidi <alisaidi@amazon.com>
[peterz: use try_cmpxchg()]
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Steve Capper <steve.capper@arm.com>
Acked-by: Will Deacon <will@kernel.org>
Acked-by: Waiman Long <longman@redhat.com>
Tested-by: Steve Capper <steve.capper@arm.com>
(cherry picked from commit 84a24bf8c52e66b7ac89ada5e3cfbe72d65c1896)
Signed-off-by: Guilherme G. Piccoli <gpiccoli@canonical.com>
Acked-by: Kleber Sacilotto de Souza <kleber.souza@canonical.com>
Acked-by: Tim Gardner <tim.gardner@canonical.com>
Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
kernel/locking/qrwlock.c

index fe9ca92faa2a7a7b0915a6ae420f31b2e4fc4dd8..909b0bf22a1ecbfa963e8db1680c3b8eed9cfdfe 100644 (file)
@@ -61,6 +61,8 @@ EXPORT_SYMBOL(queued_read_lock_slowpath);
  */
 void queued_write_lock_slowpath(struct qrwlock *lock)
 {
+       int cnts;
+
        /* Put the writer into the wait queue */
        arch_spin_lock(&lock->wait_lock);
 
@@ -74,9 +76,8 @@ void queued_write_lock_slowpath(struct qrwlock *lock)
 
        /* When no more readers or writers, set the locked flag */
        do {
-               atomic_cond_read_acquire(&lock->cnts, VAL == _QW_WAITING);
-       } while (atomic_cmpxchg_relaxed(&lock->cnts, _QW_WAITING,
-                                       _QW_LOCKED) != _QW_WAITING);
+               cnts = atomic_cond_read_relaxed(&lock->cnts, VAL == _QW_WAITING);
+       } while (!atomic_try_cmpxchg_acquire(&lock->cnts, &cnts, _QW_LOCKED));
 unlock:
        arch_spin_unlock(&lock->wait_lock);
 }