FAILED: patch "[PATCH] mm: memcontrol: fix NULL pointer crash in" failed to apply to 4.12-stable tree
Subsystems (4)
[email protected]
maintainerThe patch below does not apply to the 4.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
Quoted 1 line
From
739f79fc9db1b38f96b5a5109b247a650fbebf6dMon Sep 17 00:00:00 2001
From: Johannes Weiner <[email protected]>
Date: Fri, 18 Aug 2017 15:15:48 -0700
Subject: [PATCH] mm: memcontrol: fix NULL pointer crash in
test_clear_page_writeback()
Jaegeuk and Brad report a NULL pointer crash when writeback ending tries to update the memcg stats:
BUG: unable to handle kernel NULL pointer dereference at 00000000000003b0
IP: test_clear_page_writeback+0x12e/0x2c0
[...]
RIP: 0010:test_clear_page_writeback+0x12e/0x2c0
Call Trace:
<IRQ>
end_page_writeback+0x47/0x70
f2fs_write_end_io+0x76/0x180 [f2fs]
bio_endio+0x9f/0x120
blk_update_request+0xa8/0x2f0
scsi_end_request+0x39/0x1d0
scsi_io_completion+0x211/0x690
scsi_finish_command+0xd9/0x120
scsi_softirq_done+0x127/0x150
__blk_mq_complete_request_remote+0x13/0x20
flush_smp_call_function_queue+0x56/0x110
generic_smp_call_function_single_interrupt+0x13/0x30
smp_call_function_single_interrupt+0x27/0x40
call_function_single_interrupt+0x89/0x90
RIP: 0010:native_safe_halt+0x6/0x10 (gdb) l *(test_clear_page_writeback+0x12e)
0xffffffff811bae3e is in test_clear_page_writeback (./include/linux/memcontrol.h:619).
614 mod_node_page_state(page_pgdat(page), idx, val);
615 if (mem_cgroup_disabled() || !page->mem_cgroup)
616 return;
617 mod_memcg_state(page->mem_cgroup, idx, val);
618 pn = page->mem_cgroup->nodeinfo[page_to_nid(page)];
619 this_cpu_add(pn->lruvec_stat->count[idx], val);
620 }
621
622 unsigned long mem_cgroup_soft_limit_reclaim(pg_data_t *pgdat, int order,
623 gfp_t gfp_mask,The issue is that writeback doesn't hold a page reference and the page might get freed after PG_writeback is cleared (and the mapping is unlocked) in test_clear_page_writeback(). The stat functions looking up the page's node or zone are safe, as those attributes are static across allocation and free cycles. But page->mem_cgroup is not, and it will get cleared if we race with truncation or migration.
It appears this race window has been around for a while, but less likely to trigger when the memcg stats were updated first thing after PG_writeback is cleared. Recent changes reshuffled this code to update the global node stats before the memcg ones, though, stretching the race window out to an extent where people can reproduce the problem.
Update test_clear_page_writeback() to look up and pin page->mem_cgroup before clearing PG_writeback, then not use that pointer afterward. It is a partial revert of 62cccb8c8e7a ("mm: simplify lock_page_memcg()") but leaves the pageref-holding callsites that aren't affected alone.
include/linux/memcontrol.h +8 -2
@@ -484,7 +484,8 @@ bool mem_cgroup_oom_synchronize(bool wait);extern int do_swap_account;#endif -void lock_page_memcg (struct page *page); +struct mem_cgroup *lock_page_memcg (struct page *page); +void __unlock_page_memcg (struct mem_cgroup *memcg);void unlock_page_memcg (struct page *page); static inline unsigned long memcg_page_state(struct mem_cgroup *memcg,
@@ -809,7 +810,12 @@ mem_cgroup_print_oom_info(struct mem_cgroup *memcg, struct task_struct *p) { } -static inline void lock_page_memcg (struct page *page) +static inline struct mem_cgroup *lock_page_memcg (struct page *page) +{ +return NULL ; +} + +static inline void __unlock_page_memcg (struct mem_cgroup *memcg) { }
mm/memcontrol.c +31 -12
82 lines not on this page · raw message
mm/page-writeback.c +12 -3
@@ -2724,9 +2724,12 @@ EXPORT_SYMBOL(clear_page_dirty_for_io);int test_clear_page_writeback (struct page *page) {struct address_space *mapping =page_mapping (page); +struct mem_cgroup *memcg; +struct lruvec *lruvec;int ret; -lock_page_memcg (page); + memcg =lock_page_memcg (page); + lruvec =mem_cgroup_page_lruvec (page,page_pgdat (page));if (mapping &&mapping_use_writeback_tags (mapping)) {struct inode *inode = mapping->host;struct backing_dev_info *bdi =inode_to_bdi (inode);
@@ -2754,12 +2757,18 @@ int test_clear_page_writeback(struct page *page) }else { ret =TestClearPageWriteback (page); } +/* +* NOTE: Page might be free now! Writeback doesn't hold a page +* reference on its own, it relies on truncation to wait for +* the clearing of PG_writeback. The below can only access +* page state that is static across allocation cycles. +*/ if (ret) { -dec_lruvec_page_state (page,NR_WRITEBACK ); +dec_lruvec_state (lruvec,NR_WRITEBACK );dec_zone_page_state (page,NR_ZONE_WRITE_PENDING );inc_node_page_state (page,NR_WRITTEN ); } -unlock_page_memcg (page); +__unlock_page_memcg (memcg);return ret; }