mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] fs/ntfs3: fix ENOENT in indx_new() for blocks with unloaded runs
       [not found] <20261029172655.432752-1-m.sadowniczyk@gmail.com>
@ 2026-10-30 15:56 ` Maciej Sadowniczyk
  0 siblings, 0 replies; only message in thread
From: Maciej Sadowniczyk @ 2026-10-30 15:56 UTC (permalink / raw)
  To: Konstantin Komarov; +Cc: ntfs3, linux-kernel

indx->alloc_run is filled on demand, one $INDEX_ALLOCATION segment at
a time. When the attribute is split over several MFT records (via
$ATTRIBUTE_LIST), a segment none of whose blocks has been read since
the inode was loaded may have no runs in alloc_run. indx_read_ra() handles
this: when ntfs_read_bh_ra() fails with -ENOENT, it loads the runs with
attr_load_runs_range() and retries.

indx_new() has no such fallback. When indx_add_allocate() takes a free
bit from the index bitmap that lies inside the existing allocation, the
allocation is not extended and nothing loads the runs for that block.
ntfs_get_bh() in indx_new() then fails with -ENOENT, and so does the
insert that needed the new index block.

This was hit on $Extend/$Reparse, which gets an entry for every
symlink. After many symlinks had been created and some deleted, and
the volume remounted, symlink() started to fail with ENOENT and kept
failing: the bit is marked used only after indx_new() succeeds, so
indx_find_free() returned the same free bit every time.

  ntfs_create_inode
   ntfs_insert_reparse
    indx_insert_entry
     indx_insert_into_buffer
      indx_new
       ntfs_get_bh  <- -ENOENT

Creating regular files and directories, which add no $Reparse entry,
kept working. Other indexes, including $I30 of large directories, go
through the same indx_new() path.

Load the missing runs in indx_new() the same way indx_read_ra() does
and retry ntfs_get_bh().

The missing fallback was found with an AI assistant comparing
indx_new() with indx_read_ra() (see Assisted-by).

Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block")
Cc: stable@vger.kernel.org
Closes: https://lore.kernel.org/ntfs3/20261029172655.432752-1-m.sadowniczyk@gmail.com/
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Maciej Sadowniczyk <m.sadowniczyk@gmail.com>
---

Notes:
    The code change is the same as the untested diff in the report (only
    the comment now names indx_read_ra()); it is now tested.
    
    Tested on the ntfs3 devel tree (ee1cb3443cff) in a virtme-ng VM, with
    lockdep and KASAN enabled, using the reproducer from the report:
    
    - without this patch, symlink #14940 fails with ENOENT. The failed
      symlink() leaves nothing behind: after a remount b/l holds exactly
      the 14940 symlinks created before it.
    - with it, all 20000 symlinks are created. After another remount all
      of them read back correctly and can be deleted; ntfsinfo (ntfs-3g)
      then counts 20332 entries in $Extend/$Reparse:$R, i.e. the 20000
      remaining a/l symlinks plus one end entry per INDX block (332).
      ntfsfix -n reports no problems and there are no KASAN reports.
    
    In this tree the reproducer's readdir (find -delete) trips the known
    "bad unlock balance in ntfs_readdir" (syzbot; "[PATCH v2] fs/ntfs3: fix
    double ni_unlock() in ntfs_readdir()"), which turns lockdep off before
    the indx_new() path runs. So I repeated the patched run without any
    readdir, deleting the symlinks by name: debug_locks stayed 1 for the
    whole run, there were no lockdep or KASAN reports, and a function
    trace showed attr_load_runs_range() being called from indx_new(), i.e.
    the new fallback ran.
    
    The failure in the report comes from indx_insert_into_buffer(); the
    indx_insert_into_root() caller and $I30 directories were not
    specifically tested.
    
    Starting from the function_graph trace I collected, an AI assistant
    located the missing fallback by comparing indx_new() with
    indx_read_ra(), and drafted the fix and this changelog. I have reviewed
    and tested them.

 fs/ntfs3/index.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c
index c06b36760..2b0f2a6bd 100644
--- a/fs/ntfs3/index.c
+++ b/fs/ntfs3/index.c
@@ -987,6 +987,23 @@ static struct indx_node *indx_new(struct ntfs_index *indx,
 	}
 
 	err = ntfs_get_bh(ni->mi.sbi, &indx->alloc_run, vbo, bytes, &r->nb);
+	if (err == -ENOENT) {
+		/*
+		 * A reused block may lie in an $INDEX_ALLOCATION segment
+		 * whose runs are not loaded yet. Load them, as
+		 * indx_read_ra() does.
+		 */
+		const struct INDEX_NAMES *in = &s_index_names[indx->type];
+
+		down_write(&indx->run_lock);
+		err = attr_load_runs_range(ni, ATTR_ALLOC, in->name,
+					   in->name_len, &indx->alloc_run, vbo,
+					   vbo + bytes);
+		up_write(&indx->run_lock);
+		if (!err)
+			err = ntfs_get_bh(ni->mi.sbi, &indx->alloc_run, vbo,
+					  bytes, &r->nb);
+	}
 
 	if (err) {
 		kfree(index);

base-commit: ee1cb3443cff5a800948b662756f75b17ca63fe2
-- 
2.56.0


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

only message in thread, other threads:[~2026-10-30 15:56 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20261029172655.432752-1-m.sadowniczyk@gmail.com>
2026-10-30 15:56 ` [PATCH] fs/ntfs3: fix ENOENT in indx_new() for blocks with unloaded runs Maciej Sadowniczyk

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®