Re: [PATCH] mm, swap: fix SWAP_USAGE_OFFLIST_BIT collision with real usage count

From: Kairui Song

Date: Mon Aug 31 2026 - 07:14:14 EST


On Sat, Aug 29, 2026 at 3:14 AM Nhat Pham <nphamcs@xxxxxxxxx> wrote:
>
> SWAP_USAGE_OFFLIST_BIT is embedded in the si->inuse_pages usage counter,
> and is meant to sit above any value that counter can reach. However, it
> is defined from BITS_PER_TYPE(atomic_t), so it is bit 30. On a system
> with 4 KiB pages the flag collides with the usage count once that count
> reaches 4 TiB.
>
> swap_usage_in_pages() masks bit 30 out, so whenever the real count has
> that bit set, every caller of it reads 4 TiB low:
>
> * /proc/swaps understates Used by 4 TiB.
>
> * A raw count of exactly 2^30 masks to zero, so try_to_unuse() takes its
> "if (!swap_usage_in_pages(si)) goto success;" early exit and swapoff
> tears the device down while pages are still swapped out. Nothing in
> the rest of swapoff aborts the teardown, so those pages are lost.
>
> Independently of swapoff, the collision also corrupts the counter and
> the plist. On a device in normal use, a free that leaves bit 30 set in
> the count makes swap_usage_sub() see the flag where there is only count,
> and call add_to_avail_list(). It clears the bit with
> fetch_and(~SWAP_USAGE_OFFLIST_BIT), leaving the stored count 4 TiB below
> the real one, and calls plist_add() on a device that is already listed,
> tripping the WARN_ON(!plist_node_empty(node)) in plist_add() and linking
> the node a second time.
>
> Change the definition of SWAP_USAGE_OFFLIST_BIT to be based on
> atomic_long_t instead. Note that the usage counter field itself is of
> this same type, so it is still a valid bit.
>
> Fixes: b228386cf237 ("mm, swap: clean up plist removal and adding")
> Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
> Closes: https://sashiko.dev/#/patchset/20260825153238.2695446-1-nphamcs%40gmail.com

It's really nice that AI can help catch more bugs.

> Suggested-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
> Cc: <stable@xxxxxxxxxxxxxxx>
> Signed-off-by: Nhat Pham <nphamcs@xxxxxxxxx>
> ---
> mm/swapfile.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 53bf01d5f7f1..601979b97f95 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -156,7 +156,7 @@ static struct swap_info_struct *swap_entry_to_info(swp_entry_t entry)
> * This bit will be set if the device is not on the plist and not
> * usable, will be cleared if the device is on the plist.
> */
> -#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_t) - 2))
> +#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_long_t) - 2))

That's a very clean fix, thanks!

Acked-by: Kairui Song <kasong@xxxxxxxxxxx>