History

xarray: Fix early termination of xas_for_each_marked

xas_for_each_marked() is using entry == NULL as a termination condition of the iteration. When xas_for_each_marked() is used protected only by RCU, this can however race with xas_store(xas, NULL) in the following way: TASK1 TASK2 page_cache_delete() find_get_pages_range_tag() xas_for_each_marked() xas_find_marked() off = xas_find_chunk() xas_store(&xas, NULL) xas_init_marks(&xas); ... rcu_assign_pointer(*slot, NULL); entry = xa_entry(off); And thus xas_for_each_marked() terminates prematurely possibly leading to missed entries in the iteration (translating to missing writeback of some pages or a similar problem). If we find a NULL entry that has been marked, skip it (unless we're trying to allocate an entry).

Matthew Wilcox (Oracle) committed 6 years ago

Commit 7e934cf1 parent 34eee836 files · +98 −3Browse files

Showing the changes to include/linux (1 of 6 files). Show all

Unified · Split

include/linux/xarray.h+5−1
@@ -1648,6 +1648,7 @@ static inline void *xas_next_marked(struct xa_state *xas, unsigned long max, · 1647 unmodified lines
16481648 xa_mark_t mark)
16491649 {
16501650 struct xa_node *node = xas->xa_node;
1651+ void *entry;
16511652 unsigned int offset;
16521653
16531654 if (unlikely(xas_not_node(node) || node->shift))
@@ -1659,7 +1660,10 @@ static inline void *xas_next_marked(struct xa_state *xas, unsigned long max, · 5 unmodified lines
16591660 return NULL;
16601661 if (offset == XA_CHUNK_SIZE)
16611662 return xas_find_marked(xas, max, mark);
1662− return xa_entry(xas->xa, node, offset);
1663+ entry = xa_entry(xas->xa, node, offset);
1664+ if (!entry)
1665+ return xas_find_marked(xas, max, mark);
1666+ return entry;
16631667 }
16641668
16651669 /*