[PATCH v5 19/31] ntfs: report index validation and block I/O errors
From: Xuewen Wang
Date: Mon Nov 02 2026 - 02:44:16 EST
From: Dandan Wu <wudandan@xxxxxxxxxx>
Index validation and block I/O can detect corrupt entries, MST
failures and short reads without reporting them. Use EFSCORRUPTED for
corruption and report failures against the index owner.
Preserve block-read errors through index lookup and update its handling
of corrupt search results. Keep underlying I/O errors distinct and
normalize short index-block writes to EIO.
Report locally detected block corruption at the common exit, without
repeating corruption already reported by the attribute-read path.
Signed-off-by: Dandan Wu <wudandan@xxxxxxxxxx>
---
Validation:
- Thirty combined index/bitmap cases exercise the real attribute reader
with the index-block wrapper and bitmap modifier. This set also covers
patch 22.
- Propagated EUCLEAN is reported once on index reads; short reads, invalid
positions, MST failures and local block corruption still report.
- All pass in an extracted-source userspace harness with controlled
dependencies under ASan/UBSan; checks cover reporting calls.
fs/ntfs/index.c | 113 +++++++++++++++++++++++++++++-------------------
1 file changed, 69 insertions(+), 44 deletions(-)
diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index e719d3afcfb0..cd9f31da5189 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -37,7 +37,7 @@ static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol,
le16_to_cpu(ie->length))) {
ntfs_error(vol->sb, "Overflow from index entry in inode %lld\n",
(long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
} else {
if (collation_rule == COLLATION_FILE_NAME) {
@@ -47,7 +47,7 @@ static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol,
ntfs_error(vol->sb,
"File name overflow from index entry in inode %lld\n",
(long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
} else {
if (ie->data.vi.data_length &&
@@ -57,7 +57,7 @@ static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol,
ntfs_error(vol->sb,
"Data overflow from index entry in inode %lld\n",
(long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
}
}
@@ -104,8 +104,10 @@ static int ntfs_ib_write(struct ntfs_index_context *icx, struct index_block *ib)
ntfs_debug("vcn: %lld\n", vcn);
ret = pre_write_mst_fixup((struct ntfs_record *)ib, icx->block_size);
- if (ret)
- return -EIO;
+ if (ret) {
+ ret = -EFSCORRUPTED;
+ goto out;
+ }
ret = ntfs_inode_attr_pwrite(VFS_I(icx->ia_ni),
ntfs_ib_vcn_to_pos(icx, vcn), icx->block_size,
@@ -115,12 +117,18 @@ static int ntfs_ib_write(struct ntfs_index_context *icx, struct index_block *ib)
post_write_mst_fixup((struct ntfs_record *)ib);
if (ret != icx->block_size) {
+ if (ret >= 0)
+ ret = -EIO;
ntfs_debug("Failed to write index block %lld, inode %llu",
vcn, (unsigned long long)icx->idx_ni->mft_no);
- return ret;
+ goto out;
}
- return 0;
+ ret = 0;
+out:
+ if (ret)
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), ret);
+ return ret;
}
static int ntfs_icx_ib_write(struct ntfs_index_context *icx)
@@ -285,7 +293,7 @@ static int ntfs_index_header_inconsistent(struct ntfs_volume *vol,
ntfs_error(vol->sb,
"index block in inode %llu is smaller than an index header.",
(unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
entries_offset = le32_to_cpu(ih->entries_offset);
@@ -297,28 +305,28 @@ static int ntfs_index_header_inconsistent(struct ntfs_volume *vol,
ntfs_error(vol->sb,
"Invalid index entry offset in inode %llu.",
(unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (index_length <= entries_offset) {
ntfs_error(vol->sb,
"No space for index entries in inode %llu.",
(unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (allocated_size < index_length) {
ntfs_error(vol->sb,
"Index entries overflow in inode %llu.",
(unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (allocated_size > bytes_available || index_length > bytes_available) {
ntfs_error(vol->sb,
"Index entries in inode %llu exceed the available buffer.",
(unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
return 0;
@@ -338,25 +346,25 @@ int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol,
ntfs_error(vol->sb,
"Index entry out of bounds in inode %llu.",
(unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (le16_to_cpu(ie->length) < sizeof(struct index_entry_header)) {
ntfs_error(vol->sb,
"Index entry too small in inode %llu.",
inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (ntfs_ie_end(ie))
break;
if (!ie->key_length)
- return -EIO;
+ return -EFSCORRUPTED;
if (ntfs_index_entry_inconsistent(vol, ie,
collation_rule, inum))
- return -EIO;
+ return -EFSCORRUPTED;
}
return 0;
@@ -504,7 +512,7 @@ static u32 ntfs_ib_canonical_entries_offset(u32 block_size)
* The size of block is assumed to have been checked to be what is
* defined in the index root.
*
- * Returns 0 if no error was found, -EIO otherwise
+ * Returns 0 if no error was found, -EFSCORRUPTED otherwise
*
* |<--->| offsetof(struct index_block, index)
* | |<--->| sizeof(struct index_header)
@@ -533,7 +541,7 @@ int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
if (!ntfs_is_indx_record(ib->magic)) {
ntfs_error(sb, "Corrupt index block signature: vcn %lld inode %llu\n",
vcn, (unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (le64_to_cpu(ib->index_block_vcn) != vcn) {
@@ -541,21 +549,21 @@ int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
"Corrupt index block: s64 (%lld) is different from expected s64 (%lld) in inode %llu\n",
(long long)le64_to_cpu(ib->index_block_vcn),
vcn, inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (ib_size != block_size) {
ntfs_error(sb,
"Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n",
vcn, inum, ib_size, block_size);
- return -EIO;
+ return -EFSCORRUPTED;
}
if (ntfs_index_header_inconsistent(vol, &ib->index,
block_size -
offsetof(struct index_block, index),
inum))
- return -EIO;
+ return -EFSCORRUPTED;
if (le32_to_cpu(ib->index.entries_offset) <
ntfs_ib_canonical_entries_offset(block_size)) {
@@ -563,11 +571,10 @@ int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
"Index block: entries offset %u is below the minimum for a %u-byte block: vcn %lld inode %llu\n",
le32_to_cpu(ib->index.entries_offset), block_size,
vcn, inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
-
if (ntfs_index_entries_inconsistent(vol, &ib->index, cr, inum))
- return -EIO;
+ return -EFSCORRUPTED;
return 0;
}
@@ -580,7 +587,7 @@ int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
if (value_length < offsetof(struct index_root, index)) {
ntfs_error(vol->sb, "$INDEX_ROOT in inode %llu is too small.",
(unsigned long long)inum);
- return -EIO;
+ return -EFSCORRUPTED;
}
return ntfs_index_header_inconsistent(vol, &ir->index,
@@ -686,7 +693,7 @@ static int ntfs_ie_lookup(const void *key, const u32 key_len,
ntfs_error(icx->idx_ni->vol->sb,
"Index entry out of bounds in inode %llu.\n",
(unsigned long long)icx->idx_ni->mft_no);
- return -ERANGE;
+ goto corrupt;
}
/*
@@ -705,7 +712,7 @@ static int ntfs_ie_lookup(const void *key, const u32 key_len,
if (rc == -EINVAL) {
ntfs_error(icx->idx_ni->vol->sb,
"Collation error. Perhaps a filename contains invalid characters?\n");
- return -ERANGE;
+ goto corrupt;
}
/*
* If @key collates before the key of the current entry, there
@@ -739,13 +746,17 @@ static int ntfs_ie_lookup(const void *key, const u32 key_len,
if (*vcn < 0) {
ntfs_error(icx->idx_ni->vol->sb, "Negative vcn in inode %llu\n",
(unsigned long long)icx->idx_ni->mft_no);
- return -EINVAL;
+ goto corrupt;
}
ntfs_debug("Parent entry number %d\n", item);
icx->parent_pos[icx->pindex] = item;
return -EAGAIN;
+
+corrupt:
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), -EFSCORRUPTED);
+ return -EFSCORRUPTED;
}
struct ntfs_inode *ntfs_ia_open(struct ntfs_index_context *icx, struct ntfs_inode *ni)
@@ -765,6 +776,7 @@ struct ntfs_inode *ntfs_ia_open(struct ntfs_index_context *icx, struct ntfs_inod
static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_block *dst)
{
+ int report_err = 0;
s64 pos, ret;
ntfs_debug("vcn: %lld\n", vcn);
@@ -773,20 +785,31 @@ static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_bl
ret = ntfs_inode_attr_pread(VFS_I(icx->ia_ni), pos, icx->block_size, (u8 *)dst);
if (ret != icx->block_size) {
+ if (ret >= 0 || ret == -EINVAL) {
+ ret = -EFSCORRUPTED;
+ report_err = ret;
+ } else if (ret == -EIO) {
+ report_err = ret;
+ }
if (ret == -1)
ntfs_error(icx->idx_ni->vol->sb, "Failed to read index block");
else
ntfs_error(icx->idx_ni->vol->sb,
"Failed to read full index block at %lld\n", pos);
- return -EIO;
+ goto out;
}
- post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size);
- if (ntfs_index_block_inconsistent(icx->idx_ni->vol, dst,
- icx->block_size, vcn, icx->cr,
- icx->idx_ni->mft_no))
- return -EIO;
- return 0;
+ ret = post_read_mst_fixup((struct ntfs_record *)dst, icx->block_size);
+ if (ret || ntfs_index_block_inconsistent(icx->idx_ni->vol, dst,
+ icx->block_size, vcn, icx->cr,
+ icx->idx_ni->mft_no)) {
+ ret = -EFSCORRUPTED;
+ report_err = ret;
+ }
+out:
+ if (report_err)
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni), report_err);
+ return ret;
}
static int ntfs_icx_parent_inc(struct ntfs_index_context *icx)
@@ -803,8 +826,10 @@ static int ntfs_icx_parent_dec(struct ntfs_index_context *icx)
{
icx->pindex--;
if (icx->pindex < 0) {
+ ntfs_report_file_metadata_error(VFS_I(icx->idx_ni),
+ -EFSCORRUPTED);
ntfs_error(icx->idx_ni->vol->sb, "Corrupt index pointer (%d)", icx->pindex);
- return -EINVAL;
+ return -EFSCORRUPTED;
}
return 0;
}
@@ -886,7 +911,7 @@ int ntfs_index_lookup(const void *key, const u32 key_len, struct ntfs_index_cont
old_vcn = VCN_INDEX_ROOT_PARENT;
err = ntfs_ie_lookup(key, key_len, icx, &ir->index, &vcn, &ie);
- if (err == -ERANGE || err == -EINVAL)
+ if (err == -EFSCORRUPTED)
goto err_out;
icx->ir = ir;
@@ -911,21 +936,19 @@ int ntfs_index_lookup(const void *key, const u32 key_len, struct ntfs_index_cont
descend_into_child_node:
icx->parent_vcn[icx->pindex] = old_vcn;
- if (ntfs_icx_parent_inc(icx)) {
- err = -EIO;
+ err = ntfs_icx_parent_inc(icx);
+ if (err)
goto err_out;
- }
old_vcn = vcn;
ntfs_debug("Descend into node with s64 %lld.\n", vcn);
- if (ntfs_ib_read(icx, vcn, ib)) {
- err = -EIO;
+ err = ntfs_ib_read(icx, vcn, ib);
+ if (err)
goto err_out;
- }
err = ntfs_ie_lookup(key, key_len, icx, &ib->index, &vcn, &ie);
if (err != -EAGAIN) {
- if (err == -EINVAL || err == -ERANGE)
+ if (err == -EFSCORRUPTED)
goto err_out;
icx->is_in_root = false;
@@ -935,6 +958,8 @@ int ntfs_index_lookup(const void *key, const u32 key_len, struct ntfs_index_cont
}
if ((ib->index.flags & NODE_MASK) == LEAF_NODE) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(VFS_I(ni), -EFSCORRUPTED);
ntfs_error(icx->idx_ni->vol->sb,
"Index entry with child node found in a leaf node in inode 0x%llx.\n",
(unsigned long long)ni->mft_no);