swap: fix races exposed by swap discard
Subsystems (2)
PATCH v6 3/4 swap: fix races exposed by swap discard
Last patch can expose races, according to Hugh:
swapoff was sometimes failing with "Cannot allocate memory", coming from try_to_unuse()'s -ENOMEM: it needs to allow for swap_duplicate() failing on a free entry temporarily SWAP_MAP_BAD while being discarded.
We should use ACCESS_ONCE() there, and whenever accessing swap_map locklessly; but rather than peppering it throughout try_to_unuse(), just declare *swap_map with volatile.
try_to_unuse() is accustomed to *swap_map going down racily, but not necessarily to it jumping up from 0 to SWAP_MAP_BAD: we'll be safer to prevent that transition once SWP_WRITEOK is switched off, when it's a waste of time to issue discards anyway (swapon can do a whole discard).
Another issue is:
In swapin_readahead(), read_swap_cache_async() can read a bad swap entry, because we don't check if readahead swap entry is bad. This doesn't break anything but such swapin page is wasteful and can only be freed at page reclaim. We should avoid read such swap entry. And in discard, we mark swap entry SWAP_MAP_BAD and then switch it to normal when discard is finished. If readahead reads such swap entry, we have the same issue, so we much check if swap entry is bad too.
Thanks Hugh to inspire swapin_readahead could use bad swap entry.
--- mm/swapfile.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)
mm/swapfile.c +11 -4
@@ -357,7 +357,8 @@ static void dec_cluster_info_page(struct* instead of free it immediately. The cluster will be freed * after discard. */ -if (p->flags &SWP_PAGE_DISCARD ) { +if ((p->flags & (SWP_WRITEOK |SWP_PAGE_DISCARD )) == + (SWP_WRITEOK |SWP_PAGE_DISCARD )) {swap_cluster_schedule_discard (p, idx);return ; }
@@ -1255,7 +1256,7 @@ static unsigned int find_next_to_unuse(selse continue ; } - count = si->swap_map[i]; + count =ACCESS_ONCE (si->swap_map[i]);if (count &&swap_count (count) !=SWAP_MAP_BAD )break ; }
@@ -1275,7 +1276,7 @@ int try_to_unuse(unsigned int type, bool {struct swap_info_struct *si = swap_info[type];struct mm_struct *start_mm; -unsigned char *swap_map; +volatile unsigned char *swap_map;/* ACCESS_ONCE throughout */ unsigned char swcount;struct page *page;swp_entry_t entry;
@@ -1326,7 +1327,8 @@ int try_to_unuse(unsigned int type, bool* reused since sys_swapoff() already disabled * allocation from here, or alloc_page() failed. */ -if (!*swap_map) + swcount = *swap_map; +if (!swcount || swcount ==SWAP_MAP_BAD )continue ; retval = -ENOMEM ;break ;
@@ -2506,6 +2508,11 @@ static int __swap_duplicate(swp_entry_t goto unlock_out; count = p->swap_map[offset]; +if (unlikely (swap_count (count) ==SWAP_MAP_BAD )) { + err = -ENOENT ; + goto unlock_out; + } + has_cache = count &SWAP_HAS_CACHE ; count &= ~SWAP_HAS_CACHE ; err =0 ;
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Andrew Morton
maintainerQuoted 25 lines · On Tue, 16 Jul 2013 04:43:54 +0800 Shaohua Li <[email protected]> wrote:
Last patch can expose races, according to Hugh:
swapoff was sometimes failing with "Cannot allocate memory", coming from try_to_unuse()'s
-ENOMEM: it needs to allow forswap_duplicate()failing on a free entry temporarilySWAP_MAP_BADwhile being discarded.We should use
ACCESS_ONCE()there, and whenever accessingswap_maplocklessly; but rather than peppering it throughouttry_to_unuse(), just declare*swap_mapwith volatile.
try_to_unuse()is accustomed to*swap_mapgoing down racily, but not necessarily to it jumping up from 0 toSWAP_MAP_BAD: we'll be safer to prevent that transition onceSWP_WRITEOKis switched off, when it's a waste of time to issue discards anyway (swapon can do a whole discard).Another issue is:
In
swapin_readahead(),read_swap_cache_async()can read a bad swap entry, because we don't check if readahead swap entry is bad. This doesn't break anything but such swapin page is wasteful and can only be freed at page reclaim. We should avoid read such swap entry. And in discard, we mark swap entrySWAP_MAP_BADand then switch it to normal when discard is finished. If readahead reads such swap entry, we have the same issue, so we much check if swap entry is bad too.
Quoted 1 line
Thanks Hugh to inspire
swapin_readaheadcould use bad swap entry.
Oh geeze. How is anyone supposed to maintain this code :(
mm/swapfile.c int try_to_unuse(unsigned int type, bool · line 1279
Quoted 9 lines
...
mm/swapfile.c +1
-1@@ -1275,7 +1276,7 @@ int try_to_unuse(unsigned int type, bool {struct swap_info_struct *si = swap_info[type];struct mm_struct *start_mm; -unsigned char *swap_map; +volatile unsigned char *swap_map;/* ACCESS_ONCE throughout */
Again, it would take an unreasonable effort for anyone else to understand why this is being done. Please document your code with sufficient detail to permit a reasonably experienced kernel developer to understand it.
mm/swapfile.c int try_to_unuse(unsigned int type, bool
Quoted 6 lines
mm/swapfile.c +0
-0unsigned char swcount;struct page *page;swp_entry_t entry;
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>