summaryrefslogtreecommitdiffstats
path: root/lib/radix-tree.c
diff options
context:
space:
mode:
authorJohannes Weiner <hannes@cmpxchg.org>2017-01-06 19:21:43 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2017-01-07 21:22:40 -0500
commitea07b862ac8ef9b8c8358517d2e39f847dda6659 (patch)
treea1db46e9dd5dcf4c26dca819fad0b69fbb37e073 /lib/radix-tree.c
parentb0b9b3df27d100a975b4e8818f35382b64a5e35c (diff)
mm: workingset: fix use-after-free in shadow node shrinker
Several people report seeing warnings about inconsistent radix tree nodes followed by crashes in the workingset code, which all looked like use-after-free access from the shadow node shrinker. Dave Jones managed to reproduce the issue with a debug patch applied, which confirmed that the radix tree shrinking indeed frees shadow nodes while they are still linked to the shadow LRU: WARNING: CPU: 2 PID: 53 at lib/radix-tree.c:643 delete_node+0x1e4/0x200 CPU: 2 PID: 53 Comm: kswapd0 Not tainted 4.10.0-rc2-think+ #3 Call Trace: delete_node+0x1e4/0x200 __radix_tree_delete_node+0xd/0x10 shadow_lru_isolate+0xe6/0x220 __list_lru_walk_one.isra.4+0x9b/0x190 list_lru_walk_one+0x23/0x30 scan_shadow_nodes+0x2e/0x40 shrink_slab.part.44+0x23d/0x5d0 shrink_node+0x22c/0x330 kswapd+0x392/0x8f0 This is the WARN_ON_ONCE(!list_empty(&node->private_list)) placed in the inlined radix_tree_shrink(). The problem is with 14b468791fa9 ("mm: workingset: move shadow entry tracking to radix tree exceptional tracking"), which passes an update callback into the radix tree to link and unlink shadow leaf nodes when tree entries change, but forgot to pass the callback when reclaiming a shadow node. While the reclaimed shadow node itself is unlinked by the shrinker, its deletion from the tree can cause the left-most leaf node in the tree to be shrunk. If that happens to be a shadow node as well, we don't unlink it from the LRU as we should. Consider this tree, where the s are shadow entries: root->rnode | [0 n] | | [s ] [sssss] Now the shadow node shrinker reclaims the rightmost leaf node through the shadow node LRU: root->rnode | [0 ] | [s ] Because the parent of the deleted node is the first level below the root and has only one child in the left-most slot, the intermediate level is shrunk and the node containing the single shadow is put in its place: root->rnode | [s ] The shrinker again sees a single left-most slot in a first level node and thus decides to store the shadow in root->rnode directly and free the node - which is a leaf node on the shadow node LRU. root->rnode | s Without the update callback, the freed node remains on the shadow LRU, where it causes later shrinker runs to crash. Pass the node updater callback into __radix_tree_delete_node() in case the deletion causes the left-most branch in the tree to collapse too. Also add warnings when linked nodes are freed right away, rather than wait for the use-after-free when the list is scanned much later. Fixes: 14b468791fa9 ("mm: workingset: move shadow entry tracking to radix tree exceptional tracking") Reported-by: Dave Chinner <david@fromorbit.com> Reported-by: Hugh Dickins <hughd@google.com> Reported-by: Andrea Arcangeli <aarcange@redhat.com> Reported-and-tested-by: Dave Jones <davej@codemonkey.org.uk> Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Cc: Christoph Hellwig <hch@lst.de> Cc: Chris Leech <cleech@redhat.com> Cc: Lee Duncan <lduncan@suse.com> Cc: Jan Kara <jack@suse.cz> Cc: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> Cc: Matthew Wilcox <mawilcox@linuxonhyperv.com> Cc: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'lib/radix-tree.c')
-rw-r--r--lib/radix-tree.c11
1 files changed, 9 insertions, 2 deletions
diff --git a/lib/radix-tree.c b/lib/radix-tree.c
index 6f382e07de77..0b92d605fb69 100644
--- a/lib/radix-tree.c
+++ b/lib/radix-tree.c
@@ -640,6 +640,7 @@ static inline void radix_tree_shrink(struct radix_tree_root *root,
640 update_node(node, private); 640 update_node(node, private);
641 } 641 }
642 642
643 WARN_ON_ONCE(!list_empty(&node->private_list));
643 radix_tree_node_free(node); 644 radix_tree_node_free(node);
644 } 645 }
645} 646}
@@ -666,6 +667,7 @@ static void delete_node(struct radix_tree_root *root,
666 root->rnode = NULL; 667 root->rnode = NULL;
667 } 668 }
668 669
670 WARN_ON_ONCE(!list_empty(&node->private_list));
669 radix_tree_node_free(node); 671 radix_tree_node_free(node);
670 672
671 node = parent; 673 node = parent;
@@ -767,6 +769,7 @@ static void radix_tree_free_nodes(struct radix_tree_node *node)
767 struct radix_tree_node *old = child; 769 struct radix_tree_node *old = child;
768 offset = child->offset + 1; 770 offset = child->offset + 1;
769 child = child->parent; 771 child = child->parent;
772 WARN_ON_ONCE(!list_empty(&node->private_list));
770 radix_tree_node_free(old); 773 radix_tree_node_free(old);
771 if (old == entry_to_node(node)) 774 if (old == entry_to_node(node))
772 return; 775 return;
@@ -1824,15 +1827,19 @@ EXPORT_SYMBOL(radix_tree_gang_lookup_tag_slot);
1824 * __radix_tree_delete_node - try to free node after clearing a slot 1827 * __radix_tree_delete_node - try to free node after clearing a slot
1825 * @root: radix tree root 1828 * @root: radix tree root
1826 * @node: node containing @index 1829 * @node: node containing @index
1830 * @update_node: callback for changing leaf nodes
1831 * @private: private data to pass to @update_node
1827 * 1832 *
1828 * After clearing the slot at @index in @node from radix tree 1833 * After clearing the slot at @index in @node from radix tree
1829 * rooted at @root, call this function to attempt freeing the 1834 * rooted at @root, call this function to attempt freeing the
1830 * node and shrinking the tree. 1835 * node and shrinking the tree.
1831 */ 1836 */
1832void __radix_tree_delete_node(struct radix_tree_root *root, 1837void __radix_tree_delete_node(struct radix_tree_root *root,
1833 struct radix_tree_node *node) 1838 struct radix_tree_node *node,
1839 radix_tree_update_node_t update_node,
1840 void *private)
1834{ 1841{
1835 delete_node(root, node, NULL, NULL); 1842 delete_node(root, node, update_node, private);
1836} 1843}
1837 1844
1838/** 1845/**