Re: [PATCH 12/16] mm/shmem: fix truncate hang in large swap entry

From: Kiryl Shutsemau

Date: Mon Oct 26 2026 - 13:41:30 EST


On Mon, Oct 26, 2026 at 10:40:45AM +0100, David Hildenbrand (Arm) wrote:
> Truncating a tmpfs file or memfd to a page-aligned size inside a large
> swap entry can loop indefinitely in shmem_undo_range(). The task holds
> the inode lock and cannot be killed.
>
> A large swap entry represents a whole swapped-out folio. For example,
> truncate a file whose pages 0-7 share one swap entry to four pages:
>
> [ 0 1 2 3 | 4 5 6 7 ]
> keep | discard
> ^
> new EOF, start = 4
>
> i_size has already been updated when boundary handling asks
> shmem_get_partial_folio() for page 4. Since it is swapped out, the helper
> calls shmem_get_folio() with SGP_READ. That rejects page 4 with -EINVAL
> because its starting offset equals i_size. Nothing is read back.
>
> The final scan cannot free the entry without losing pages 0-3. It skips
> it, restarts from start = 4, and finds it again indefinitely. An unaligned
> truncate avoids this particular failure because the requested page starts
> below i_size. Folios already in the page-cache cannot trigger it as the
> direct lookup will return them beyond EOF.
>
> Fix it by letting shmem_get_partial_folio() call shmem_swapin_folio()
> directly for swap entries. This bypasses the read path's i_size check,
> allowing the existing boundary handling to zero and split the folio. If
> swap-in returns a smaller folio, it also splits the swap entry so the final
> scan can free the entries beyond EOF. Retry the lookup if the entry
> changes during swap-in.
>
> Handling swap-in allocation failures remains broken and is unchanged by
> this commit. Even when shmem_swapin_folio() correctly returns -ENOMEM
> instead of -EEXIST, shmem_get_partial_folio() returns NULL and
> shmem_undo_range() skips boundary handling. If the large swap entry
> remains intact, truncation can still loop in the final scan, while hole
> punching can return without zeroing the boundary.

Oof.

> This may read an entire large folio back from swap while holding
> i_rwsem, only to discard part of it. Avoiding that I/O would require
> splitting both the swap entry and any large folio still in the swap
> cache. That, however, is much bigger work.
>
> Original fix generated by LLM and sent by Chris Mason in private.
>
> Fixes: 8a1968bd997f ("mm/shmem, swap: fix race of truncate and swap entry split")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>

It is getting horrible situation somewhat better, so:

Acked-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>

--
Kiryl Shutsemau / Kirill A. Shutemov