From nobody Mon Apr 6 19:57:25 2026 Received: from mail-pl1-f180.google.com (mail-pl1-f180.google.com [209.85.214.180]) (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 BF4FA368972 for ; Wed, 18 Mar 2026 07:58:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773820735; cv=none; b=BWIYHI/W31yGbEWxoJGD25thT0PogcmIZSgA0JJLdE2hgeV0Sa1BUu8sLk0h7uCvWGe9r5kf3WN6+G5xT7FAwJ8SeprjwEZttaWFxuQiscK7DUtZX2z9tsp49Lca0fsaif2dKoQzMJbmg+fGBc/ql+c1CzICB7vn3Qyi+csDrpg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773820735; c=relaxed/simple; bh=+dxtWE1DwIgIzEHxMwSi9pwNrnQxZQGBzo8VT4ntRIA=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=usouQcPqPeFZBCumrGFemeOeA5O21Qz96UB44QVy4g3vG8J19ul1y96ONIZlRm9NsEp0ooAs7O4kG+p7lQ9NNZ+odsoifujB+PIM44sBrVDmvc6ELuqtErob8PDeMGjcp0ZPtO4TJulXcDD0LXxBjSxHV+UwNtRawPdqt3KvzPo= 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=CoM/z0qM; arc=none smtp.client-ip=209.85.214.180 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="CoM/z0qM" Received: by mail-pl1-f180.google.com with SMTP id d9443c01a7336-2a7a9b8ed69so80045075ad.2 for ; Wed, 18 Mar 2026 00:58:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773820734; x=1774425534; 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=HhVqm7dtmhIq5iMPEQ+RzAXpBXYN81Z6Q4RNXVVrMG8=; b=CoM/z0qMtFbq4/X7gHeRUYnGPf7z71khDevB8UwzCbpapQS3MkEBad5FtT2zX3uG9Y zQglQo5lHySGcmEUHSPYt/eLt0CuaCqPxb1yQAF1FAWCespFDwgpUaZicXTd7Snwdc2G a+CcR6/sozaWws4XASUidDNUFEMgdOf2j7gZjP6Fnd09OV72qoBotPz9lrjiHJ5MxHFD 0vIgIAyaarNBNVbVUKy7N37CmlIbrAfMGgQTthKG/g+SXqt86RxqsZzzdKO5ARQND5Iv EBLcVS+MxFyTqJJ88k3T+b/AKmiHgNc8d6Ov32EAzkIggfGS6tDgdR3zzYNKEyEbXbTi pJ6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773820734; x=1774425534; 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=HhVqm7dtmhIq5iMPEQ+RzAXpBXYN81Z6Q4RNXVVrMG8=; b=Z8x84B11w7FZ/YzZGJTg5IjrmWURrf2okbJeIoN2d99F64R7Y91mo3A8cBIgT7O8xN p/6xD0aJIIss038ytrbjiw/X9FhEpex1akYSPcxsCCN6c5VOYtP8fccqHVG9+J/5W1xd 5CZDw2zmlBYGcf0JFAIYG8sc11B0ydlSdWpBsn0YeoEPFH0g45C6hLmg/2j3Dd18OHAq PE6X/b/EwbFlCQTQilE7VNUehqblPq/m4xdQui9xRHsco6hq00yAFVPVt273k9Rxna0H AU6vYWM2bou9jBxgFaYWGyOnnc+TNgvA95epnMkNQDHuHI0QFWxTfdtIEDkrZNb8S7nt aCCA== X-Forwarded-Encrypted: i=1; AJvYcCUbtfavf3nnb/1vojtmZaRV+alRGEjg5TgrtThKQVBFZeVHXidJmgHkPpo6LmRbdHfK8yj9ROXwWRhi53E=@vger.kernel.org X-Gm-Message-State: AOJu0YzKk2T8L7rK1e9DYLY0mQG2pVjxWFtg/5VFwkH+DELoHefxIGpw xkTWmnIAa99pVezudE9AOnOntKFgLlWAkCOdvlV9CjDP9LidE96i3vML X-Gm-Gg: ATEYQzyesGIdtH8t/FF1Y8s5QkpC1K5Bf9kYDmR8R83vQ8Swg1J5TuVVmpg5VgiaIZ3 apycGG1md+eIPRuvpLrFgUrgR81YfeWzg64b9VY+6beB7eDQtEOXz3/wfXpPzKa7WuNhoKdPAII KTWDgjjoTPnSWP6yOiFIi80sPYT017eUBkhHWAo9+bUiA1Wx5S0zUGaVKXRtn6rKNiPgcAYRcuO YeHkTXkmovKB+3As+DkolonNWVG6D69ZgQm1BTxh/hjN6gJ9I7Kd9+e1VaCj6tiE7OtWvoBPZJk NB3qMlV1p0/OP9GSCT0nlI7OvfAt2sDoVkdyuGpvViUzpGr8nYQSB34L8k9KMV87NXk3+1Du5SA um3xBb5XouIFvk2fxcmtu5mp5AKPHqmMu7P5j+f8rGz5HHOcSl3QeglkyWywo0qgOsmuQgPVDS9 jd/t1fTtbiPsP8SORHMvQENIPvChldt8+9V4HnKHsIl8Tz+2clDrAFYmwcgb7TqyWi89qI/DM= X-Received: by 2002:a17:902:ce11:b0:2ad:d5d7:bad2 with SMTP id d9443c01a7336-2b06e43709fmr25696775ad.48.1773820733922; Wed, 18 Mar 2026 00:58:53 -0700 (PDT) Received: from kernel-fuzz.. ([103.172.182.26]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b06e41aad6sm17610545ad.16.2026.03.18.00.58.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 18 Mar 2026 00:58:53 -0700 (PDT) From: ZhengYuan Huang To: tytso@mit.edu, adilger.kernel@dilger.ca, tahsin@google.com Cc: linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org, baijiaju1990@gmail.com, r33s3n6@gmail.com, zzzccc427@gmail.com, ZhengYuan Huang , stable@vger.kernel.org Subject: [PATCH] ext4: xattr: fix out-of-bounds access in ext4_xattr_set_entry Date: Wed, 18 Mar 2026 15:58:42 +0800 Message-ID: <20260318075842.3341370-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: quoted-printable Content-Type: text/plain; charset="utf-8" [BUG] KASAN reports show out-of-bounds and use-after-free memory accesses when ext4_xattr_set_entry() processes corrupted on-disk xattr entries: BUG: KASAN: slab-out-of-bounds in ext4_xattr_set_entry+0xfc2/0x1f40 fs/ex= t4/xattr.c:1735 Write of size 12 at addr ffff88801a249af4 [slab OOB] Call Trace: ... ext4_xattr_set_entry+0xfc2/0x1f40 fs/ext4/xattr.c:1735 ext4_xattr_ibody_set+0x396/0x5a0 fs/ext4/xattr.c:2268 ext4_destroy_inline_data_nolock+0x25e/0x560 fs/ext4/inline.c:463 ext4_convert_inline_data_nolock+0x186/0xa80 fs/ext4/inline.c:1105 ext4_try_add_inline_entry+0x58e/0x960 fs/ext4/inline.c:1224 ext4_add_entry+0x6d2/0xce0 fs/ext4/namei.c:2389 ext4_rename+0x133c/0x2490 fs/ext4/namei.c:3929 ext4_rename2+0x1de/0x2c0 fs/ext4/namei.c:4208 vfs_rename+0xd42/0x1d50 fs/namei.c:5216 do_renameat2+0x715/0xb60 fs/namei.c:5364 ... BUG: KASAN: use-after-free in ext4_xattr_set_entry+0xfd3/0x1f40 fs/ext4/x= attr.c:1736 Write of size 65796 at addr ffff88802feb6ee8 [UAF across page boundary] [CAUSE] During inode load, xattr_check_inode() validates the ibody xattr entries found in the inode at that time, and ext4_read_inode_extra() sets EXT4_STATE_XATTR after the validation succeeds. Later, when updating an ibody xattr, ext4_xattr_ibody_find() does not rely on those already validated contents. It calls ext4_get_inode_loc() and reads the inode table block again, so the entry eventually passed to ext4_xattr_set_entry() comes from a new on-disk read. xattr_find_entry() may return that entry based on its name, but does not revalidate its e_value_offs and e_value_size before they are dereferenced. Therefore, if the inode table block is modified between inode load and the later xattr update, the code ends up validating one version of the xattr data and using another. ext4_xattr_set_entry() may then consume corrupted e_value_offs/e_value_size fields from the newly read entry, which can cause out-of-bounds accesses, size_t underflow, and use-after-free. [FIX] Fix this by validating the target entry's value offset and size in ext4_xattr_set_entry() before using them. Reject invalid entries with -EFSCORRUPTED, consistent with the checks already enforced by check_xattrs() for ibody xattrs. Fixes: dec214d00e0d7 ("ext4: xattr inode deduplication") Cc: stable@vger.kernel.org Signed-off-by: ZhengYuan Huang --- fs/ext4/xattr.c | 58 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index ce7253b3f549..3ebfe2dfcae9 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -1638,6 +1638,48 @@ static int ext4_xattr_set_entry(struct ext4_xattr_in= fo *i, EXT4_XATTR_SIZE(le32_to_cpu(here->e_value_size)) : 0; new_size =3D (i->value && !in_inode) ? EXT4_XATTR_SIZE(i->value_len) : 0; =20 + /* Compute min_offs and last. */ + last =3D s->first; + for (; !IS_LAST_ENTRY(last); last =3D next) { + next =3D EXT4_XATTR_NEXT(last); + if ((void *)next >=3D s->end) { + EXT4_ERROR_INODE(inode, "corrupted xattr entries"); + ret =3D -EFSCORRUPTED; + goto out; + } + if (!last->e_value_inum && last->e_value_size) { + size_t offs =3D le16_to_cpu(last->e_value_offs); + + if (offs < min_offs) + min_offs =3D offs; + } + } + + /* + * Validate the value range before dereferencing e_value_offs / e_value_s= ize. + * This mirrors check_xattrs() for the entry we are about to touch. + */ + if (!s->not_found && !here->e_value_inum && here->e_value_size) { + u16 offs =3D le16_to_cpu(here->e_value_offs); + size_t size =3D le32_to_cpu(here->e_value_size); + void *value; + + if (offs > s->end - s->base) { + EXT4_ERROR_INODE(inode, "corrupted xattr entry: invalid value offset"); + ret =3D -EFSCORRUPTED; + goto out; + } + + value =3D s->base + offs; + if (value < (void *)last + sizeof(__u32) || + size > s->end - value || + EXT4_XATTR_SIZE(size) > s->end - value) { + EXT4_ERROR_INODE(inode, "corrupted xattr entry: invalid value range"); + ret =3D -EFSCORRUPTED; + goto out; + } + } + /* * Optimization for the simple case when old and new values have the * same padded sizes. Not applicable if external inodes are involved. @@ -1657,22 +1699,6 @@ static int ext4_xattr_set_entry(struct ext4_xattr_in= fo *i, goto update_hash; } =20 - /* Compute min_offs and last. */ - last =3D s->first; - for (; !IS_LAST_ENTRY(last); last =3D next) { - next =3D EXT4_XATTR_NEXT(last); - if ((void *)next >=3D s->end) { - EXT4_ERROR_INODE(inode, "corrupted xattr entries"); - ret =3D -EFSCORRUPTED; - goto out; - } - if (!last->e_value_inum && last->e_value_size) { - size_t offs =3D le16_to_cpu(last->e_value_offs); - if (offs < min_offs) - min_offs =3D offs; - } - } - /* Check whether we have enough space. */ if (i->value) { size_t free; --=20 2.43.0