Re: [PATCH] fs/ntfs3: validate index entry key bounds
Konstantin Komarov <[email protected]> Fri, 22 May 2026 17:23:19 +0200
| Newsgroups | dev.linux.lists.ntfs3,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
On 4/24/26 05:47, ZhengYuan Huang wrote: > [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 <[email protected]> > --- > 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; > } Hello, Sorry for the delay. Your patch is applied, thanks. Regards, Konstantin