summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mm/memfd_luo.c26
1 files changed, 21 insertions, 5 deletions
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 1c9510289312..b8edb9f981d7 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -146,7 +146,6 @@ static int memfd_luo_preserve_folios(struct file *file,
for (i = 0; i < nr_folios; i++) {
struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
struct folio *folio = folios[i];
- unsigned int flags = 0;
err = kho_preserve_folio(folio);
if (err)
@@ -154,8 +153,26 @@ static int memfd_luo_preserve_folios(struct file *file,
folio_lock(folio);
- if (folio_test_dirty(folio))
- flags |= MEMFD_LUO_FOLIO_DIRTY;
+ /*
+ * A dirty folio is one which has been written to. A clean folio
+ * is its opposite. Since a clean folio does not carry user
+ * data, it can be freed by page reclaim under memory pressure.
+ *
+ * Saving the dirty flag at prepare() time doesn't work since it
+ * can change later. Saving it at freeze() also won't work
+ * because the dirty bit is normally synced at unmap and there
+ * might still be a mapping of the file at freeze().
+ *
+ * To see why this is a problem, say a folio is clean at
+ * preserve, but gets dirtied later. The pfolio flags will mark
+ * it as clean. After retrieve, the next kernel might try to
+ * reclaim this folio under memory pressure, losing user data.
+ *
+ * Unconditionally mark it dirty to avoid this problem. This
+ * comes at the cost of making clean folios un-reclaimable after
+ * live update.
+ */
+ folio_mark_dirty(folio);
/*
* If the folio is not uptodate, it was fallocated but never
@@ -174,12 +191,11 @@ static int memfd_luo_preserve_folios(struct file *file,
flush_dcache_folio(folio);
folio_mark_uptodate(folio);
}
- flags |= MEMFD_LUO_FOLIO_UPTODATE;
folio_unlock(folio);
pfolio->pfn = folio_pfn(folio);
- pfolio->flags = flags;
+ pfolio->flags = MEMFD_LUO_FOLIO_DIRTY | MEMFD_LUO_FOLIO_UPTODATE;
pfolio->index = folio->index;
}