mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] fs/ntfs3: fix ABBA page-lock deadlock in ni_read_folio_cmpr()
@ 2026-10-13 18:47 Eugene Susla
  0 siblings, 0 replies; only message in thread
From: Eugene Susla @ 2026-10-13 18:47 UTC (permalink / raw)
  To: almaz.alexandrovich; +Cc: ntfs3, linux-fsdevel, linux-kernel, swilczek.lx

ni_read_folio_cmpr() is called with the folio being read locked, then
locks every other page of the compression frame in ascending order via
ntfs_lock_new_page(), sleeping on each page lock. Two tasks reading
different pages of the same frame (e.g. two page faults on an mmap of a
compressed file) can each hold the page the other one needs next:

  task A: faults page 15, holds it, waits for page 0
  task B: faults page 0, holds it, waits for page 15

Both tasks sleep forever in D state:

  __filemap_get_folio_mpol+0x344/0x4e0
  ni_read_folio_cmpr+0x18b/0x490
  ntfs_read_folio+0x8c/0x110
  filemap_read_folio+0x1f/0xa0
  filemap_fault+0x7c0/0x1400

They cannot be killed, and suspend fails because they cannot be frozen.

The same lock order inversion exists between a reader and
ntfs_compress_write(), which locks the frame pages in ascending order
via ntfs_get_frame_pages().

Never sleep on the other page locks from the read path: pass FGP_NOWAIT
and, if a page is locked by someone else, decompress into a temporary
page instead, as is already done for pages that are uptodate. The data
in that page is discarded; the page is left to its lock holder. squashfs
does the same for the other pages of a compressed block.

ni_decompress_file() keeps blocking: it holds no page lock when it starts
locking a frame, so it cannot be part of such a cycle.

Reproduced under virtme-ng on v7.3-rc6 with a 64 MiB LZNT-compressed
file on a loop-mounted ntfs3 image: two processes faulting page 0 and
page 15 of every 64 KiB frame after a cache drop hang in the first pass
(5 of 5 runs). With this patch, 5 runs of 50 passes and one 16-process
run of 20 passes complete, the temporary-page fallback is taken about
46000 times in a 50-pass run, and the pages read and the file's cached
contents match the original data.

Link: https://syzkaller.appspot.com/bug?id=91960c6feb9ae216c9004c0f864f74aab1aebd5a
Fixes: 4342306f0f0d ("fs/ntfs3: Add file operations and implementation")
Assisted-by: LLM
Signed-off-by: Eugene Susla <eugenesusla@gmail.com>
---

Notes (not part of the changelog):

I hit this on 7.2.9 with a Windows dual-boot volume where my home folder
had NTFS compression enabled: two concurrent git commands in one repo
ended up in D state in ni_read_folio_cmpr() and the machine could no
longer suspend. A reboot was the only way out.

AI disclosure: the diagnosis, the reproducer and this patch were produced
with an LLM coding assistant, which also ran the tests listed above on my
machine. I have reviewed the analysis, the patch and the test results.

Not tested:
- concurrent writes: the reader vs ntfs_compress_write() case is
  covered by reasoning only, not by a test;
- LZX/XPRESS (WOF) compressed files, only LZNT;
- the syzbot report above (no reproducer, auto-obsoleted) shows a reader
  waiting in ntfs_lock_new_page() and a writer waiting in
  ntfs_get_frame_pages(), i.e. that reader vs writer case; I believe
  this patch fixes it but have not confirmed that.

Build: W=1 adds no warnings to fs/ntfs3/frecord.o. The reproducer (a
Python script and a guest shell script for virtme-ng, about 100 lines)
is available on request.

 fs/ntfs3/frecord.c | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index bead01a95..9a071b2a1 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -1893,14 +1893,30 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
 	return REPARSE_LINK;
 }
 
+/*
+ * ntfs_lock_new_page - Get a locked page to decompress a frame into.
+ *
+ * With @nowait the page lock is only tried, never waited for: the caller
+ * already holds the lock of another page of the same frame, and a second
+ * reader doing the same for a different page of that frame would deadlock
+ * with it. A page locked by someone else is left to its owner and a
+ * temporary page is used instead.
+ */
 static struct folio *ntfs_lock_new_page(struct address_space *mapping,
-					pgoff_t index, gfp_t gfp)
+					pgoff_t index, gfp_t gfp, bool nowait)
 {
-	struct folio *folio = __filemap_get_folio(
-		mapping, index, FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp);
+	fgf_t fgp = FGP_LOCK | FGP_ACCESSED | FGP_CREAT;
+	struct folio *folio;
 
-	if (IS_ERR(folio))
-		return folio;
+	if (nowait)
+		fgp |= FGP_NOWAIT;
+
+	folio = __filemap_get_folio(mapping, index, fgp, gfp);
+	if (IS_ERR(folio)) {
+		if (!nowait || PTR_ERR(folio) != -EAGAIN)
+			return folio;
+		goto use_tmp;
+	}
 
 	if (!folio_test_uptodate(folio)) {
 		struct page *page = folio_file_page(folio, index);
@@ -1913,6 +1929,7 @@ static struct folio *ntfs_lock_new_page(struct address_space *mapping,
 	/* Use a temporary page to avoid data corruption */
 	folio_unlock(folio);
 	folio_put(folio);
+use_tmp:
 	folio = folio_alloc(gfp, 0);
 	if (!folio)
 		return ERR_PTR(-ENOMEM);
@@ -1975,7 +1992,7 @@ int ni_read_folio_cmpr(struct ntfs_inode *ni, struct folio *folio)
 		if (i == idx)
 			continue;
 
-		f = ntfs_lock_new_page(mapping, index, gfp_mask);
+		f = ntfs_lock_new_page(mapping, index, gfp_mask, true);
 		if (IS_ERR(f)) {
 			err = PTR_ERR(f);
 			goto out1;
@@ -2072,7 +2089,7 @@ int ni_decompress_file(struct ntfs_inode *ni)
 		for (i = 0; i < pages_per_frame; i++, index++) {
 			struct folio *f;
 
-			f = ntfs_lock_new_page(mapping, index, gfp_mask);
+			f = ntfs_lock_new_page(mapping, index, gfp_mask, false);
 			if (IS_ERR(f)) {
 				while (i--) {
 					unlock_page(pages[i]);
-- 
2.56.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-13 18:47 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-13 18:47 [PATCH] fs/ntfs3: fix ABBA page-lock deadlock in ni_read_folio_cmpr() Eugene Susla

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®