fs: dlm: fix mismatch of plock results from userspace
authorAlexander Aring <aahringo@redhat.com>
Wed, 24 May 2023 16:02:04 +0000 (12:02 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 23 Jul 2023 11:49:38 +0000 (13:49 +0200)
commit 57e2c2f2d94cfd551af91cedfa1af6d972487197 upstream.

When a waiting plock request (F_SETLKW) is sent to userspace
for processing (dlm_controld), the result is returned at a
later time. That result could be incorrectly matched to a
different waiting request in cases where the owner field is
the same (e.g. different threads in a process.) This is fixed
by comparing all the properties in the request and reply.

The results for non-waiting plock requests are now matched
based on list order because the results are returned in the
same order they were sent.

Cc: stable@vger.kernel.org
Signed-off-by: Alexander Aring <aahringo@redhat.com>
Signed-off-by: David Teigland <teigland@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/dlm/plock.c

index 0df2470..739e7d5 100644 (file)
@@ -394,7 +394,7 @@ static ssize_t dev_read(struct file *file, char __user *u, size_t count,
                if (op->info.flags & DLM_PLOCK_FL_CLOSE)
                        list_del(&op->list);
                else
-                       list_move(&op->list, &recv_list);
+                       list_move_tail(&op->list, &recv_list);
                memcpy(&info, &op->info, sizeof(info));
        }
        spin_unlock(&ops_lock);
@@ -432,20 +432,52 @@ static ssize_t dev_write(struct file *file, const char __user *u, size_t count,
        if (check_version(&info))
                return -EINVAL;
 
+       /*
+        * The results for waiting ops (SETLKW) can be returned in any
+        * order, so match all fields to find the op.  The results for
+        * non-waiting ops are returned in the order that they were sent
+        * to userspace, so match the result with the first non-waiting op.
+        */
        spin_lock(&ops_lock);
-       list_for_each_entry(iter, &recv_list, list) {
-               if (iter->info.fsid == info.fsid &&
-                   iter->info.number == info.number &&
-                   iter->info.owner == info.owner) {
-                       list_del_init(&iter->list);
-                       memcpy(&iter->info, &info, sizeof(info));
-                       if (iter->data)
-                               do_callback = 1;
-                       else
-                               iter->done = 1;
-                       op = iter;
-                       break;
+       if (info.wait) {
+               list_for_each_entry(iter, &recv_list, list) {
+                       if (iter->info.fsid == info.fsid &&
+                           iter->info.number == info.number &&
+                           iter->info.owner == info.owner &&
+                           iter->info.pid == info.pid &&
+                           iter->info.start == info.start &&
+                           iter->info.end == info.end &&
+                           iter->info.ex == info.ex &&
+                           iter->info.wait) {
+                               op = iter;
+                               break;
+                       }
                }
+       } else {
+               list_for_each_entry(iter, &recv_list, list) {
+                       if (!iter->info.wait) {
+                               op = iter;
+                               break;
+                       }
+               }
+       }
+
+       if (op) {
+               /* Sanity check that op and info match. */
+               if (info.wait)
+                       WARN_ON(op->info.optype != DLM_PLOCK_OP_LOCK);
+               else
+                       WARN_ON(op->info.fsid != info.fsid ||
+                               op->info.number != info.number ||
+                               op->info.owner != info.owner ||
+                               op->info.optype != info.optype);
+
+               list_del_init(&op->list);
+               memcpy(&op->info, &info, sizeof(info));
+               if (op->data)
+                       do_callback = 1;
+               else
+                       op->done = 1;
        }
        spin_unlock(&ops_lock);