Syzbot reported crashes in lru_gen_test_recent() and subsequent NULL pointer dereferences in the page cache code: Oops: general protection fault in lru_gen_test_recent+0xfc/0x370 KASAN: probably user-memory-access in range [0x0000000000004e00-0x0000000000004e07] And later: BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor instruction fetch in kernel mode RIP: 0010:0x0 Call Trace: filemap_read_folio+0xc8/0x2a0 Investigation revealed that unpack_shadow() can extract an invalid node ID from shadow entries, causing NODE_DATA(nid) to return NULL for pgdat. In the reported case, the shadow value was 0x0000000000000041, which is suspiciously small and indicates corruption. When this NULL pgdat is passed to mem_cgroup_lruvec(), it leads to crashes when dereferencing memcg->nodeinfo. The corrupted state also propagates through the call chain causing subsequent crashes in page cache code. The root cause of shadow entry corruption is unclear and may indicate a deeper issue in xarray management, page cache eviction/refault race conditions, or memory corruption. However, regardless of the source, the code should handle corrupted entries defensively. Fix this by: 1. Checking if pgdat is NULL in lru_gen_test_recent() after unpacking the shadow entry, and setting *lruvec to NULL to signal corruption. 2. Adding a NULL check for lruvec in lru_gen_refault() to catch and skip processing of corrupted entries before the corruption propagates further. This prevents the immediate crash while the root cause of shadow corruption can be investigated separately. Reported-by: syzbot+e008db2ac01e282550ee@syzkaller.appspot.com Closes: https://syzkaller.appspot.com/bug?extid=e008db2ac01e282550ee Fixes: b1a71694fb00c ("mm/mglru: rework refault detection") Cc: Yu Zhao Signed-off-by: Deepanshu Kartikey --- mm/workingset.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mm/workingset.c b/mm/workingset.c index e9f05634747a..0ec205a1ae92 100644 --- a/mm/workingset.c +++ b/mm/workingset.c @@ -270,7 +270,14 @@ static bool lru_gen_test_recent(void *shadow, struct lruvec **lruvec, struct pglist_data *pgdat; unpack_shadow(shadow, &memcg_id, &pgdat, token, workingset); - + /* + * If pgdat is NULL, the shadow entry contains an invalid node ID. + * Set lruvec to NULL so caller can detect and skip processing. + */ + if (unlikely(!pgdat)) { + *lruvec = NULL; + return false; + } memcg = mem_cgroup_from_id(memcg_id); *lruvec = mem_cgroup_lruvec(memcg, pgdat); @@ -294,9 +301,8 @@ static void lru_gen_refault(struct folio *folio, void *shadow) rcu_read_lock(); recent = lru_gen_test_recent(shadow, &lruvec, &token, &workingset); - if (lruvec != folio_lruvec(folio)) + if (!lruvec || lruvec != folio_lruvec(folio)) goto unlock; - mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + type, delta); if (!recent) -- 2.43.0