[PATCH v4 24/31] ntfs: serialize and report export parent reconstruction errors

From: Baolin Liu

Date: Tue Oct 20 2026 - 04:53:28 EST


From: Xuewen Wang <wangxuewen@xxxxxxxxxx>

Exportfs calls ntfs_get_parent() without the child inode lock. Hold the
child's mrec_lock across FILE_NAME lookup and parent loading to stabilize
corruption checks against rename and rmdir.

Report an unused parent as EFSCORRUPTED only for a linked, non-deleting
child. Report missing required or malformed FILE_NAME attributes, while
continuing past malformed candidates as before. Release the lock before
d_obtain_alias().

Preserve ordinary stale-handle behavior. NTFS returns EFSCORRUPTED for a
confirmed corrupt parent reference; generic open_by_handle_at() decoding
still converts that error to ESTALE.

Signed-off-by: Xuewen Wang <wangxuewen@xxxxxxxxxx>
---
fs/ntfs/namei.c | 43 ++++++++++++++++++++++++++++++++++---------
1 file changed, 34 insertions(+), 9 deletions(-)

diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index 47e52270e8ee..a64ab7881052 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -1730,7 +1730,8 @@ const struct inode_operations ntfs_dir_inode_ops = {
* fs/exportfs/expfs.c::find_exported_dentry() which in turn is called from the
* default ->decode_fh() which is export_decode_fh() in the same file.
*
- * Note: ntfs_get_parent() is called with @d_inode(child_dent)->i_mutex down.
+ * Serialize the file name lookup and parent load against rename and rmdir
+ * with the child's mrec_lock; exportfs does not hold the child's inode lock.
*
* Return the dentry of the parent directory on success or the error code on
* error (IS_ERR() is true).
@@ -1738,6 +1739,7 @@ const struct inode_operations ntfs_dir_inode_ops = {
static struct dentry *ntfs_get_parent(struct dentry *child_dent)
{
struct inode *vi = d_inode(child_dent);
+ struct inode *parent;
struct ntfs_inode *ni = NTFS_I(vi);
struct mft_record *mrec;
struct ntfs_attr_search_ctx *ctx;
@@ -1747,15 +1749,20 @@ static struct dentry *ntfs_get_parent(struct dentry *child_dent)
int err;

ntfs_debug("Entering for inode 0x%llx.", ni->mft_no);
+ mutex_lock(&ni->mrec_lock);
/* Get the mft record of the inode belonging to the child dentry. */
mrec = map_mft_record(ni);
- if (IS_ERR(mrec))
- return ERR_CAST(mrec);
+ if (IS_ERR(mrec)) {
+ err = PTR_ERR(mrec);
+ goto unlock_err;
+ }
/* Find the first file name attribute in the mft record. */
ctx = ntfs_attr_get_search_ctx(ni, mrec);
if (IS_ERR(ctx)) {
unmap_mft_record(ni);
- return ERR_PTR(PTR_ERR(ctx));
+ err = PTR_ERR(ctx);
+ ctx = NULL;
+ goto unlock_err;
}
try_next:
err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, CASE_SENSITIVE, 0, NULL,
@@ -1763,27 +1770,45 @@ static struct dentry *ntfs_get_parent(struct dentry *child_dent)
if (unlikely(err)) {
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(ni);
- if (err == -ENOENT)
+ if (err == -ENOENT && vi->i_nlink) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(vi, -EFSCORRUPTED);
ntfs_error(vi->i_sb,
"Inode 0x%llx does not have a file name attribute. Run chkdsk.",
ni->mft_no);
- return ERR_PTR(err);
+ }
+ goto unlock_err;
}
attr = ctx->attr;
- if (unlikely(attr->non_resident))
+ if (unlikely(attr->non_resident)) {
+ ntfs_report_file_metadata_error(vi, -EFSCORRUPTED);
goto try_next;
+ }
fn = (struct file_name_attr *)((u8 *)attr +
le16_to_cpu(attr->data.resident.value_offset));
if (unlikely((u8 *)fn + le32_to_cpu(attr->data.resident.value_length) >
- (u8 *)attr + le32_to_cpu(attr->length)))
+ (u8 *)attr + le32_to_cpu(attr->length))) {
+ ntfs_report_file_metadata_error(vi, -EFSCORRUPTED);
goto try_next;
+ }
/* Get the inode number of the parent directory. */
parent_ino = MREF_LE(fn->parent_directory);
/* Release the search context and the mft record of the child. */
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(ni);

- return d_obtain_alias(ntfs_iget(vi->i_sb, parent_ino));
+ parent = ntfs_iget(vi->i_sb, parent_ino);
+ if (IS_ERR(parent) && PTR_ERR(parent) == -ENOENT &&
+ vi->i_nlink && !NInoBeingDeleted(ni)) {
+ ntfs_report_file_metadata_error(vi, -EFSCORRUPTED);
+ parent = ERR_PTR(-EFSCORRUPTED);
+ }
+ mutex_unlock(&ni->mrec_lock);
+ return d_obtain_alias(parent);
+
+unlock_err:
+ mutex_unlock(&ni->mrec_lock);
+ return ERR_PTR(err);
}

static struct inode *ntfs_nfs_get_inode(struct super_block *sb,
--
2.51.0