Commit 739f79fc authored by Johannes Weiner's avatar Johannes Weiner Committed by Linus Torvalds

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 62cccb8c ("mm: simplify lock_page_memcg()")
but leaves the pageref-holding callsites that aren't affected alone.

Link: http://lkml.kernel.org/r/20170809183825.GA26387@cmpxchg.org
Fixes: 62cccb8c ("mm: simplify lock_page_memcg()")
Signed-off-by: default avatarJohannes Weiner <hannes@cmpxchg.org>
Reported-by: default avatarJaegeuk Kim <jaegeuk@kernel.org>
Tested-by: default avatarJaegeuk Kim <jaegeuk@kernel.org>
Reported-by: default avatarBradley Bolen <bradleybolen@gmail.com>
Tested-by: default avatarBrad Bolen <bradleybolen@gmail.com>
Cc: Vladimir Davydov <vdavydov@virtuozzo.com>
Cc: Michal Hocko <mhocko@suse.cz>
Cc: <stable@vger.kernel.org>	[4.6+]
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 039a8e38
...@@ -484,7 +484,8 @@ bool mem_cgroup_oom_synchronize(bool wait); ...@@ -484,7 +484,8 @@ bool mem_cgroup_oom_synchronize(bool wait);
extern int do_swap_account; extern int do_swap_account;
#endif #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); void unlock_page_memcg(struct page *page);
static inline unsigned long memcg_page_state(struct mem_cgroup *memcg, 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) ...@@ -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)
{ {
} }
......
...@@ -1611,9 +1611,13 @@ bool mem_cgroup_oom_synchronize(bool handle) ...@@ -1611,9 +1611,13 @@ bool mem_cgroup_oom_synchronize(bool handle)
* @page: the page * @page: the page
* *
* This function protects unlocked LRU pages from being moved to * This function protects unlocked LRU pages from being moved to
* another cgroup and stabilizes their page->mem_cgroup binding. * another cgroup.
*
* It ensures lifetime of the returned memcg. Caller is responsible
* for the lifetime of the page; __unlock_page_memcg() is available
* when @page might get freed inside the locked section.
*/ */
void lock_page_memcg(struct page *page) struct mem_cgroup *lock_page_memcg(struct page *page)
{ {
struct mem_cgroup *memcg; struct mem_cgroup *memcg;
unsigned long flags; unsigned long flags;
...@@ -1622,18 +1626,24 @@ void lock_page_memcg(struct page *page) ...@@ -1622,18 +1626,24 @@ void lock_page_memcg(struct page *page)
* The RCU lock is held throughout the transaction. The fast * The RCU lock is held throughout the transaction. The fast
* path can get away without acquiring the memcg->move_lock * path can get away without acquiring the memcg->move_lock
* because page moving starts with an RCU grace period. * because page moving starts with an RCU grace period.
*/ *
* The RCU lock also protects the memcg from being freed when
* the page state that is going to change is the only thing
* preventing the page itself from being freed. E.g. writeback
* doesn't hold a page reference and relies on PG_writeback to
* keep off truncation, migration and so forth.
*/
rcu_read_lock(); rcu_read_lock();
if (mem_cgroup_disabled()) if (mem_cgroup_disabled())
return; return NULL;
again: again:
memcg = page->mem_cgroup; memcg = page->mem_cgroup;
if (unlikely(!memcg)) if (unlikely(!memcg))
return; return NULL;
if (atomic_read(&memcg->moving_account) <= 0) if (atomic_read(&memcg->moving_account) <= 0)
return; return memcg;
spin_lock_irqsave(&memcg->move_lock, flags); spin_lock_irqsave(&memcg->move_lock, flags);
if (memcg != page->mem_cgroup) { if (memcg != page->mem_cgroup) {
...@@ -1649,18 +1659,18 @@ void lock_page_memcg(struct page *page) ...@@ -1649,18 +1659,18 @@ void lock_page_memcg(struct page *page)
memcg->move_lock_task = current; memcg->move_lock_task = current;
memcg->move_lock_flags = flags; memcg->move_lock_flags = flags;
return; return memcg;
} }
EXPORT_SYMBOL(lock_page_memcg); EXPORT_SYMBOL(lock_page_memcg);
/** /**
* unlock_page_memcg - unlock a page->mem_cgroup binding * __unlock_page_memcg - unlock and unpin a memcg
* @page: the page * @memcg: the memcg
*
* Unlock and unpin a memcg returned by lock_page_memcg().
*/ */
void unlock_page_memcg(struct page *page) void __unlock_page_memcg(struct mem_cgroup *memcg)
{ {
struct mem_cgroup *memcg = page->mem_cgroup;
if (memcg && memcg->move_lock_task == current) { if (memcg && memcg->move_lock_task == current) {
unsigned long flags = memcg->move_lock_flags; unsigned long flags = memcg->move_lock_flags;
...@@ -1672,6 +1682,15 @@ void unlock_page_memcg(struct page *page) ...@@ -1672,6 +1682,15 @@ void unlock_page_memcg(struct page *page)
rcu_read_unlock(); rcu_read_unlock();
} }
/**
* unlock_page_memcg - unlock a page->mem_cgroup binding
* @page: the page
*/
void unlock_page_memcg(struct page *page)
{
__unlock_page_memcg(page->mem_cgroup);
}
EXPORT_SYMBOL(unlock_page_memcg); EXPORT_SYMBOL(unlock_page_memcg);
/* /*
......
...@@ -2724,9 +2724,12 @@ EXPORT_SYMBOL(clear_page_dirty_for_io); ...@@ -2724,9 +2724,12 @@ EXPORT_SYMBOL(clear_page_dirty_for_io);
int test_clear_page_writeback(struct page *page) int test_clear_page_writeback(struct page *page)
{ {
struct address_space *mapping = page_mapping(page); struct address_space *mapping = page_mapping(page);
struct mem_cgroup *memcg;
struct lruvec *lruvec;
int ret; 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)) { if (mapping && mapping_use_writeback_tags(mapping)) {
struct inode *inode = mapping->host; struct inode *inode = mapping->host;
struct backing_dev_info *bdi = inode_to_bdi(inode); struct backing_dev_info *bdi = inode_to_bdi(inode);
...@@ -2754,12 +2757,18 @@ int test_clear_page_writeback(struct page *page) ...@@ -2754,12 +2757,18 @@ int test_clear_page_writeback(struct page *page)
} else { } else {
ret = TestClearPageWriteback(page); 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) { if (ret) {
dec_lruvec_page_state(page, NR_WRITEBACK); dec_lruvec_state(lruvec, NR_WRITEBACK);
dec_zone_page_state(page, NR_ZONE_WRITE_PENDING); dec_zone_page_state(page, NR_ZONE_WRITE_PENDING);
inc_node_page_state(page, NR_WRITTEN); inc_node_page_state(page, NR_WRITTEN);
} }
unlock_page_memcg(page); __unlock_page_memcg(memcg);
return ret; return ret;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment