From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dy1-f170.google.com (mail-dy1-f170.google.com [74.125.82.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 54CAE476060 for ; Tue, 13 Oct 2026 18:47:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.170 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791917253; cv=none; b=Sn0KH/Y9HqlUdng0k2fy+eZb4XOnDDHrZujeVkbSXySTM4Of+WyoMg3S98jk1cwJ3NwM+0VERU8WrmKqVd4ETltEpxr2ggPq3nXZRhptYn8bRGrYa5hkS7GaVK8/DvPW4BbYn6ABZwCwGICqmvtEikI2mqZuH55k/Yf351j7ias= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791917253; c=relaxed/simple; bh=pYjY9rWfr21kO4iNAj0qb5nTvYGVZnDwiPxGonsD5Js=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=KpA5oGpGBFWV7GBWwGwMHqWQI1Y05T1nSkCOW5xUokppdvpj89HFRcwwIAM4xYjhYiIs6Hmrf2oXHbdwuqx8rSJ0jQ1CUcfGPFlOyH9HCGS5NdTJC2cy6O3sQVHSsdqCWN784gdCZJlMekRZlBYs2p4Syjk4tGC/hCj9bHulTPI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=oVVR5shj; arc=none smtp.client-ip=74.125.82.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="oVVR5shj" Received: by mail-dy1-f170.google.com with SMTP id 5a478bee46e88-3584f70a5aaso414120eec.1 for ; Tue, 13 Oct 2026 11:47:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1791917251; x=1792522051; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=udG0DXbTIaYDWqaYT2920eFyVeAlI4kC5bwVowQYt8Y=; b=oVVR5shjsFYu4XjANAvCnm6KQmuDNGhs1m97UTqC9YM5VlZ7sBhnSR6ZIvS/vZMClE VWEvJ8oYWO7hQOJ+pebRds+R4+t067u3pXEA9PAFQnZvgaHwV7yRwNLEMcOwve/QTsKO 3JaBiTTvWtA+nNt61uFAUJ9jxaV7bBb7MB4RTf1nSn9lM3VoBrufv17zebPrLtnuDBCa OR8qhfkl1Rpa6QRyiN/48HkVCRj8NkUbtCMHVdDtC0LcyYqa1VTVLL44h7IZGXR06CJ4 aWaj3+IetSfTsK3/hRoNE9l+80Wxhbq4+e9/seTbGNCHTgycUGagKq/JDkXUsdpQlOy0 5W+g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791917251; x=1792522051; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=udG0DXbTIaYDWqaYT2920eFyVeAlI4kC5bwVowQYt8Y=; b=uB6jiJwjW++8FNrpnENe6f1NfT78fb4tToCdOLfbmhB1IEbbM2paGbEDdabdfMpEZ5 2nApyQDiqEIK/YUjNcrhs8DeZrXNJ+38CmXYz2a7LxJImufdGsvwMmFq6k6/RMTbWv5Y UeGdhv0VR1ZeTOALvCECAoZ/NdJf6hFmQLZypIPOUgHuCLhbIdvVWRDg3aQT5mI+aPtl SNxh5/jQ9+IR1swzFJbXza9nkvuNYjFxXheTahCZEhoGaJM0543mkh4pUSQqUMxwEsHj d8XZWAUFftG91wWHElvOKT5BegN5HTA2zKej1RhT4KdcKW1TLfFe1X9Y/oPzQ5UL813d kkuw== X-Forwarded-Encrypted: i=1; AKwUvBy5kPcklZysJ4YC6lFFtrxvGmy/QkOZlUz3J/a+gerRbu/XW7buE62DUWo6LTyBETSXgBjfedKPlE59E60=@vger.kernel.org X-Gm-Message-State: AFq9FYI7uDbj8tIIKLix78Aao6sYA5qVd7/nVbGwsdxwCSLSac5xI+Y6 tBAQ/GBkcB96/VPymqwa7WFmC9fsQv9MO4Vc0Z3ncL5iMS/HQozC0x7g X-Gm-Gg: AYBFou2Ki90mjSjl95ljd+HgNZGUW1VmdxowRur3p9pd4kHvGzwasgh+qnryVXK/tYh jVtO0hn7pLq8haLa5Vg94LNFePmymHoDdoWlA9d/7szAgCsWQtpzz4nwR7bdNp5insgx27fIc4Z tBd32ZxiSJxxG9+4aVCIfGBed8IPHymo5qVVuNu+wB2cwa4nmy9J+u4fOtO7Zt5fHmdGn2uAKUd Lcd94zmrfwrdtVxwIFb8U92XrXTzakLVKtsBkbcdoOASknQ3Sq2e4u8GhSReelmP29IXOsLKoEE XDtc1negaif1WpMg9b9NfYsBUN/K+/yWF5P1dUXgj4UaYi0A7SdAvmadep99om6VWY3x+n42ED+ UhqWxcHxYEXijueEr2fr7Br7uykhAXOxaikf2TjeJaIzMCcnvPLiIplcNRF/i6TvuQoz6mvqHb5 rdBgOfWBFUNhqx32Xpv8Rmejy/r78ERulHLSjtKSOTaTg8MNy3lgERCyY34mVEG9QqNG8K1oNtq il5xNI74/OpDod/s6K3pcsyElIVHG0zrymGsTAIpmQyLt1igCIEjhGmnDM3Zl5nyeFqPs96RaPy eIGnqm/WbAoZdVbQVzQBg+ArBdU= X-Received: by 2002:a05:701b:450d:20b0:143:5c2a:e45f with SMTP id a92af1059eb24-16a4e6cc453mr27577271c88.0.1791917251231; Tue, 13 Oct 2026 11:47:31 -0700 (PDT) Received: from eugene-desktop2.localdomain (c-73-222-231-84.hsd1.ca.comcast.net. [73.222.231.84]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-169a1becefdsm40261107c88.7.2026.10.13.11.47.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 13 Oct 2026 11:47:30 -0700 (PDT) From: Eugene Susla To: almaz.alexandrovich@paragon-software.com Cc: ntfs3@lists.linux.dev, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, swilczek.lx@gmail.com Subject: [PATCH] fs/ntfs3: fix ABBA page-lock deadlock in ni_read_folio_cmpr() Date: Tue, 13 Oct 2026 11:47:28 -0700 Message-ID: <20261013184728.665956-1-eugenesusla@gmail.com> X-Mailer: git-send-email 2.56.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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