• Johannes Weiner's avatar
    mm: protect set_page_dirty() from ongoing truncation · 2d6d7f98
    Johannes Weiner authored
    Tejun, while reviewing the code, spotted the following race condition
    between the dirtying and truncation of a page:
    
    __set_page_dirty_nobuffers()       __delete_from_page_cache()
      if (TestSetPageDirty(page))
                                         page->mapping = NULL
    				     if (PageDirty())
    				       dec_zone_page_state(page, NR_FILE_DIRTY);
    				       dec_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
        if (page->mapping)
          account_page_dirtied(page)
            __inc_zone_page_state(page, NR_FILE_DIRTY);
    	__inc_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
    
    which results in an imbalance of NR_FILE_DIRTY and BDI_RECLAIMABLE.
    
    Dirtiers usually lock out truncation, either by holding the page lock
    directly, or in case of zap_pte_range(), by pinning the mapcount with
    the page table lock held.  The notable exception to this rule, though,
    is do_wp_page(), for which this race exists.  However, do_wp_page()
    already waits for a locked page to unlock before setting the dirty bit,
    in order to prevent a race where clear_page_dirty() misses the page bit
    in the presence of dirty ptes.  Upgrade that wait to a fully locked
    set_page_dirty() to also cover the situation explained above.
    
    Afterwards, the code in set_page_dirty() dealing with a truncation race
    is no longer needed.  Remove it.
    Reported-by: default avatarTejun Heo <tj@kernel.org>
    Signed-off-by: default avatarJohannes Weiner <hannes@cmpxchg.org>
    Acked-by: default avatarKirill A. Shutemov <kirill.shutemov@linux.intel.com>
    Reviewed-by: default avatarJan Kara <jack@suse.cz>
    Cc: <stable@vger.kernel.org>
    Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
    Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
    2d6d7f98
memory.c 103 KB