From: Greg Kroah-Hartman Date: Sun, 23 Jan 2022 15:07:19 +0000 (+0100) Subject: 4.9-stable patches X-Git-Tag: v4.4.300~134 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2ba13ffe5f8f7c9e4e9225754507cf3335f91a72;p=thirdparty%2Fkernel%2Fstable-queue.git 4.9-stable patches added patches: shmem-fix-a-race-between-shmem_unused_huge_shrink-and-shmem_evict_inode.patch --- diff --git a/queue-4.9/series b/queue-4.9/series index a8463f955c0..ff5eeed55f9 100644 --- a/queue-4.9/series +++ b/queue-4.9/series @@ -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 index 00000000000..a094770df06 --- /dev/null +++ b/queue-4.9/shmem-fix-a-race-between-shmem_unused_huge_shrink-and-shmem_evict_inode.patch @@ -0,0 +1,172 @@ +From 62c9827cbb996c2c04f615ecd783ce28bcea894b Mon Sep 17 00:00:00 2001 +From: Gang Li +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 + +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 +Reviewed-by: Muchun Song +Acked-by: Kirill A. Shutemov +Cc: Hugh Dickins +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Linus Torvalds +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } +