From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj1-f45.google.com (mail-pj1-f45.google.com [209.85.216.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D138221CFEF for ; Fri, 24 Apr 2026 03:47:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.45 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777002469; cv=none; b=o1tgYZoMsEi2r7DDRXJcnDK7IzvAkGm+PWYzJWPruMO3Y8TQ/d96MkvUdDKZG06BWDPuSrROUsSuv/79pByniErOZjEH1uW4QA+SpVhbdW4Zpf6zdRXOgEsHUyQZaQ8KkzxqfVMo5+Yfdh0/3dpggIwj2/qfKfcbCwHKQ9OSesM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777002469; c=relaxed/simple; bh=PBBirlauToFaoElrfWH3WRoR82zAWI3rESzMDPUurj0=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=WlGUeS29GycllqKxQb+lrmYyXPWTNKB0B4YZXFNJTZx9r5tEJ3cWUPmIlp8+8Yb6N3VBgcVfTw5BCrVaJszZz9zwslRZZ7Sbst9yOnP+lwk9hZ+e135WV4rW48K7hdcUYYXy0ay0yhRIyxkI+ebzTcKctUOIpPbAlqYdTtqINpw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=AMSjsmpc; arc=none smtp.client-ip=209.85.216.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AMSjsmpc" Received: by mail-pj1-f45.google.com with SMTP id 98e67ed59e1d1-35fc258aaa4so4687043a91.2 for ; Thu, 23 Apr 2026 20:47:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777002467; x=1777607267; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=9R0BaFnb3nh1LLGnzayjWCHAhXdxGea45vvjoN1ngig=; b=AMSjsmpclbxv3vVeMebAF09OBr7orK1q3APAbjw3CZD62/uFuNQUHDCi1Tv6r8uJDC eAiCR4yIYTPiVABCG5TylzMzk/Hjpby1HnYt52vl2pTif53YBG2xZhW11CEDvtzOnKXy fYvTHW0eG9QF4ZMUDnHB+a71sywf1sdYYar498s7Q3pGTSJzCWbJzhVuZHnOZLzHGfd7 j90fb4kc/qhFBn2B8gZLvi2XCG485PyJgbRBdviOKtqYRDke3j/IUhebSpadC6+lX2qU UiJoHRP9PePtK8969Nxm5736Gq1c5b+KzlU0qaI6lW/7ppPWZsixxNNlDeyGSN1iF6t/ xfTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777002467; x=1777607267; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=9R0BaFnb3nh1LLGnzayjWCHAhXdxGea45vvjoN1ngig=; b=KNfvKfvYXoJNyK39I1gLFajl3Ps1HfN6NT4oF3WRCE+cPxasHSOFCQnOeQ09M7m6IO NpBaqn+tGcCnCpnfpI9P2zvL9wz2CTmNb8ar+t8ctSHaCYP0Hhhv45bS4tzfr9NbyPe+ XK5J1vkjHF7o5OmDn7/oYX9l05vVFCtbOpPnVNoZwpttvzzd90umW63EeSpUY6Q/qYfZ TDmu967A8ulXU8h9zCjfjLMcRE04HnT9waw8YI7pzYCRiAy7HHnbVupmcAAPvENDn6hJ qtb+OHt92ANuWedm8tvLE1rNLn2jL6qTzC0a6DrEcqfHpqIqN6u7vKuqJ0CQZ6DEJ9bH ujOg== X-Forwarded-Encrypted: i=1; AFNElJ8YoHmA2WdQZ+Abo0dSq8LjWA9rxvDwxKxQNvQinUv0MC2ZEwN+E2EmugyBUkOi6aZ4OV4SIgiEsh/Iedc=@vger.kernel.org X-Gm-Message-State: AOJu0YxV+FBjhM0XHx5Sqa6tedww/7SDFSwxhYfNEYnUJB+poW10A6qh huwQzEGALLEoyFDb+mXSivi2bvpLu0BXFA1hjBVqXNPtXbcvpVqgv1+SEax5fRICA/E= X-Gm-Gg: AeBDieu5l8ZiYDGj+PLyBsCcKcH/Mcfo4BffBsFlVjLFp+s9HNiIN0flDSibeA9y/aW mG8QWJCuFEvGKT2RjdHj8+Fy/g1OlkSMa4MSD9fp0DyooWwJU06SgR8ya3UhU2RwlL6GFrSZmFo Y82N2K23wD1woxlJnxBcEW/EIsmmk/Bm4i64HJoupuu8dwRlIu86A8sJ3DnofT4zlg7gi+kgekB lqJsKQv5go6UJxIrgsNXKLwK6DeKUVipa+8/QrN12ouQiCblaRs91E8gQjmrTdZBBMXr7JFXqq2 yCunczb1G2wcY1+13KZ9djZOGfwZeoBazwfLvN71GxLTL2oQg7K3yt5Jvt/VwxySBvFUGz7+w1F 7FYjT3m2/WXisgdmNtTlxVKPC5XtyeA6ACQLY1Y/UVWvl9xoz1VX94Jxvgd8HP7Fg/RHVKI1JeK ZA9D6PNLZ5MvtYA8CDah+qPT3xODLXAg9kfV5cIa5fGpPegII2LryE2rosBYNcdYo3AMnyqbV+z ntcWgUyRh/a8EHAx3cW X-Received: by 2002:a17:90b:37cb:b0:35e:5ae3:299d with SMTP id 98e67ed59e1d1-36140411fd9mr27953032a91.11.1777002466983; Thu, 23 Apr 2026 20:47:46 -0700 (PDT) Received: from kernel-fuzz.. ([103.172.182.26]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-36140edb04dsm21832382a91.0.2026.04.23.20.47.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Apr 2026 20:47:46 -0700 (PDT) From: ZhengYuan Huang 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 Subject: [PATCH] fs/ntfs3: validate index entry key bounds Date: Fri, 24 Apr 2026 11:47:36 +0800 Message-ID: <20260424034736.1975025-1-gality369@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit [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 --- 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; }