* [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®