Commit 5a4d8944 authored by Nhat Pham's avatar Nhat Pham Committed by Andrew Morton
Browse files

cachestat: do not flush stats in recency check

syzbot detects that cachestat() is flushing stats, which can sleep, in its
RCU read section (see [1]).  This is done in the workingset_test_recent()
step (which checks if the folio's eviction is recent).

Move the stat flushing step to before the RCU read section of cachestat,
and skip stat flushing during the recency check.

[1]: https://lore.kernel.org/cgroups/000000000000f71227061bdf97e0@google.com/

Link: https://lkml.kernel.org/r/20240627201737.3506959-1-nphamcs@gmail.com


Fixes: b0068472 ("mm: workingset: move the stats flush into workingset_test_recent()")
Signed-off-by: default avatarNhat Pham <nphamcs@gmail.com>
Reported-by: default avatar <syzbot+b7f13b2d0cc156edf61a@syzkaller.appspotmail.com>
Closes: https://lore.kernel.org/cgroups/000000000000f71227061bdf97e0@google.com/


Debugged-by: default avatarJohannes Weiner <hannes@cmpxchg.org>
Suggested-by: default avatarJohannes Weiner <hannes@cmpxchg.org>
Acked-by: default avatarJohannes Weiner <hannes@cmpxchg.org>
Acked-by: default avatarShakeel Butt <shakeel.butt@linux.dev>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: David Hildenbrand <david@redhat.com>
Cc: "Huang, Ying" <ying.huang@intel.com>
Cc: Kairui Song <kasong@tencent.com>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Yosry Ahmed <yosryahmed@google.com>
Cc: <stable@vger.kernel.org>	[6.8+]
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 9fd154ba
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -354,7 +354,8 @@ static inline swp_entry_t page_swap_entry(struct page *page)
}

/* linux/mm/workingset.c */
bool workingset_test_recent(void *shadow, bool file, bool *workingset);
bool workingset_test_recent(void *shadow, bool file, bool *workingset,
				bool flush);
void workingset_age_nonresident(struct lruvec *lruvec, unsigned long nr_pages);
void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg);
void workingset_refault(struct folio *folio, void *shadow);
+4 −1
Original line number Diff line number Diff line
@@ -4248,6 +4248,9 @@ static void filemap_cachestat(struct address_space *mapping,
	XA_STATE(xas, &mapping->i_pages, first_index);
	struct folio *folio;

	/* Flush stats (and potentially sleep) outside the RCU read section. */
	mem_cgroup_flush_stats_ratelimited(NULL);

	rcu_read_lock();
	xas_for_each(&xas, folio, last_index) {
		int order;
@@ -4311,7 +4314,7 @@ static void filemap_cachestat(struct address_space *mapping,
					goto resched;
			}
#endif
			if (workingset_test_recent(shadow, true, &workingset))
			if (workingset_test_recent(shadow, true, &workingset, false))
				cs->nr_recently_evicted += nr_pages;

			goto resched;
+11 −3
Original line number Diff line number Diff line
@@ -412,10 +412,12 @@ void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg)
 * @file: whether the corresponding folio is from the file lru.
 * @workingset: where the workingset value unpacked from shadow should
 * be stored.
 * @flush: whether to flush cgroup rstat.
 *
 * Return: true if the shadow is for a recently evicted folio; false otherwise.
 */
bool workingset_test_recent(void *shadow, bool file, bool *workingset)
bool workingset_test_recent(void *shadow, bool file, bool *workingset,
				bool flush)
{
	struct mem_cgroup *eviction_memcg;
	struct lruvec *eviction_lruvec;
@@ -467,9 +469,15 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset)

	/*
	 * Flush stats (and potentially sleep) outside the RCU read section.
	 *
	 * Note that workingset_test_recent() itself might be called in RCU read
	 * section (for e.g, in cachestat) - these callers need to skip flushing
	 * stats (via the flush argument).
	 *
	 * XXX: With per-memcg flushing and thresholding, is ratelimiting
	 * still needed here?
	 */
	if (flush)
		mem_cgroup_flush_stats_ratelimited(eviction_memcg);

	eviction_lruvec = mem_cgroup_lruvec(eviction_memcg, pgdat);
@@ -558,7 +566,7 @@ void workingset_refault(struct folio *folio, void *shadow)

	mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + file, nr);

	if (!workingset_test_recent(shadow, file, &workingset))
	if (!workingset_test_recent(shadow, file, &workingset, true))
		return;

	folio_set_active(folio);