From: sashiko-bot@kernel.org
To: "Artem Blagodarenko" <artem.blagodarenko@gmail.com>,
linux-ext4@vger.kernel.org
Cc: tytso@mit.edu
Subject: Re: [PATCH v13 07/11] ext4: rename ext4_dir_rec_len() and clarify dirdata usage
Date: Thu, 29 Oct 2026 12:50:01 +0000 [thread overview]
Message-ID: <sashiko-outbox-189315@kernel.org> (raw)
In-Reply-To: <20261029122413.230409-8-ablagodarenko@thelustrecollective.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] ext4: mutable ext4_dir_entry_len() causes OOB write in ext4_insert_dentry()
--
commit da71cae2acaae8de2bca9ec5b4ce285d12415d14
Author: Artem Blagodarenko <artem.blagodarenko@gmail.com>
ext4: rename ext4_dir_rec_len() and clarify dirdata usage
This commit renames ext4_dir_rec_len() to ext4_dirent_rec_len(), widens its
name_len parameter from __u8 to unsigned int, and updates several callers to
use ext4_dir_entry_len() or pass an explicit dirdata length to
ext4_find_dest_de().
> diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
> index 8d7b81e6948e7..38911ae2bcfc0 100644
> --- a/fs/ext4/dir.c
> +++ b/fs/ext4/dir.c
> @@ -89,16 +89,15 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
> bool fake = is_fake_dir_entry(de);
> bool has_csum = ext4_has_feature_metadata_csum(dir->i_sb);
>
> - if (unlikely(rlen < ext4_dir_rec_len(1, fake ? NULL : dir)))
> + if (unlikely(rlen < ext4_dirent_rec_len(1, fake ? NULL : dir)))
> error_msg = "rec_len is smaller than minimal";
> else if (unlikely(rlen % 4 != 0))
> error_msg = "rec_len % 4 != 0";
> - else if (unlikely(rlen < ext4_dir_rec_len(de->name_len,
> - fake ? NULL : dir)))
> - error_msg = "rec_len is too small for name_len";
> else if (unlikely(next_offset > size))
> error_msg = "directory entry overrun";
> - else if (unlikely(next_offset > size - ext4_dir_rec_len(1,
> + else if (unlikely(rlen < ext4_dir_entry_len(de, size, fake ? NULL : dir)))
> + error_msg = "rec_len is too small for name_len";
> + else if (unlikely(next_offset > size - ext4_dirent_rec_len(1,
> has_csum ? NULL : dir) &&
> next_offset != size))
> error_msg = "directory entry too close to block end";
[ ... ]
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 2d6d3be4ef7df..839274c4d7fcd 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -2649,11 +2649,16 @@ struct ext4_dirent_hash {
> * casefolded and encrypted need to store the hash as well, so we add room for
> * ext4_extended_dir_entry_2. For all entries related to '.' or '..' you should
> * pass NULL for dir, as those entries do not use the extra fields.
> + *
> + * For directories with the dirdata feature, extra data may follow the filename.
> + * Use ext4_dir_entry_len() to compute the length of a directory entry
> + * including any dirdata, or ext4_dirent_rec_len() directly when the total
> + * name_len (including dirdata length) is already known.
> */
> -static inline unsigned int ext4_dir_rec_len(__u8 name_len,
> +static inline unsigned int ext4_dirent_rec_len(unsigned int name_len,
> const struct inode *dir)
> {
> - int rec_len = (name_len + 8 + EXT4_DIR_ROUND);
> + unsigned int rec_len = (name_len + 8 + EXT4_DIR_ROUND);
>
> /*
> * Without dirdata, the casefold+fscrypt hash lives at a fixed position
[ ... ]
> @@ -4266,7 +4272,7 @@ static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de,
> unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len, blocksize);
> unsigned int dirdata = ext4_dirent_get_data_len(de, rec_len);
>
> - return ext4_dir_rec_len(de->name_len + dirdata, dir);
> + return ext4_dirent_rec_len(de->name_len + dirdata, dir);
> }
[ ... ]
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index b3acab0689c93..e54608950fb34 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
[ ... ]
> @@ -1902,7 +1902,7 @@ dx_move_dirents(struct inode *dir, char *from, char *to,
> while (count--) {
> struct ext4_dir_entry_2 *de = (struct ext4_dir_entry_2 *)
> (from + (map->offs<<2));
> - rec_len = ext4_dir_rec_len(de->name_len, dir);
> + rec_len = ext4_dir_entry_len(de, blocksize, dir);
>
> memcpy (to, de, rec_len);
> ((struct ext4_dir_entry_2 *) to)->rec_len =
> @@ -1935,7 +1935,7 @@ static struct ext4_dir_entry_2 *dx_pack_dirents(struct inode *dir, char *base,
> while ((char*)de < base + blocksize) {
> next = ext4_next_entry(de, blocksize);
> if (de->inode && de->name_len) {
> - rec_len = ext4_dir_rec_len(de->name_len, dir);
> + rec_len = ext4_dir_entry_len(de, blocksize, dir);
> if (de > to)
> memmove(to, de, rec_len);
> to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
[ ... ]
> @@ -2126,7 +2127,7 @@ void ext4_insert_dentry(struct inode *dir,
>
> int nlen, rlen;
>
> - nlen = ext4_dir_rec_len(de->name_len, dir);
> + nlen = ext4_dir_entry_len(de, buf_size, dir);
> rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
> if (de->inode) {
> struct ext4_dir_entry_2 *de1 =
[Severity: Critical]
Can switching to ext4_dir_entry_len() here and widening ext4_dirent_rec_len()
cause an out-of-bounds write past bh->b_data or trigger the BUG_ON() in
ext4_rec_len_to_disk() when splitting a directory block via
do_split() -> add_dirent_to_buf() -> ext4_insert_dentry()?
In fs/ext4/ext4.h, ext4_dir_entry_len() computes the entry length using the
entry's current de->rec_len via ext4_dirent_get_data_len():
fs/ext4/ext4.h:ext4_dirent_get_data_len() {
...
{
u8 ddh_len = READ_ONCE(ddh->ddh_length);
if (ddh_len == 0 || ddh_len > rec_len - offset)
return 0;
dlen += ddh_len + (dlen == 0);
...
}
Because ext4_dirent_get_data_len() returns 0 whenever ddh_len exceeds
rec_len - offset (and does not check ext4_has_feature_dirdata()),
__ext4_check_dir_entry() in fs/ext4/dir.c will not reject an entry whose
on-disk rec_len is smaller than its dirdata length.
During a directory leaf split in fs/ext4/namei.c:do_split():
fs/ext4/namei.c:do_split() {
...
de2 = dx_move_dirents(dir, data1, data2, map + split, count - split,
blocksize);
de = dx_pack_dirents(dir, data1, blocksize);
de->rec_len = ext4_rec_len_to_disk(data1 + (blocksize - csum_size) -
(char *) de,
blocksize);
de2->rec_len = ext4_rec_len_to_disk(data2 + (blocksize - csum_size) -
(char *) de2,
blocksize);
...
}
If the final entry de in the target split half has a small rec_len (for
example, rec_len = 12, name_len = 1, file_type = 0x11) with a larger
ddh_length byte stored at offset 10 (still inside the 12-byte entry),
ext4_dirent_get_data_len(de, 12) returns 0 during __ext4_check_dir_entry(),
dx_move_dirents(), and dx_pack_dirents(), copying all 12 bytes.
Then do_split() expands de->rec_len to cover the remainder of the block and
passes de to add_dirent_to_buf(), which skips ext4_find_dest_de() when de is
non-NULL and calls ext4_insert_dentry() directly:
fs/ext4/namei.c:ext4_insert_dentry() {
int nlen, rlen;
nlen = ext4_dir_entry_len(de, buf_size, dir);
rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
if (de->inode) {
struct ext4_dir_entry_2 *de1 =
(struct ext4_dir_entry_2 *)((char *)de + nlen);
de1->rec_len = ext4_rec_len_to_disk(rlen - nlen, buf_size);
de->rec_len = ext4_rec_len_to_disk(nlen, buf_size);
de = de1;
}
...
memcpy(de->name, fname_name(fname), fname_len(fname));
}
When ext4_insert_dentry() recomputes nlen = ext4_dir_entry_len(de, buf_size,
dir) with the expanded de->rec_len, ddh_len <= rec_len - offset can now
succeed, growing nlen up to rlen (or rlen + 8 if ext4_hash_in_dirent(dir) &&
!ext4_has_feature_dirdata(dir->i_sb)). In addition, because
ext4_dirent_rec_len() now takes unsigned int name_len, an entry with multiple
valid dirdata fields can be up to 1284 bytes, which on 1024-byte or 2048-byte
block filesystems can also leave rlen - nlen in do_split() smaller than the
new entry being inserted.
Since ext4_insert_dentry() does not check that rlen - nlen is large enough to
hold the new entry, wouldn't nlen > rlen underflow rlen - nlen and hit BUG_ON
in ext4_rec_len_to_disk(), or nlen <= rlen advance de1 to the end of the
block buffer so that memcpy(de->name, ...) writes past the end of bh->b_data?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029122413.230409-1-ablagodarenko@thelustrecollective.com?part=7
next prev parent reply other threads:[~2026-10-29 12:50 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 12:23 [PATCH v13 00/11] Data in direntry (dirdata) feature Artem Blagodarenko
2026-10-29 12:24 ` [PATCH v13 01/11] ext4: validate count against limit in ext4_dx_csum_verify/_set Artem Blagodarenko
2026-10-29 12:37 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 02/11] ext4: replace ext4_dir_entry with ext4_dir_entry_2 Artem Blagodarenko
2026-10-29 12:34 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 03/11] ext4: add ext4_dir_entry_is_tail() Artem Blagodarenko
2026-10-29 12:31 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 04/11] ext4: add dirdata format definitions and access helpers Artem Blagodarenko
2026-10-29 12:31 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 05/11] ext4: refactor dx_root to support variable dirent sizes Artem Blagodarenko
2026-10-29 12:49 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 06/11] ext4: add ext4_dir_entry_len() Artem Blagodarenko
2026-10-29 12:50 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 07/11] ext4: rename ext4_dir_rec_len() and clarify dirdata usage Artem Blagodarenko
2026-10-29 12:50 ` sashiko-bot [this message]
2026-10-29 12:24 ` [PATCH v13 08/11] ext4: dirdata feature Artem Blagodarenko
2026-10-29 12:51 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 09/11] ext4: add dirdata LUFID support for directory entry rename Artem Blagodarenko
2026-10-29 12:47 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 10/11] ext4: add dirdata set/get helpers Artem Blagodarenko
2026-10-29 12:44 ` sashiko-bot
2026-10-29 12:24 ` [PATCH v13 11/11] ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory entries Artem Blagodarenko
2026-10-29 12:58 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-189315@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=artem.blagodarenko@gmail.com \
--cc=linux-ext4@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tytso@mit.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox