From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pf1-f171.google.com (mail-pf1-f171.google.com [209.85.210.171]) (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 0C9B53E3DAD for ; Thu, 23 Apr 2026 09:41:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.171 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776937287; cv=none; b=nu5akVFJ/SoBfwC01GlyRvOYCC7+HeqLObcwcWaM+ReUCamjxSLwuUjGDddB7Dwhfsk03mMQvjch1AHLMx7zOQzbZ9n2rRXn7NcxrqRWDkvJ94nEP2LCNrSPYMIo7fyHU8Oga3GwBM9s+EjqiRrPWC0EEaG9hcOg0RYjI4Jn0zg= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776937287; c=relaxed/simple; bh=ihmAfo21IMLbCzr7j/S706oEA+WU7/yAbjbZECWwRKg=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=o/R8a7wx6Q8WyKU+XFiC1GXVtg48lMLXJQ9s+l5Y7vYphqmg7tlEJH5cydRAhbKt5kyERQ5cd+dL4449uj9vkgUvtQiHA25nzIx2Xq2ACXqnSpao/L6Fx70KRHqREWGY6hQQGpCjKTSDW+UPWm2akqVL64Qhc1UXHF8nI3jwYN0= 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=RB6PBxW5; arc=none smtp.client-ip=209.85.210.171 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="RB6PBxW5" Received: by mail-pf1-f171.google.com with SMTP id d2e1a72fcca58-82418b0178cso4021766b3a.1 for ; Thu, 23 Apr 2026 02:41:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776937285; x=1777542085; 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=MjXWV2juw1vRp8krlN6jQn7bDWv+2fW54+D1FPrGink=; b=RB6PBxW5PCdEi16wdIECp86g9+qGPCgVCtdYVGAV5RaAsrGfzJlx21Kjevj4eGAP2y siiWp0BmduY4a/84Dvq6At7T6eKRnjroUYmzis4x8ooKiSPS4Dsoo3Oi0khwT3kXd+3z BAMtQf7NLFqV4p/0HxoeBOARyy7h9eInajbUhTO+x5CjjCuwkOKb9qkf4JoEcBA2KkGj GwNniH5MqPtWRD5RPpfddjT7Fs3FxfeZiILlESy1zuOD8hLRMrQ08aRRHQVT8OXsTNID Ax73K22oC2Uf/PC1+6/buHakt+ZmoTPPRQ9+RyCif1A9VHURTpJkMyl6WGoQnj4DZBIt p0KA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776937285; x=1777542085; 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=MjXWV2juw1vRp8krlN6jQn7bDWv+2fW54+D1FPrGink=; b=QxppWxMrYwDp5GVg53+Coos9aR5mhnBngp/u1yIGXOvu6qTHb3EVdtOfeZOrRq/fK+ JoJc8GZa+LTzYnV0JBNp+xIjwTJwVXeo2X0GjnhMNQ+fjksscfjp4T9qInwIqJ8j+OKq eX7tDtg37TsWyRzyNfk/Pl8LhpKuD9+Q62Nvufa5WfkEEFJG5et6ukeY/zOZoAxYdBcT PfaElQqe+cgVKyA6iEwb4XytTqhazemCFIFmtXH1rtaDNzLw1ijCjF6TNYm0wEQMJhOK tJSdHccfn9LGCqcSI6r6n2b8gl4o2AqtRkBXXi1FZ0vf3PK5xgq6r/Z3yCka/ntksyHK ZqUg== X-Forwarded-Encrypted: i=1; AFNElJ+qyB8KyTDYkgeiy8v/DAiJgkoAh2AbOiNDq3JEvhTg2k2nc0CumG9V/JTwtNDECrjZdnlKdyMrHA34lGQ=@vger.kernel.org X-Gm-Message-State: AOJu0YxX61VXq9qqgFfnT/1VKxUWOvMQl58jId4vHT0gCmAppUul7eh6 zapuchEHCTpQB7YwUP9AWQnCeG/Wyy4qORUJHysQ3FDgZw6rzIyHsTU5 X-Gm-Gg: AeBDietDvuX2fvo6C7qJwTYuMV7daHVVNJnuMsEIFwrtkspPfl/No1QI0I8PJti92Ud SnI6tTZ06KUaMQ2nFbfJ9puT3MsNhfQU0/Bc2FCO/5laCNIt15P+yAWIN1eU5mCjpa6CAufMmEb vUBFIaGAAsUVn3f8Bqix6j1Uj+MiD1rlxQ6QzoVb/7YyE3LtzOrcz/Y0dTPWf+wLmYL32Q6VAty nkVfnY4w5PTz4+c4d7fztK791cEa4k4SEolHZK7okLYDBxgCkfrjOCBWMhWxgJpXCMgj816obUi +018uijm8mWuDlVYfQ0IFllY2yXX2X3Xhz7VDuxezOUQqfXVwcLwB5elqQEx838kfzue8c/uiDs PzAe8cxS2bj74bzMt6kFwTGGtlV6dXhN4Wn9JaKMRh3dBpmTg8aZqGu6I3cXFos1T3qeopI+ONZ GXGD6dX4UW4ahhmfLlY5NKAzlb14pYg/hO0+gGAXwc1ciXajC7PHBBjlm1RYgiP8sgrBGSOwGw0 uuSD0pvUGLDoXaeE4PJ X-Received: by 2002:a05:6a00:bb84:b0:82f:1d38:f693 with SMTP id d2e1a72fcca58-82f8c8e0f98mr27141046b3a.35.1776937285181; Thu, 23 Apr 2026 02:41:25 -0700 (PDT) Received: from kernel-fuzz.. ([103.172.182.26]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-82f8e9cbb1dsm21547319b3a.14.2026.04.23.02.41.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Apr 2026 02:41:24 -0700 (PDT) From: ZhengYuan Huang To: mark@fasheh.com, jlbec@evilplan.org, joseph.qi@linux.alibaba.com Cc: ocfs2-devel@lists.linux.dev, linux-kernel@vger.kernel.org, baijiaju1990@gmail.com, r33s3n6@gmail.com, zzzccc427@gmail.com, ZhengYuan Huang Subject: [PATCH v2] ocfs2: validate in-inode extent list on read Date: Thu, 23 Apr 2026 17:41:15 +0800 Message-ID: <20260423094116.876696-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 fuzzed OCFS2 image can flip the high byte of di->id2.i_list.l_next_free_rec. Later openat("./file0", O_WRONLY|O_CREAT|O_TRUNC, ...) reaches ocfs2_commit_truncate() and indexes past the in-inode extent array. KASAN: use-after-free in ocfs2_commit_truncate+0x16ea/0x1aa0 fs/ocfs2/alloc.c:7310 Read of size 1 at addr ffff888023e900c7 by task syz.0.11/360 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 ocfs2_commit_truncate+0x16ea/0x1aa0 fs/ocfs2/alloc.c:7310 ocfs2_truncate_file+0xcdd/0x13c0 fs/ocfs2/file.c:509 ocfs2_setattr+0xa6d/0x1fd0 fs/ocfs2/file.c:1212 notify_change+0x4b5/0x1030 fs/attr.c:546 do_truncate+0x1d2/0x230 fs/open.c:68 handle_truncate fs/namei.c:3596 [inline] do_open fs/namei.c:3979 [inline] path_openat+0x260f/0x2ce0 fs/namei.c:4134 do_filp_open+0x1f6/0x430 fs/namei.c:4161 do_sys_openat2+0x117/0x1c0 fs/open.c:1437 do_sys_open fs/open.c:1452 [inline] __do_sys_openat fs/open.c:1468 [inline] __se_sys_openat fs/open.c:1463 [inline] __x64_sys_openat+0x15b/0x220 fs/open.c:1463 ... [CAUSE] ocfs2_validate_inode_block() checks dinode header fields, but it does not validate the in-inode extent list layout for regular extent-bearing dinodes. A corrupted l_next_free_rec can therefore exceed the fixed record count. Truncate later uses l_next_free_rec - 1 as the last record index and reads rec->e_flags from beyond el->l_recs[]. [FIX] Validate the in-inode extent list when the dinode is read. Reject lists with an inconsistent l_count, an out-of-range l_next_free_rec, or malformed used records before they reach truncate and the other extent consumers that assume a valid in-inode extent list. Signed-off-by: ZhengYuan Huang --- v2: - Rewrite the [BUG] and [CAUSE] sections around corrupted l_next_free_rec driving the truncate UAF - Keep the code change unchanged because the existing read-side validation already blocks this variant --- fs/ocfs2/inode.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index a510a0eb1adc..7fcd1ee4fd46 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -215,6 +215,61 @@ static int ocfs2_dinode_has_extents(struct ocfs2_dinode *di) return 1; } +static int ocfs2_validate_inode_extent_list(struct super_block *sb, + struct buffer_head *bh, + struct ocfs2_dinode *di) +{ + struct ocfs2_extent_list *el = &di->id2.i_list; + u16 count = le16_to_cpu(el->l_count); + u16 next_free = le16_to_cpu(el->l_next_free_rec); + u16 expected = ocfs2_extent_recs_per_inode_with_xattr(sb, di); + u32 prev_end = 0; + bool have_prev = false; + int i; + + if (count != expected) + return ocfs2_error(sb, + "Invalid dinode #%llu: extent list count %u (expected %u)\n", + (unsigned long long)bh->b_blocknr, count, + expected); + + if (next_free > count) + return ocfs2_error(sb, + "Invalid dinode #%llu: extent list index %u (count %u)\n", + (unsigned long long)bh->b_blocknr, next_free, + count); + + for (i = 0; i < next_free; i++) { + struct ocfs2_extent_rec *rec = &el->l_recs[i]; + u32 rec_cpos = le32_to_cpu(rec->e_cpos); + u32 rec_clusters = ocfs2_rec_clusters(el, rec); + u32 rec_end = rec_cpos + rec_clusters; + + if (!rec_clusters) { + if (!el->l_tree_depth && i == 0) + continue; + + return ocfs2_error(sb, + "Invalid dinode #%llu: empty extent record %d at depth %u\n", + (unsigned long long)bh->b_blocknr, + i, le16_to_cpu(el->l_tree_depth)); + } + + if (have_prev && + ((el->l_tree_depth && rec_cpos != prev_end) || + (!el->l_tree_depth && rec_cpos < prev_end))) + return ocfs2_error(sb, + "Invalid dinode #%llu: extent record %d starts at %u after previous end %u\n", + (unsigned long long)bh->b_blocknr, i, + rec_cpos, prev_end); + + prev_end = rec_end; + have_prev = true; + } + + return 0; +} + /* * here's how inodes get read from disk: * iget5_locked -> find_actor -> OCFS2_FIND_ACTOR @@ -1552,6 +1607,12 @@ int ocfs2_validate_inode_block(struct super_block *sb, } } + if (ocfs2_dinode_has_extents(di)) { + rc = ocfs2_validate_inode_extent_list(sb, bh, di); + if (rc) + goto bail; + } + if ((le16_to_cpu(di->i_dyn_features) & OCFS2_HAS_REFCOUNT_FL) && !di->i_refcount_loc) { rc = ocfs2_error(sb, "Inode #%llu has refcount flag but no i_refcount_loc\n", @@ -1812,4 +1873,3 @@ const struct ocfs2_caching_operations ocfs2_inode_caching_ops = { .co_io_lock = ocfs2_inode_cache_io_lock, .co_io_unlock = ocfs2_inode_cache_io_unlock, }; - -- 2.43.0