From: ZhengYuan Huang <gality369@gmail.com>
To: almaz.alexandrovich@paragon-software.com
Cc: ntfs3@lists.linux.dev, linux-kernel@vger.kernel.org,
baijiaju1990@gmail.com, r33s3n6@gmail.com, zzzccc427@gmail.com,
ZhengYuan Huang <gality369@gmail.com>
Subject: [PATCH] fs/ntfs3: validate index entry key bounds
Date: Fri, 24 Apr 2026 11:47:36 +0800 [thread overview]
Message-ID: <20260424034736.1975025-1-gality369@gmail.com> (raw)
[BUG]
A malformed NTFS directory index entry can advertise a key_size larger
than the bytes actually present in its NTFS_DE payload. Directory lookup
then passes that malformed key to cmp_fnames(), which can read past the
end of the kmalloc'ed index buffer.
BUG: KASAN: slab-out-of-bounds in fname_full_size fs/ntfs3/ntfs.h:590 [inline]
BUG: KASAN: slab-out-of-bounds in cmp_fnames+0x1ea/0x230 fs/ntfs3/index.c:46
Read of size 1 at addr ffff88801c313018 by task syz.6.3365/9279
Call Trace:
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0xbe/0x130 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xd1/0x650 mm/kasan/report.c:482
kasan_report+0xfb/0x140 mm/kasan/report.c:595
__asan_report_load1_noabort+0x14/0x30 mm/kasan/report_generic.c:378
fname_full_size fs/ntfs3/ntfs.h:590 [inline]
cmp_fnames+0x1ea/0x230 fs/ntfs3/index.c:46
hdr_find_e.isra.0+0x3ed/0x670 fs/ntfs3/index.c:762
indx_find+0x4b5/0x900 fs/ntfs3/index.c:1186
dir_search_u+0x2c0/0x460 fs/ntfs3/dir.c:254
ntfs_lookup+0x1cc/0x2a0 fs/ntfs3/namei.c:85
__lookup_slow+0x241/0x450 fs/namei.c:1816
lookup_slow fs/namei.c:1833 [inline]
walk_component+0x31c/0x570 fs/namei.c:2151
link_path_walk+0x592/0xd60 fs/namei.c:2519
path_lookupat+0x138/0x660 fs/namei.c:2675
filename_lookup+0x1f3/0x560 fs/namei.c:2705
filename_setxattr+0xad/0x1c0 fs/xattr.c:660
path_setxattrat+0x1d8/0x280 fs/xattr.c:713
__do_sys_lsetxattr fs/xattr.c:754 [inline]
__se_sys_lsetxattr fs/xattr.c:750 [inline]
__x64_sys_lsetxattr+0xd0/0x150 fs/xattr.c:750
...
Allocated by task 9279:
kasan_save_stack+0x39/0x70 mm/kasan/common.c:56
kasan_save_track+0x14/0x40 mm/kasan/common.c:77
kasan_save_alloc_info+0x37/0x60 mm/kasan/generic.c:573
poison_kmalloc_redzone mm/kasan/common.c:400 [inline]
__kasan_kmalloc+0xc3/0xd0 mm/kasan/common.c:417
kasan_kmalloc include/linux/kasan.h:262 [inline]
__do_kmalloc_node mm/slub.c:5650 [inline]
__kmalloc_noprof+0x2bd/0x900 mm/slub.c:5662
kmalloc_noprof include/linux/slab.h:961 [inline]
indx_read+0x41d/0xad0 fs/ntfs3/index.c:1059
indx_find+0x447/0x900 fs/ntfs3/index.c:1179
dir_search_u+0x2c0/0x460 fs/ntfs3/dir.c:254
ntfs_lookup+0x1cc/0x2a0 fs/ntfs3/namei.c:85
__lookup_slow+0x241/0x450 fs/namei.c:1816
lookup_slow fs/namei.c:1833 [inline]
walk_component+0x31c/0x570 fs/namei.c:2151
link_path_walk+0x592/0xd60 fs/namei.c:2519
path_lookupat+0x138/0x660 fs/namei.c:2675
filename_lookup+0x1f3/0x560 fs/namei.c:2705
filename_setxattr+0xad/0x1c0 fs/xattr.c:660
path_setxattrat+0x1d8/0x280 fs/xattr.c:713
__do_sys_lsetxattr fs/xattr.c:754 [inline]
__se_sys_lsetxattr fs/xattr.c:750 [inline]
__x64_sys_lsetxattr+0xd0/0x150 fs/xattr.c:750
...
[CAUSE]
The index-header validators only validated INDEX_HDR-level geometry.
They did not walk each NTFS_DE to verify entry alignment, subnode
layout, or that key_size fit inside the entry payload. They also
allowed a last sentinel entry to carry a non-zero key_size.
[FIX]
Walk every NTFS_DE in ntfs3's index-header validators and reject
entries with invalid layout, mismatched subnode state, oversized
key_size, or non-zero sentinel keys before lookup or log replay can
consume them.
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
fs/ntfs3/fslog.c | 26 ++++++++++++++++++++------
fs/ntfs3/index.c | 37 ++++++++++++++++++++++++++++++++++++-
2 files changed, 56 insertions(+), 7 deletions(-)
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index acfa18b84401..ff8e2a808d12 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -2599,11 +2599,12 @@ static int read_next_log_rec(struct ntfs_log *log, struct lcb *lcb, u64 *lsn)
bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
{
+ const bool has_subnode = hdr_has_subnode(hdr);
__le16 mask;
u32 min_de, de_off, used, total;
const struct NTFS_DE *e;
- if (hdr_has_subnode(hdr)) {
+ if (has_subnode) {
min_de = sizeof(struct NTFS_DE) + sizeof(u64);
mask = NTFS_IE_HAS_SUBNODES;
} else {
@@ -2620,20 +2621,33 @@ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
return false;
}
- e = Add2Ptr(hdr, de_off);
+ e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
for (;;) {
u16 esize = le16_to_cpu(e->size);
- struct NTFS_DE *next = Add2Ptr(e, esize);
+ u16 key_size = le16_to_cpu(e->key_size);
+ u16 data_size;
- if (esize < min_de || PtrOffset(hdr, next) > used ||
+ if (!IS_ALIGNED(esize, 8) || esize < min_de ||
(e->flags & NTFS_IE_HAS_SUBNODES) != mask) {
return false;
}
- if (de_is_last(e))
+ if (size_add(de_off, esize) > used)
+ return false;
+
+ if (de_is_last(e)) {
+ if (key_size)
+ return false;
+
break;
+ }
+
+ data_size = esize - min_de;
+ if (key_size > data_size)
+ return false;
- e = next;
+ de_off += esize;
+ e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
}
return true;
diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c
index 5344b29b0577..faedfadf6335 100644
--- a/fs/ntfs3/index.c
+++ b/fs/ntfs3/index.c
@@ -611,16 +611,51 @@ static const struct NTFS_DE *hdr_insert_head(struct INDEX_HDR *hdr,
*/
static bool index_hdr_check(const struct INDEX_HDR *hdr, u32 bytes)
{
+ const bool has_subnode = hdr_has_subnode(hdr);
+ const u16 min_size = sizeof(struct NTFS_DE) +
+ (has_subnode ? sizeof(u64) : 0);
u32 end = le32_to_cpu(hdr->used);
u32 tot = le32_to_cpu(hdr->total);
u32 off = le32_to_cpu(hdr->de_off);
+ const struct NTFS_DE *e;
if (!IS_ALIGNED(off, 8) || tot > bytes || end > tot ||
- size_add(off, sizeof(struct NTFS_DE)) > end) {
+ size_add(off, min_size) > end) {
/* incorrect index buffer. */
return false;
}
+ /* Ensure every key stays inside its entry before lookup walks it. */
+ e = (const struct NTFS_DE *)((const u8 *)hdr + off);
+ for (;;) {
+ u16 e_size = le16_to_cpu(e->size);
+ u16 key_size = le16_to_cpu(e->key_size);
+ u16 data_size;
+
+ if (!IS_ALIGNED(e_size, 8) || e_size < min_size ||
+ de_has_vcn(e) != has_subnode) {
+ /* incorrect index entry. */
+ return false;
+ }
+
+ if (size_add(off, e_size) > end)
+ return false;
+
+ if (de_is_last(e)) {
+ if (key_size)
+ return false;
+
+ break;
+ }
+
+ data_size = e_size - min_size;
+ if (key_size > data_size)
+ return false;
+
+ off += e_size;
+ e = (const struct NTFS_DE *)((const u8 *)hdr + off);
+ }
+
return true;
}
reply other threads:[~2026-04-24 3:47 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260424034736.1975025-1-gality369@gmail.com \
--to=gality369@gmail.com \
--cc=almaz.alexandrovich@paragon-software.com \
--cc=baijiaju1990@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ntfs3@lists.linux.dev \
--cc=r33s3n6@gmail.com \
--cc=zzzccc427@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox