Skip to content

Commit 98d6e5d

Browse files
Gality369aalexandrovich
authored andcommitted
fs/ntfs3: validate index entry key bounds
[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> Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
1 parent b1c1101 commit 98d6e5d

2 files changed

Lines changed: 56 additions & 7 deletions

File tree

fs/ntfs3/fslog.c

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2599,11 +2599,12 @@ static int read_next_log_rec(struct ntfs_log *log, struct lcb *lcb, u64 *lsn)
25992599

26002600
bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
26012601
{
2602+
const bool has_subnode = hdr_has_subnode(hdr);
26022603
__le16 mask;
26032604
u32 min_de, de_off, used, total;
26042605
const struct NTFS_DE *e;
26052606

2606-
if (hdr_has_subnode(hdr)) {
2607+
if (has_subnode) {
26072608
min_de = sizeof(struct NTFS_DE) + sizeof(u64);
26082609
mask = NTFS_IE_HAS_SUBNODES;
26092610
} else {
@@ -2620,20 +2621,33 @@ bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes)
26202621
return false;
26212622
}
26222623

2623-
e = Add2Ptr(hdr, de_off);
2624+
e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
26242625
for (;;) {
26252626
u16 esize = le16_to_cpu(e->size);
2626-
struct NTFS_DE *next = Add2Ptr(e, esize);
2627+
u16 key_size = le16_to_cpu(e->key_size);
2628+
u16 data_size;
26272629

2628-
if (esize < min_de || PtrOffset(hdr, next) > used ||
2630+
if (!IS_ALIGNED(esize, 8) || esize < min_de ||
26292631
(e->flags & NTFS_IE_HAS_SUBNODES) != mask) {
26302632
return false;
26312633
}
26322634

2633-
if (de_is_last(e))
2635+
if (size_add(de_off, esize) > used)
2636+
return false;
2637+
2638+
if (de_is_last(e)) {
2639+
if (key_size)
2640+
return false;
2641+
26342642
break;
2643+
}
2644+
2645+
data_size = esize - min_de;
2646+
if (key_size > data_size)
2647+
return false;
26352648

2636-
e = next;
2649+
de_off += esize;
2650+
e = (const struct NTFS_DE *)((const u8 *)hdr + de_off);
26372651
}
26382652

26392653
return true;

fs/ntfs3/index.c

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,16 +611,51 @@ static const struct NTFS_DE *hdr_insert_head(struct INDEX_HDR *hdr,
611611
*/
612612
static bool index_hdr_check(const struct INDEX_HDR *hdr, u32 bytes)
613613
{
614+
const bool has_subnode = hdr_has_subnode(hdr);
615+
const u16 min_size = sizeof(struct NTFS_DE) +
616+
(has_subnode ? sizeof(u64) : 0);
614617
u32 end = le32_to_cpu(hdr->used);
615618
u32 tot = le32_to_cpu(hdr->total);
616619
u32 off = le32_to_cpu(hdr->de_off);
620+
const struct NTFS_DE *e;
617621

618622
if (!IS_ALIGNED(off, 8) || tot > bytes || end > tot ||
619-
size_add(off, sizeof(struct NTFS_DE)) > end) {
623+
size_add(off, min_size) > end) {
620624
/* incorrect index buffer. */
621625
return false;
622626
}
623627

628+
/* Ensure every key stays inside its entry before lookup walks it. */
629+
e = (const struct NTFS_DE *)((const u8 *)hdr + off);
630+
for (;;) {
631+
u16 e_size = le16_to_cpu(e->size);
632+
u16 key_size = le16_to_cpu(e->key_size);
633+
u16 data_size;
634+
635+
if (!IS_ALIGNED(e_size, 8) || e_size < min_size ||
636+
de_has_vcn(e) != has_subnode) {
637+
/* incorrect index entry. */
638+
return false;
639+
}
640+
641+
if (size_add(off, e_size) > end)
642+
return false;
643+
644+
if (de_is_last(e)) {
645+
if (key_size)
646+
return false;
647+
648+
break;
649+
}
650+
651+
data_size = e_size - min_size;
652+
if (key_size > data_size)
653+
return false;
654+
655+
off += e_size;
656+
e = (const struct NTFS_DE *)((const u8 *)hdr + off);
657+
}
658+
624659
return true;
625660
}
626661

0 commit comments

Comments
 (0)