• Ryan Roberts's avatar
    mm: swap: fix race between free_swap_and_cache() and swapoff() · 82b1c07a
    Ryan Roberts authored
    There was previously a theoretical window where swapoff() could run and
    teardown a swap_info_struct while a call to free_swap_and_cache() was
    running in another thread.  This could cause, amongst other bad
    possibilities, swap_page_trans_huge_swapped() (called by
    free_swap_and_cache()) to access the freed memory for swap_map.
    
    This is a theoretical problem and I haven't been able to provoke it from a
    test case.  But there has been agreement based on code review that this is
    possible (see link below).
    
    Fix it by using get_swap_device()/put_swap_device(), which will stall
    swapoff().  There was an extra check in _swap_info_get() to confirm that
    the swap entry was not free.  This isn't present in get_swap_device()
    because it doesn't make sense in general due to the race between getting
    the reference and swapoff.  So I've added an equivalent check directly in
    free_swap_and_cache().
    
    Details of how to provoke one possible issue (thanks to David Hildenbrand
    for deriving this):
    
    --8<-----
    
    __swap_entry_free() might be the last user and result in
    "count == SWAP_HAS_CACHE".
    
    swapoff->try_to_unuse() will stop as soon as soon as si->inuse_pages==0.
    
    So the question is: could someone reclaim the folio and turn
    si->inuse_pages==0, before we completed swap_page_trans_huge_swapped().
    
    Imagine the following: 2 MiB folio in the swapcache. Only 2 subpages are
    still references by swap entries.
    
    Process 1 still references subpage 0 via swap entry.
    Process 2 still references subpage 1 via swap entry.
    
    Process 1 quits. Calls free_swap_and_cache().
    -> count == SWAP_HAS_CACHE
    [then, preempted in the hypervisor etc.]
    
    Process 2 quits. Calls free_swap_and_cache().
    -> count == SWAP_HAS_CACHE
    
    Process 2 goes ahead, passes swap_page_trans_huge_swapped(), and calls
    __try_to_reclaim_swap().
    
    __try_to_reclaim_swap()->folio_free_swap()->delete_from_swap_cache()->
    put_swap_folio()->free_swap_slot()->swapcache_free_entries()->
    swap_entry_free()->swap_range_free()->
    ...
    WRITE_ONCE(si->inuse_pages, si->inuse_pages - nr_entries);
    
    What stops swapoff to succeed after process 2 reclaimed the swap cache
    but before process1 finished its call to swap_page_trans_huge_swapped()?
    
    --8<-----
    
    Link: https://lkml.kernel.org/r/20240306140356.3974886-1-ryan.roberts@arm.com
    Fixes: 7c00bafe ("mm/swap: free swap slots in batch")
    Closes: https://lore.kernel.org/linux-mm/65a66eb9-41f8-4790-8db2-0c70ea15979f@redhat.com/Signed-off-by: default avatarRyan Roberts <ryan.roberts@arm.com>
    Cc: David Hildenbrand <david@redhat.com>
    Cc: "Huang, Ying" <ying.huang@intel.com>
    Cc: <stable@vger.kernel.org>
    Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
    82b1c07a
swapfile.c 92.8 KB