mm/vmscan: flush deferred TLB before freeing large folios in reclaim
Status from this message: Queued for 6.6 and 6.1 with Matthew's Reviewed-by added, thanks.
Get series with b4
b4 am [email protected]Versions newest first
Patches in v1 Applied
| # | Subject and files | Size | Replies | Applied |
|---|---|---|---|---|
| 1/1 | mm/vmscan: flush deferred TLB before freeing large foliosmm/ | +4 | 3 replies |
Reviews trailers per patch
The patch has a Reviewed-by or Acked-by.
| Patch | Matthew Wilcox (Oracle) |
|---|---|
| 1/1 | R-b |
Trailers given in replies are filled; those carried in the patches from earlier versions are outlined with dashes.
Cover letter [PATCH 5.15.y 6.1.y 6.6.y 0/1] mm/vmscan: flush deferred TLB before freeing large folios in reclaim
Hi,
We were chasing random user-space segfaults on our production kernels. They were fully non-deterministic, and the fault address reported for each SIGSEGV was itself random and had nothing to do with the code that was actually running -- which smells like a stale TLB entry, not corrupted data.
We managed to reproduce it with stress-ng: under memory pressure the workers take random SIGSEGV/SIGILL too, again at random fault addresses.
It turns out we're missing a TLB flush. In reclaim, shrink_folio_list() tears down the PTEs with a deferred, batched flush. Order-0 folios are collected and only freed after that batch is flushed by try_to_unmap_flush(). Large folios, though, are freed right away at the free_it label via destroy_large_folio(), which runs *before* the flush. So a large folio's pages can go back to the allocator and get reused while some other CPU still has a stale TLB entry pointing at them -- and that CPU then reads or executes through the old translation into a page that now belongs to someone else. When it's executable text mapped as a large folio, the CPU literally fetches instructions out of a reused page, which is where the random crashes come from. (This is about file-backed large folios, not anonymous THP, so transparent_hugepage=never doesn't help.)
How we reproduce it:
- Make a cgroup and set memory.high.
- Run ~45 stress-ng workers in it (e.g. --cpu N --cpu-method all).
- Alongside, run a tiny program that keeps allocating anonymous memory to push the cgroup over memory.high and keep reclaim busy.
Dropping caches first (echo 3 > /proc/sys/vm/drop_caches) makes the text refault as large folios and reproduces it much faster.
To be 100% sure about the mechanism, we filled every reclaimed large folio with 0xCC (INT3) and held onto it instead of freeing it. Under the repro, stress-ng workers immediately hit INT3 at instruction pointers inside their own text -- i.e. CPUs were fetching instructions through stale TLB entries from freed, poisoned pages. stress-ng has no INT3 in its binary, so the only way to execute one is through a stale translation into a freed page. Several CPUs hit it within the same microsecond, which lines up nicely with a single batched unmap whose flush was skipped on more than one CPU.
Upstream this got fixed as a side effect of
commit bc2ff4cbc329 ("mm: free folios in a batch in shrink_folio_list()") which sends large folios down the same flush-before-free batch path.
The patch below is the minimal fix: just flush the deferred batch before freeing a large folio inline.
Jiayuan Chen (1):
mm/vmscan: flush deferred TLB before freeing large folios
mm/vmscan.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
Signature · 1 line
2.43.0