From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.181]) (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 21CB63B1013 for ; Mon, 22 Jun 2026 13:03:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782133428; cv=none; b=DwOQF8nr1X886WQpNwttIRZH4sHfB3bsTHJ2/+dxu7PZBCbokGchV13PJ0qom12fzZSKFoBYUasVUxxYvQgmPAqfQU4T7cEZW0PsvvGO9rZmHtL1G6xjifYoLe49e7btVelBDGdEywtDX/KWpcNz11YDm5t8YJ5ey5nmplZpzqs= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782133428; c=relaxed/simple; bh=bVon15wOa2aL9wqkydJrWMTomive7VVBvU7Bz6UdY4Q=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=dm9G8mMOuTuBJ+4w9XInZZilX9mGG+l0y/GMM8xMsiGDQQNL0eGaVde7nzF09CmchiY47ZPnXjscRkfMKRYVt/xODXRR7cJrxUTamIgsJBuDE1iIo6RL6saJS47I/fBakXcT5LYWmbrQr2V3lMUnlMYmDv1aStqVuJWsD6anNEo= 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=lgDcRHwk; arc=none smtp.client-ip=209.85.214.181 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="lgDcRHwk" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-2bf22d29dabso26459385ad.2 for ; Mon, 22 Jun 2026 06:03:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782133421; x=1782738221; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=hzDUeVtJRnFTQjoY9bBajtviJo63PoGZcW0Wq1nkiSo=; b=lgDcRHwk1jHcaT74FVI/g0vexgScvEf/9L3kx278fl13VAOpASgWS9WrKQOcjlSIxI MX80fAyK3LD8iqdBn3Cx5O5cvtz51P24Xf1PgRRhHqNc4Mt34b2eJOcio2wAB5oDpd2m Chi5M/8utUuMNA/Wnij+SKm0axATAvaGfo97XKw+hSNcr6jX5MZhEvRjdW8XwuY9ovWI BJS0g1wgJ8VQccUucXUfSZX24tzerFLfdsuqldU/hpgn0AgQtigq86M+sk9M9jlmKa9a z8GFjJHh94eYTg1EO3vLoWfgldr+9Byzj611o0uqqsVx/W91oisxLD92wCG8ulQApy/0 vNNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782133421; x=1782738221; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=hzDUeVtJRnFTQjoY9bBajtviJo63PoGZcW0Wq1nkiSo=; b=RTxiNMifThfkYtxOA/ExOiN4MXijJKaCssB0qfga2Ps+kRpn8GgTQjgFMSAFSlOFK3 8D/WLZk14XwviPtWJ0b9mpL35UeNpH1gN8kKuBe6kEHN37DAfSQTJu0O6/TCBCFPwRAW xRewX3UhuG809sv31MMsP3C4V0d+jGwfOWdEtheyjRQ/V3wYYq9aW+vbAWnQDIDEYQx+ BEXpFeaSLNxL4aD0HiiZDsnrz7Mm5tTOOINYhSELKoSq9oFWm/biVgnKD76QUlABAXO4 IF4lHDRMXWHbBG3Tf4rYBB2CarePwNDIuKAAfu8zzHzdX9UacbOFi+6HxuRskusNsqDD sFbw== X-Forwarded-Encrypted: i=1; AHgh+Rp/0ddAOmh0XeIZq8NhtehrTt3CTT0TExFMdTFK0NNhp+zLNS8bgg5Y4iYIIjSxmc/pUblJpomRpvLLVN0=@vger.kernel.org X-Gm-Message-State: AOJu0YyWaAt/TSah3DHWV6aj+lnbf9BuCTvfEWmV9i9r6+3io/qMxXR+ P0YGW5KVonytaVNtGxmgcCjwgFI9Vqs9mIMVdSiTO9uqKW5+4RLy8ZgCSo84+Ue2vbA= X-Gm-Gg: AfdE7cnU2SWTGPUnju1yDALD53qDXOJbujlKeHvtDWIF52s08CC7Ob1sB3beM5UNZmu 140BKsMwS0xPPqpvHvU00FEV1Yj1nRpTpgu7awiauRvvMV2KAIS8e9K0ALQ/3bmxmLMOubbZj1M BI5vfqnjfIkF99kHVcu04K1L/7lPamNEUDtni837dJMHlpQ873JPW71+wvY47J63gFC8Us6Hc8H Li/KibzOT8uoQaQbutaCH3mw7dM9yX5y/uO6OMU3TOF82OTggCO/bwr+nJgo86Vst89Cx/GvfAu oEk2ZYMz0/n/9abcN9xopYAZnYdX+91+0sTNSAfBNXeye12/OcQToc1r9uRsHnA0kTW7G9jXvUL 5btS6MDm68tqXRXAvPR4a35FThlcqCsxfrNYOKuS7PksqU5p2wA646ih89sk26gqR2E9pvPkzdv HGn1/YAso= X-Received: by 2002:a17:902:f688:b0:2c6:6926:8968 with SMTP id d9443c01a7336-2c742b1c878mr98218485ad.20.1782133420911; Mon, 22 Jun 2026 06:03:40 -0700 (PDT) Received: from localhost ([111.228.63.84]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2c743fd6420sm78895885ad.70.2026.06.22.06.03.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 22 Jun 2026 06:03:40 -0700 (PDT) From: Cen Zhang To: Joseph Qi , Mark Fasheh , Joel Becker Cc: ocfs2-devel@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v4 1/2] ocfs2: validate inline xattrs during inode block validation Date: Mon, 22 Jun 2026 21:03:31 +0800 Message-Id: <20260622130332.2094018-2-zzzccc427@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260622130332.2094018-1-zzzccc427@gmail.com> References: <20260622130332.2094018-1-zzzccc427@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit ocfs2_validate_inode_block() verifies a dinode before OCFS2 users walk metadata from it, but inline xattr metadata is still checked only in some consumers. The current ibody helper validates the inline header placement and entry count before deriving the header from i_xattr_inline_size, but it does not reject entry name/value bounds from inode block validation. Extend the shared ibody helper with name/value bounds checks and call it from ocfs2_validate_inode_block(). Keep the get/list paths using the same helper before they derive pointers from i_xattr_inline_size so callers with an already-loaded dinode still get the same corruption check. Reject corrupted inline xattr metadata before ocfs2_xattr_ibody_get() or listxattr() can walk past the inline storage. Validation reproduced this kernel report: BUG: KASAN: use-after-free in ocfs2_xattr_find_entry+0x5a/0x170 Read of size 2 at addr ffff8881242a2000 by task python3/529 Call Trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 kasan_report+0xe0/0x110 ocfs2_xattr_find_entry+0x5a/0x170 ocfs2_xattr_get_nolock+0x20a/0x820 ocfs2_xattr_get+0x10c/0x1e0 __vfs_getxattr+0xe2/0x130 vfs_getxattr+0x185/0x1b0 Fixes: cf1d6c763fbc ("ocfs2: Add extended attribute support") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang --- fs/ocfs2/inode.c | 4 ++ fs/ocfs2/xattr.c | 112 +++++++++++++++++++++++++++++++++++++---------- fs/ocfs2/xattr.h | 2 + 3 files changed, 95 insertions(+), 23 deletions(-) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 662dbc845b8b..815bf3f659da 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -1608,6 +1608,10 @@ int ocfs2_validate_inode_block(struct super_block *sb, goto bail; } + rc = ocfs2_validate_inode_xattr(sb, bh->b_blocknr, di); + if (rc) + goto bail; + if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) { struct ocfs2_inline_data *data = &di->id2.i_data; diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index fcddd3c13acd..00900e65634d 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -950,39 +950,105 @@ static int ocfs2_xattr_list_entries(struct inode *inode, return result; } -static int ocfs2_xattr_ibody_lookup_header(struct inode *inode, - struct ocfs2_dinode *di, - struct ocfs2_xattr_header **header) +static int ocfs2_validate_xattr_entries(struct super_block *sb, u64 blkno, + struct ocfs2_xattr_header *xh, + size_t storage_size, + const char *where) { - u16 xattr_count; + u16 xattr_count = le16_to_cpu(xh->xh_count); size_t max_entries; + int i; + + if (storage_size < sizeof(*xh)) + return ocfs2_error(sb, + "Invalid %s in block %llu: storage size %zu is too small\n", + where, (unsigned long long)blkno, + storage_size); + + max_entries = (storage_size - sizeof(*xh)) / + sizeof(struct ocfs2_xattr_entry); + if (xattr_count > max_entries) { + return ocfs2_error(sb, + "Invalid %s in block %llu: entry count %u exceeds maximum %zu\n", + where, (unsigned long long)blkno, + xattr_count, max_entries); + } + + for (i = 0; i < xattr_count; i++) { + struct ocfs2_xattr_entry *xe = &xh->xh_entries[i]; + size_t name_offset = le16_to_cpu(xe->xe_name_offset); + size_t value_offset; + + if (name_offset > storage_size || + xe->xe_name_len > storage_size - name_offset) + return ocfs2_error(sb, + "Invalid %s in block %llu: entry %d name is out of bounds\n", + where, (unsigned long long)blkno, i); + + value_offset = name_offset + OCFS2_XATTR_SIZE(xe->xe_name_len); + if (value_offset > storage_size) + return ocfs2_error(sb, + "Invalid %s in block %llu: entry %d value starts out of bounds\n", + where, (unsigned long long)blkno, i); + + if (ocfs2_xattr_is_local(xe)) { + if (le64_to_cpu(xe->xe_value_size) > + storage_size - value_offset) + return ocfs2_error(sb, + "Invalid %s in block %llu: entry %d value is out of bounds\n", + where, + (unsigned long long)blkno, + i); + } else if (sizeof(struct ocfs2_xattr_value_root) > + storage_size - value_offset) { + return ocfs2_error(sb, + "Invalid %s in block %llu: entry %d value root is out of bounds\n", + where, (unsigned long long)blkno, i); + } + } + + return 0; +} + +static int ocfs2_validate_xattr_ibody_header(struct super_block *sb, u64 blkno, + struct ocfs2_dinode *di, + struct ocfs2_xattr_header **header) +{ + struct ocfs2_xattr_header *xh; u16 inline_size = le16_to_cpu(di->i_xattr_inline_size); - if (inline_size > inode->i_sb->s_blocksize || + if (inline_size > sb->s_blocksize || inline_size < sizeof(struct ocfs2_xattr_header)) { - ocfs2_error(inode->i_sb, - "Invalid xattr inline size %u in inode %llu\n", - inline_size, - (unsigned long long)OCFS2_I(inode)->ip_blkno); - return -EFSCORRUPTED; + return ocfs2_error(sb, + "Invalid inode %llu: xattr inline size %u\n", + (unsigned long long)blkno, inline_size); } - *header = (struct ocfs2_xattr_header *) - ((void *)di + inode->i_sb->s_blocksize - inline_size); + xh = (struct ocfs2_xattr_header *) + ((void *)di + sb->s_blocksize - inline_size); + if (header) + *header = xh; - xattr_count = le16_to_cpu((*header)->xh_count); - max_entries = (inline_size - sizeof(struct ocfs2_xattr_header)) / - sizeof(struct ocfs2_xattr_entry); + return ocfs2_validate_xattr_entries(sb, blkno, xh, inline_size, + "inline xattr"); +} - if (xattr_count > max_entries) { - ocfs2_error(inode->i_sb, - "xattr entry count %u exceeds maximum %zu in inode %llu\n", - xattr_count, max_entries, - (unsigned long long)OCFS2_I(inode)->ip_blkno); - return -EFSCORRUPTED; - } +int ocfs2_validate_inode_xattr(struct super_block *sb, u64 blkno, + struct ocfs2_dinode *di) +{ + if (!(le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_XATTR_FL)) + return 0; - return 0; + return ocfs2_validate_xattr_ibody_header(sb, blkno, di, NULL); +} + +static int ocfs2_xattr_ibody_lookup_header(struct inode *inode, + struct ocfs2_dinode *di, + struct ocfs2_xattr_header **header) +{ + return ocfs2_validate_xattr_ibody_header(inode->i_sb, + OCFS2_I(inode)->ip_blkno, + di, header); } int ocfs2_has_inline_xattr_value_outside(struct inode *inode, diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 65e9aa743919..6b7589941315 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -43,6 +43,8 @@ int ocfs2_xattr_set_handle(handle_t *, struct inode *, struct buffer_head *, struct ocfs2_alloc_context *); int ocfs2_has_inline_xattr_value_outside(struct inode *inode, struct ocfs2_dinode *di); +int ocfs2_validate_inode_xattr(struct super_block *sb, u64 blkno, + struct ocfs2_dinode *di); int ocfs2_xattr_remove(struct inode *, struct buffer_head *); int ocfs2_init_security_get(struct inode *, struct inode *, const struct qstr *, -- 2.43.0