]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
4.9-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 23 Jan 2022 15:07:19 +0000 (16:07 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 23 Jan 2022 15:07:19 +0000 (16:07 +0100)
added patches:
shmem-fix-a-race-between-shmem_unused_huge_shrink-and-shmem_evict_inode.patch

queue-4.9/series
queue-4.9/shmem-fix-a-race-between-shmem_unused_huge_shrink-and-shmem_evict_inode.patch [new file with mode: 0644]

index a8463f955c0796286017a94fc75354ea0ea480f6..ff5eeed55f9dc4acb7b2320e92cc5a599d01213f 100644 (file)
@@ -25,3 +25,4 @@ media-pvrusb2-fix-control-message-timeouts.patch
 media-stk1160-fix-control-message-timeouts.patch
 can-softing_cs-softingcs_probe-fix-memleak-on-registration-failure.patch
 pci-add-function-1-dma-alias-quirk-for-marvell-88se9125-sata-controller.patch
+shmem-fix-a-race-between-shmem_unused_huge_shrink-and-shmem_evict_inode.patch
diff --git a/queue-4.9/shmem-fix-a-race-between-shmem_unused_huge_shrink-and-shmem_evict_inode.patch b/queue-4.9/shmem-fix-a-race-between-shmem_unused_huge_shrink-and-shmem_evict_inode.patch
new file mode 100644 (file)
index 0000000..a094770
--- /dev/null
@@ -0,0 +1,172 @@
+From 62c9827cbb996c2c04f615ecd783ce28bcea894b Mon Sep 17 00:00:00 2001
+From: Gang Li <ligang.bdlg@bytedance.com>
+Date: Fri, 14 Jan 2022 14:05:23 -0800
+Subject: shmem: fix a race between shmem_unused_huge_shrink and shmem_evict_inode
+
+From: Gang Li <ligang.bdlg@bytedance.com>
+
+commit 62c9827cbb996c2c04f615ecd783ce28bcea894b upstream.
+
+Fix a data race in commit 779750d20b93 ("shmem: split huge pages beyond
+i_size under memory pressure").
+
+Here are call traces causing race:
+
+   Call Trace 1:
+     shmem_unused_huge_shrink+0x3ae/0x410
+     ? __list_lru_walk_one.isra.5+0x33/0x160
+     super_cache_scan+0x17c/0x190
+     shrink_slab.part.55+0x1ef/0x3f0
+     shrink_node+0x10e/0x330
+     kswapd+0x380/0x740
+     kthread+0xfc/0x130
+     ? mem_cgroup_shrink_node+0x170/0x170
+     ? kthread_create_on_node+0x70/0x70
+     ret_from_fork+0x1f/0x30
+
+   Call Trace 2:
+     shmem_evict_inode+0xd8/0x190
+     evict+0xbe/0x1c0
+     do_unlinkat+0x137/0x330
+     do_syscall_64+0x76/0x120
+     entry_SYSCALL_64_after_hwframe+0x3d/0xa2
+
+A simple explanation:
+
+Image there are 3 items in the local list (@list).  In the first
+traversal, A is not deleted from @list.
+
+  1)    A->B->C
+        ^
+        |
+        pos (leave)
+
+In the second traversal, B is deleted from @list.  Concurrently, A is
+deleted from @list through shmem_evict_inode() since last reference
+counter of inode is dropped by other thread.  Then the @list is corrupted.
+
+  2)    A->B->C
+        ^  ^
+        |  |
+     evict pos (drop)
+
+We should make sure the inode is either on the global list or deleted from
+any local list before iput().
+
+Fixed by moving inodes back to global list before we put them.
+
+[akpm@linux-foundation.org: coding style fixes]
+
+Link: https://lkml.kernel.org/r/20211125064502.99983-1-ligang.bdlg@bytedance.com
+Fixes: 779750d20b93 ("shmem: split huge pages beyond i_size under memory pressure")
+Signed-off-by: Gang Li <ligang.bdlg@bytedance.com>
+Reviewed-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
+Cc: Hugh Dickins <hughd@google.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/shmem.c |   37 +++++++++++++++++++++----------------
+ 1 file changed, 21 insertions(+), 16 deletions(-)
+
+--- a/mm/shmem.c
++++ b/mm/shmem.c
+@@ -436,7 +436,7 @@ static unsigned long shmem_unused_huge_s
+       struct shmem_inode_info *info;
+       struct page *page;
+       unsigned long batch = sc ? sc->nr_to_scan : 128;
+-      int removed = 0, split = 0;
++      int split = 0;
+       if (list_empty(&sbinfo->shrinklist))
+               return SHRINK_STOP;
+@@ -451,7 +451,6 @@ static unsigned long shmem_unused_huge_s
+               /* inode is about to be evicted */
+               if (!inode) {
+                       list_del_init(&info->shrinklist);
+-                      removed++;
+                       goto next;
+               }
+@@ -459,12 +458,12 @@ static unsigned long shmem_unused_huge_s
+               if (round_up(inode->i_size, PAGE_SIZE) ==
+                               round_up(inode->i_size, HPAGE_PMD_SIZE)) {
+                       list_move(&info->shrinklist, &to_remove);
+-                      removed++;
+                       goto next;
+               }
+               list_move(&info->shrinklist, &list);
+ next:
++              sbinfo->shrinklist_len--;
+               if (!--batch)
+                       break;
+       }
+@@ -484,7 +483,7 @@ next:
+               inode = &info->vfs_inode;
+               if (nr_to_split && split >= nr_to_split)
+-                      goto leave;
++                      goto move_back;
+               page = find_get_page(inode->i_mapping,
+                               (inode->i_size & HPAGE_PMD_MASK) >> PAGE_SHIFT);
+@@ -498,38 +497,44 @@ next:
+               }
+               /*
+-               * Leave the inode on the list if we failed to lock
+-               * the page at this time.
++               * Move the inode on the list back to shrinklist if we failed
++               * to lock the page at this time.
+                *
+                * Waiting for the lock may lead to deadlock in the
+                * reclaim path.
+                */
+               if (!trylock_page(page)) {
+                       put_page(page);
+-                      goto leave;
++                      goto move_back;
+               }
+               ret = split_huge_page(page);
+               unlock_page(page);
+               put_page(page);
+-              /* If split failed leave the inode on the list */
++              /* If split failed move the inode on the list back to shrinklist */
+               if (ret)
+-                      goto leave;
++                      goto move_back;
+               split++;
+ drop:
+               list_del_init(&info->shrinklist);
+-              removed++;
+-leave:
++              goto put;
++move_back:
++              /*
++               * Make sure the inode is either on the global list or deleted
++               * from any local list before iput() since it could be deleted
++               * in another thread once we put the inode (then the local list
++               * is corrupted).
++               */
++              spin_lock(&sbinfo->shrinklist_lock);
++              list_move(&info->shrinklist, &sbinfo->shrinklist);
++              sbinfo->shrinklist_len++;
++              spin_unlock(&sbinfo->shrinklist_lock);
++put:
+               iput(inode);
+       }
+-      spin_lock(&sbinfo->shrinklist_lock);
+-      list_splice_tail(&list, &sbinfo->shrinklist);
+-      sbinfo->shrinklist_len -= removed;
+-      spin_unlock(&sbinfo->shrinklist_lock);
+-
+       return split;
+ }