* [PATCH v2 1/4] ntfs: validate index block header more strictly
2026-05-23 4:14 [PATCH v2 0/4] ntfs: tighten EA and index metadata validation Hyunchul Lee
@ 2026-05-23 4:14 ` Hyunchul Lee
2026-05-23 4:14 ` [PATCH v2 2/4] ntfs: centalize $INDEX_ROOT header validation Hyunchul Lee
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Hyunchul Lee @ 2026-05-23 4:14 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Hyunchul Lee, linux-fsdevel, linux-kernel, woot000
Modify ntfs_index_block_inconsisent() to perform stricter validation of
INDEX_HEADER geometry in INDX blocks, and update
ntfs_lookup_inode_by_name() to use that function to validate INDX
blocks.
Tested-by: woot000 <woot000@woot000.com>
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
---
fs/ntfs/dir.c | 38 ++++--------------
fs/ntfs/index.c | 101 ++++++++++++++++++++++++++++++++++--------------
fs/ntfs/index.h | 3 ++
3 files changed, 81 insertions(+), 61 deletions(-)
diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c
index 20f5c7074bdd..6745a0e6e3e7 100644
--- a/fs/ntfs/dir.c
+++ b/fs/ntfs/dir.c
@@ -342,43 +342,19 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
dir_ni->mft_no);
goto unm_err_out;
}
- /* Catch multi sector transfer fixup errors. */
- if (unlikely(!ntfs_is_indx_record(ia->magic))) {
- ntfs_error(sb,
- "Directory index record with vcn 0x%llx is corrupt. Corrupt inode 0x%llx. Run chkdsk.",
- vcn, dir_ni->mft_no);
- goto unm_err_out;
- }
- if (le64_to_cpu(ia->index_block_vcn) != vcn) {
- ntfs_error(sb,
- "Actual VCN (0x%llx) of index buffer is different from expected VCN (0x%llx). Directory inode 0x%llx is corrupt or driver bug.",
- le64_to_cpu(ia->index_block_vcn),
- vcn, dir_ni->mft_no);
- goto unm_err_out;
- }
- if (le32_to_cpu(ia->index.allocated_size) + 0x18 !=
- dir_ni->itype.index.block_size) {
- ntfs_error(sb,
- "Index buffer (VCN 0x%llx) of directory inode 0x%llx has a size (%u) differing from the directory specified size (%u). Directory inode is corrupt or driver bug.",
- vcn, dir_ni->mft_no,
- le32_to_cpu(ia->index.allocated_size) + 0x18,
- dir_ni->itype.index.block_size);
- goto unm_err_out;
- }
index_end = (u8 *)ia + dir_ni->itype.index.block_size;
if (index_end > kaddr + PAGE_SIZE) {
ntfs_error(sb,
- "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.",
- vcn, dir_ni->mft_no);
+ "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.",
+ vcn, dir_ni->mft_no);
goto unm_err_out;
}
- index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length);
- if (index_end > (u8 *)ia + dir_ni->itype.index.block_size) {
- ntfs_error(sb,
- "Size of index buffer (VCN 0x%llx) of directory inode 0x%llx exceeds maximum size.",
- vcn, dir_ni->mft_no);
+ err = ntfs_index_block_inconsistent(vol, ia,
+ dir_ni->itype.index.block_size,
+ vcn, dir_ni->mft_no);
+ if (err)
goto unm_err_out;
- }
+ index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length);
/* The first index entry. */
ie = (struct index_entry *)((u8 *)&ia->index +
le32_to_cpu(ia->index.entries_offset));
diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index 146e011c1a41..9713b082b03d 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -303,6 +303,55 @@ static int ntfs_ie_end(struct index_entry *ie)
return ie->flags & INDEX_ENTRY_END || !ie->length;
}
+static int ntfs_index_header_inconsistent(struct ntfs_volume *vol,
+ const struct index_header *ih,
+ u32 bytes_available, u64 inum)
+{
+ u32 entries_offset, index_length, allocated_size;
+
+ if (bytes_available < sizeof(struct index_header)) {
+ ntfs_error(vol->sb,
+ "index block in inode %llu is smaller than an index header.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ entries_offset = le32_to_cpu(ih->entries_offset);
+ index_length = le32_to_cpu(ih->index_length);
+ allocated_size = le32_to_cpu(ih->allocated_size);
+
+ if (entries_offset < sizeof(struct index_header) ||
+ entries_offset > bytes_available) {
+ ntfs_error(vol->sb,
+ "Invalid index entry offset in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ if (index_length <= entries_offset) {
+ ntfs_error(vol->sb,
+ "No space for index entries in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ if (allocated_size < index_length) {
+ ntfs_error(vol->sb,
+ "Index entries overflow in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ 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 0;
+}
+
/*
* Find the last entry in the index block
*/
@@ -437,7 +486,7 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
* 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 -1 otherwise (with errno unchanged)
+ * Returns 0 if no error was found, -EIO otherwise
*
* |<--->| offsetof(struct index_block, index)
* | |<--->| sizeof(struct index_header)
@@ -452,21 +501,20 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
*
* size(struct index_header) <= ent_offset < ind_length <= alloc_size < bk_size
*/
-static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx,
- struct index_block *ib, s64 vcn)
+int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
+ const struct index_block *ib,
+ u32 block_size, s64 vcn, u64 inum)
{
u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) +
offsetof(struct index_block, index);
- struct super_block *sb = icx->idx_ni->vol->sb;
- unsigned long long inum = icx->idx_ni->mft_no;
+ struct super_block *sb = vol->sb;
ntfs_debug("Entering\n");
if (!ntfs_is_indx_record(ib->magic)) {
-
ntfs_error(sb, "Corrupt index block signature: vcn %lld inode %llu\n",
- vcn, (unsigned long long)icx->idx_ni->mft_no);
- return -1;
+ vcn, (unsigned long long)inum);
+ return -EIO;
}
if (le64_to_cpu(ib->index_block_vcn) != vcn) {
@@ -474,30 +522,21 @@ static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx,
"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 -1;
+ return -EIO;
}
- if (ib_size != icx->block_size) {
+ 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, icx->block_size);
- return -1;
+ "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;
}
- if (le32_to_cpu(ib->index.entries_offset) < sizeof(struct index_header)) {
- ntfs_error(sb, "Invalid index entry offset in inode %lld\n", inum);
- return -1;
- }
- if (le32_to_cpu(ib->index.index_length) <=
- le32_to_cpu(ib->index.entries_offset)) {
- ntfs_error(sb, "No space for index entries in inode %lld\n", inum);
- return -1;
- }
- if (le32_to_cpu(ib->index.allocated_size) <
- le32_to_cpu(ib->index.index_length)) {
- ntfs_error(sb, "Index entries overflow in inode %lld\n", inum);
- return -1;
- }
+ if (ntfs_index_header_inconsistent(vol, &ib->index,
+ block_size -
+ offsetof(struct index_block, index),
+ inum))
+ return -EIO;
return 0;
}
@@ -665,12 +704,14 @@ static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_bl
else
ntfs_error(icx->idx_ni->vol->sb,
"Failed to read full index block at %lld\n", pos);
- return -1;
+ return -EIO;
}
post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size);
- if (ntfs_index_block_inconsistent(icx, dst, vcn))
- return -1;
+ if (ntfs_index_block_inconsistent(icx->idx_ni->vol, dst,
+ icx->block_size, vcn,
+ icx->idx_ni->mft_no))
+ return -EIO;
return 0;
}
diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h
index e68d6fabaf9f..3451ec8a1c4e 100644
--- a/fs/ntfs/index.h
+++ b/fs/ntfs/index.h
@@ -89,6 +89,9 @@ struct ntfs_index_context {
bool sync_write;
};
+int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
+ const struct index_block *ib,
+ u32 block_size, s64 vcn, u64 inum);
int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol,
const struct index_entry *ie, __le32 collation_rule, u64 inum);
struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name,
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 2/4] ntfs: centalize $INDEX_ROOT header validation
2026-05-23 4:14 [PATCH v2 0/4] ntfs: tighten EA and index metadata validation Hyunchul Lee
2026-05-23 4:14 ` [PATCH v2 1/4] ntfs: validate index block header more strictly Hyunchul Lee
@ 2026-05-23 4:14 ` Hyunchul Lee
2026-05-23 4:14 ` [PATCH v2 3/4] ntfs: validate index entries on reading Hyunchul Lee
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Hyunchul Lee @ 2026-05-23 4:14 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Hyunchul Lee, linux-fsdevel, linux-kernel, woot000
Add a dedicated helper to perform stricter validation of $INDEX_ROOT and
use it for both directory inodes and named index inodes. This keeps the
root size and header geometry checks consistent across both read paths.
Tested-by: woot000 <woot000@woot000.com>
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
---
fs/ntfs/index.c | 18 ++++++++++++++++++
fs/ntfs/index.h | 3 +++
fs/ntfs/inode.c | 11 ++---------
3 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index 9713b082b03d..97c0e7d6a580 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -541,6 +541,24 @@ int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
return 0;
}
+int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
+ const struct attr_record *a,
+ const struct index_root *ir, u64 inum)
+{
+ u32 value_length = le32_to_cpu(a->data.resident.value_length);
+
+ 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 ntfs_index_header_inconsistent(vol, &ir->index,
+ value_length -
+ offsetof(struct index_root, index),
+ inum);
+}
+
static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name,
u32 name_len, struct ntfs_attr_search_ctx **ctx)
{
diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h
index 3451ec8a1c4e..cad78568d8b3 100644
--- a/fs/ntfs/index.h
+++ b/fs/ntfs/index.h
@@ -89,6 +89,9 @@ struct ntfs_index_context {
bool sync_write;
};
+int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
+ const struct attr_record *a,
+ const struct index_root *ir, u64 inum);
int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
const struct index_block *ib,
u32 block_size, s64 vcn, u64 inum);
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 360bebd1ee3f..63ee7acff4fc 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -890,7 +890,6 @@ static int ntfs_read_locked_inode(struct inode *vi)
*/
if (S_ISDIR(vi->i_mode)) {
struct index_root *ir;
- u8 *ir_end, *index_end;
view_index_meta:
/* It is a directory, find index root attribute. */
@@ -940,10 +939,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
}
ir = (struct index_root *)((u8 *)a +
le16_to_cpu(a->data.resident.value_offset));
- ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length);
- index_end = (u8 *)&ir->index +
- le32_to_cpu(ir->index.index_length);
- if (index_end > ir_end) {
+ if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no)) {
ntfs_error(vi->i_sb, "Directory index is corrupt.");
goto unm_err_out;
}
@@ -1483,7 +1479,6 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
struct attr_record *a;
struct ntfs_attr_search_ctx *ctx;
struct index_root *ir;
- u8 *ir_end, *index_end;
int err = 0;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
@@ -1534,9 +1529,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
}
ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset));
- ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length);
- index_end = (u8 *)&ir->index + le32_to_cpu(ir->index.index_length);
- if (index_end > ir_end) {
+ if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no)) {
ntfs_error(vi->i_sb, "Index is corrupt.");
goto unm_err_out;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 3/4] ntfs: validate index entries on reading
2026-05-23 4:14 [PATCH v2 0/4] ntfs: tighten EA and index metadata validation Hyunchul Lee
2026-05-23 4:14 ` [PATCH v2 1/4] ntfs: validate index block header more strictly Hyunchul Lee
2026-05-23 4:14 ` [PATCH v2 2/4] ntfs: centalize $INDEX_ROOT header validation Hyunchul Lee
@ 2026-05-23 4:14 ` Hyunchul Lee
2026-05-23 4:14 ` [PATCH v2 4/4] ntfs: add bounds check before accessing EA entries Hyunchul Lee
2026-05-25 2:56 ` [PATCH v2 0/4] ntfs: tighten EA and index metadata validation Namjae Jeon
4 siblings, 0 replies; 6+ messages in thread
From: Hyunchul Lee @ 2026-05-23 4:14 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Hyunchul Lee, linux-fsdevel, linux-kernel, woot000
Validate index entries immediately after reading an index root or index
block from disk. This eliminates repeated checks in lookup and readdir,
and reduce the risk of missing checks in those paths.
Tested-by: woot000 <woot000@woot000.com>
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
---
fs/ntfs/dir.c | 28 ++---------------
fs/ntfs/index.c | 84 +++++++++++++++++++++++++++----------------------
fs/ntfs/index.h | 8 +++--
fs/ntfs/inode.c | 8 +++--
4 files changed, 60 insertions(+), 68 deletions(-)
diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c
index 6745a0e6e3e7..4b6bd5f30c65 100644
--- a/fs/ntfs/dir.c
+++ b/fs/ntfs/dir.c
@@ -135,10 +135,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
/* Key length should not be zero if it is not last entry. */
if (!ie->key_length)
goto dir_err_out;
- /* Check the consistency of an index entry */
- if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME,
- dir_ni->mft_no))
- goto dir_err_out;
/*
* We perform a case sensitive comparison and if that matches
* we are done and return the mft reference of the inode (i.e.
@@ -351,7 +347,8 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
}
err = ntfs_index_block_inconsistent(vol, ia,
dir_ni->itype.index.block_size,
- vcn, dir_ni->mft_no);
+ vcn, COLLATION_FILE_NAME,
+ dir_ni->mft_no);
if (err)
goto unm_err_out;
index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length);
@@ -364,15 +361,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
* reach the last entry.
*/
for (;; ie = (struct index_entry *)((u8 *)ie + le16_to_cpu(ie->length))) {
- /* Bounds checks. */
- if ((u8 *)ie < (u8 *)ia ||
- (u8 *)ie + sizeof(struct index_entry_header) > index_end ||
- (u8 *)ie + sizeof(struct index_entry_header) + le16_to_cpu(ie->key_length) >
- index_end || (u8 *)ie + le16_to_cpu(ie->length) > index_end) {
- ntfs_error(sb, "Index entry out of bounds in directory inode 0x%llx.",
- dir_ni->mft_no);
- goto unm_err_out;
- }
/*
* The last entry cannot contain a name. It can however contain
* a pointer to a child node in the B+tree so we just break out.
@@ -382,10 +370,6 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
/* Key length should not be zero if it is not last entry. */
if (!ie->key_length)
goto unm_err_out;
- /* Check the consistency of an index entry */
- if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME,
- dir_ni->mft_no))
- goto unm_err_out;
/*
* We perform a case sensitive comparison and if that matches
* we are done and return the mft reference of the inode (i.e.
@@ -868,6 +852,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
ictx->vcn_size_bits = vol->cluster_size_bits;
else
ictx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
+ ictx->cr = ir->collation_rule;
/* The first index entry. */
next = (struct index_entry *)((u8 *)&ir->index +
@@ -905,13 +890,6 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
if (!next)
break;
nextdir:
- /* Check the consistency of an index entry */
- if (ntfs_index_entry_inconsistent(ictx, vol, next, COLLATION_FILE_NAME,
- ndir->mft_no)) {
- err = -EIO;
- goto out;
- }
-
if (ie_pos < actor->pos) {
ie_pos += le16_to_cpu(next->length);
continue;
diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index 97c0e7d6a580..00e17637f771 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -28,41 +28,10 @@
* length must have been checked beforehand to not overflow from the
* index record.
*/
-int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx,
- struct ntfs_volume *vol, const struct index_entry *ie,
- __le32 collation_rule, u64 inum)
+static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol,
+ const struct index_entry *ie,
+ __le32 collation_rule, u64 inum)
{
- if (icx) {
- struct index_header *ih;
- u8 *ie_start, *ie_end;
-
- if (icx->is_in_root)
- ih = &icx->ir->index;
- else
- ih = &icx->ib->index;
-
- if ((le32_to_cpu(ih->index_length) > le32_to_cpu(ih->allocated_size)) ||
- (le32_to_cpu(ih->index_length) > icx->block_size)) {
- ntfs_error(vol->sb, "%s Index entry(0x%p)'s length is too big.",
- icx->is_in_root ? "Index root" : "Index block",
- (u8 *)icx->entry);
- return -EINVAL;
- }
-
- ie_start = (u8 *)ih + le32_to_cpu(ih->entries_offset);
- ie_end = (u8 *)ih + le32_to_cpu(ih->index_length);
-
- if (ie_start > (u8 *)ie ||
- ie_end <= (u8 *)ie + le16_to_cpu(ie->length) ||
- le16_to_cpu(ie->length) > le32_to_cpu(ih->allocated_size) ||
- le16_to_cpu(ie->length) > icx->block_size) {
- ntfs_error(vol->sb, "Index entry(0x%p) is out of range from %s",
- (u8 *)icx->entry,
- icx->is_in_root ? "index root" : "index block");
- return -EIO;
- }
- }
-
if (ie->key_length &&
((le16_to_cpu(ie->key_length) + offsetof(struct index_entry, key)) >
le16_to_cpu(ie->length))) {
@@ -352,6 +321,44 @@ static int ntfs_index_header_inconsistent(struct ntfs_volume *vol,
return 0;
}
+int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol,
+ const struct index_header *ih,
+ __le32 collation_rule, u64 inum)
+{
+ struct index_entry *ie;
+ u8 *index_end = (u8 *)ih + le32_to_cpu(ih->index_length);
+
+ for (ie = ntfs_ie_get_first((struct index_header *)ih);
+ ; ie = ntfs_ie_get_next(ie)) {
+ if ((u8 *)ie + sizeof(struct index_entry_header) > index_end ||
+ (u8 *)ie + le16_to_cpu(ie->length) > index_end) {
+ ntfs_error(vol->sb,
+ "Index entry out of bounds in inode %llu.",
+ (unsigned long long)inum);
+ return -EIO;
+ }
+
+ if (le16_to_cpu(ie->length) < sizeof(struct index_entry_header)) {
+ ntfs_error(vol->sb,
+ "Index etnry too small in inode %llu.",
+ inum);
+ return -EIO;
+ }
+
+ if (ntfs_ie_end(ie))
+ break;
+
+ if (!ie->key_length)
+ return -EIO;
+
+ if (ntfs_index_entry_inconsistent(vol, ie,
+ collation_rule, inum))
+ return -EIO;
+ }
+
+ return 0;
+}
+
/*
* Find the last entry in the index block
*/
@@ -503,7 +510,8 @@ static struct index_entry *ntfs_ie_dup_novcn(struct index_entry *ie)
*/
int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
const struct index_block *ib,
- u32 block_size, s64 vcn, u64 inum)
+ u32 block_size, s64 vcn, __le32 cr,
+ u64 inum)
{
u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) +
offsetof(struct index_block, index);
@@ -537,7 +545,8 @@ int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
offsetof(struct index_block, index),
inum))
return -EIO;
-
+ if (ntfs_index_entries_inconsistent(vol, &ib->index, cr, inum))
+ return -EIO;
return 0;
}
@@ -727,10 +736,9 @@ static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_bl
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->block_size, vcn, icx->cr,
icx->idx_ni->mft_no))
return -EIO;
-
return 0;
}
diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h
index cad78568d8b3..9a03f53bba47 100644
--- a/fs/ntfs/index.h
+++ b/fs/ntfs/index.h
@@ -94,9 +94,11 @@ int ntfs_index_root_inconsistent(struct ntfs_volume *vol,
const struct index_root *ir, u64 inum);
int ntfs_index_block_inconsistent(struct ntfs_volume *vol,
const struct index_block *ib,
- u32 block_size, s64 vcn, u64 inum);
-int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol,
- const struct index_entry *ie, __le32 collation_rule, u64 inum);
+ u32 block_size, s64 vcn,
+ __le32 cr, u64 inum);
+int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol,
+ const struct index_header *ih,
+ __le32 collation_rule, u64 inum);
struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name,
u32 name_len);
void ntfs_index_ctx_put(struct ntfs_index_context *ictx);
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 63ee7acff4fc..9717fb5b4709 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -939,7 +939,9 @@ static int ntfs_read_locked_inode(struct inode *vi)
}
ir = (struct index_root *)((u8 *)a +
le16_to_cpu(a->data.resident.value_offset));
- if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no)) {
+ if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no) ||
+ ntfs_index_entries_inconsistent(ni->vol, &ir->index,
+ ir->collation_rule, ni->mft_no)) {
ntfs_error(vi->i_sb, "Directory index is corrupt.");
goto unm_err_out;
}
@@ -1529,7 +1531,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
}
ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset));
- if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no)) {
+ if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no) ||
+ ntfs_index_entries_inconsistent(vol, &ir->index,
+ ir->collation_rule, ni->mft_no)) {
ntfs_error(vi->i_sb, "Index is corrupt.");
goto unm_err_out;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 4/4] ntfs: add bounds check before accessing EA entries
2026-05-23 4:14 [PATCH v2 0/4] ntfs: tighten EA and index metadata validation Hyunchul Lee
` (2 preceding siblings ...)
2026-05-23 4:14 ` [PATCH v2 3/4] ntfs: validate index entries on reading Hyunchul Lee
@ 2026-05-23 4:14 ` Hyunchul Lee
2026-05-25 2:56 ` [PATCH v2 0/4] ntfs: tighten EA and index metadata validation Namjae Jeon
4 siblings, 0 replies; 6+ messages in thread
From: Hyunchul Lee @ 2026-05-23 4:14 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Hyunchul Lee, linux-fsdevel, linux-kernel
in ntfs_ea_lookup and ntfs_listxattr, this verifies that there is enough
space in the EA entry before accessing the next_entry_offset field of
the EA entry.
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
---
fs/ntfs/ea.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index c4a4a3e3e599..0cd192752b7c 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -53,11 +53,11 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name,
loff_t offset, p_ea_size;
unsigned int next;
- if (ea_buf_size < sizeof(struct ea_attr))
- goto out;
-
offset = 0;
do {
+ if (ea_buf_size - offset < sizeof(struct ea_attr))
+ break;
+
p_ea = (const struct ea_attr *)&ea_buf[offset];
next = le32_to_cpu(p_ea->next_entry_offset);
p_ea_size = next ? next : (ea_buf_size - offset);
@@ -479,13 +479,13 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0)
goto out;
- if (ea_info_qsize < sizeof(struct ea_attr)) {
- err = -EIO;
- goto out;
- }
-
offset = 0;
do {
+ if (ea_info_qsize - offset < sizeof(struct ea_attr)) {
+ err = -EIO;
+ goto out;
+ }
+
p_ea = (const struct ea_attr *)&ea_buf[offset];
next = le32_to_cpu(p_ea->next_entry_offset);
ea_size = next ? next : (ea_info_qsize - offset);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH v2 0/4] ntfs: tighten EA and index metadata validation
2026-05-23 4:14 [PATCH v2 0/4] ntfs: tighten EA and index metadata validation Hyunchul Lee
` (3 preceding siblings ...)
2026-05-23 4:14 ` [PATCH v2 4/4] ntfs: add bounds check before accessing EA entries Hyunchul Lee
@ 2026-05-25 2:56 ` Namjae Jeon
4 siblings, 0 replies; 6+ messages in thread
From: Namjae Jeon @ 2026-05-25 2:56 UTC (permalink / raw)
To: Hyunchul Lee; +Cc: linux-fsdevel, linux-kernel, charsyam
On Sat, May 23, 2026 at 1:14 PM Hyunchul Lee <hyc.lee@gmail.com> wrote:
>
> This series tightens validation of on-disk NTFS metadata in the read path.
>
> The first four patches move index validation closer to the point
> where index metadata is read and tighten the structural checks for both
> $INDEX_ROOT and INDX block headers. The last patch adds a missing bounds
> check before accessing fields in an EA entry.
>
> To: Namjae Jeon <linkinjeon@kernel.org>
> Cc: linux-fsdevel@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: charsyam@gmail.com
> Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
>
> Hyunchul Lee (4):
> ntfs: validate index block header more strictly
> ntfs: centalize $INDEX_ROOT header validation
> ntfs: validate index entries on reading
> ntfs: add bounds check before accessing EA entries
Applied them to #ntfs-next.
Thanks!
^ permalink raw reply [flat|nested] 6+ messages in thread