From: sashiko-bot@kernel.org
To: "Artem Blagodarenko" <artem.blagodarenko@gmail.com>,
linux-ext4@vger.kernel.org
Cc: tytso@mit.edu
Subject: Re: [PATCH v14 08/12] ext4: rename ext4_dir_rec_len() and clarify dirdata usage
Date: Tue, 03 Nov 2026 14:20:52 +0000 [thread overview]
Message-ID: <sashiko-outbox-195439@kernel.org> (raw)
In-Reply-To: <20261103134921.610047-9-ablagodarenko@thelustrecollective.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] ext4: unsigned len underflow in make_indexed_dir() when dot_de has dirdata
- [High] ext4: __ext4_check_dir_entry() misses out-of-bounds dirdata before rec_len grows
- [High] ext4: add_dirent_to_buf() error return after do_split() leaves bh undirtied
--
commit 06e0b22cf991d67d7b351c0723ad5474e253ef41
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(), updates
directory entry length calculations to use ext4_dir_entry_len() so dirdata
is accounted for, and adds a room check in add_dirent_to_buf() for split
blocks.
> 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";
[Severity: High]
As reported by Sashiko on v13 [1], can __ext4_check_dir_entry() ever reject
an entry whose on-disk rec_len is too small for its dirdata here, or does a
later rec_len expansion cause stale buffer bytes to be annexed into the
entry?
In ext4_dir_entry_len(), ext4_dirent_get_data_len(de, rec_len) validates
each dirdata header against the entry's current rec_len and returns 0 when
offset >= rec_len, offset + sizeof(*ddh) > rec_len, or ddh_len > rec_len -
offset:
fs/ext4/ext4.h:ext4_dirent_get_data_len() {
...
if (offset >= rec_len)
return 0;
...
if (extra_data_flags & 1) {
if (offset + sizeof(*ddh) > rec_len)
return 0;
{
u8 ddh_len = READ_ONCE(ddh->ddh_length);
if (ddh_len == 0 || ddh_len > rec_len - offset)
return 0;
dlen += ddh_len + (dlen == 0);
offset += ddh_len;
...
}
Because rlen % 4 != 0 is already checked earlier in
__ext4_check_dir_entry(), any non-zero dirdata returned by
ext4_dirent_get_data_len() satisfies 8 + de->name_len + dirdata == offset
<= rlen, so ext4_dirent_rec_len(de->name_len + dirdata, NULL) <= rlen.
When a corrupted entry has dirdata flags set with ddh_length exceeding its
current rec_len, ext4_dirent_get_data_len() returns 0 and
__ext4_check_dir_entry() accepts the entry.
Later, during do_split(), dx_move_dirents() and dx_pack_dirents() compute
rec_len = ext4_dir_entry_len(de, blocksize, dir) while de->rec_len still
has its smaller pre-split value, copying only those bytes before do_split()
expands the last entry's rec_len to the end of the half-block. Once
de->rec_len grows (either in do_split() or after
ext4_generic_delete_entry()), subsequent calls to ext4_dir_entry_len() in
add_dirent_to_buf() and ext4_insert_dentry() see the larger rec_len, so the
previously out-of-bounds ddh_length now satisfies ddh_len <= rec_len -
offset and annexes uncopied or stale buffer bytes into the directory entry.
[ ... ]
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 9953583ad6c3f..0c91e4da00fbc 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
[ ... ]
> @@ -2122,10 +2122,11 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
> int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
> void *buf, int buf_size,
> struct ext4_filename *fname,
> - struct ext4_dir_entry_2 **dest_de)
> + struct ext4_dir_entry_2 **dest_de,
> + int dlen)
> {
> struct ext4_dir_entry_2 *de;
> - unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
> + unsigned short reclen = ext4_dirent_rec_len(fname_len(fname) + dlen, dir);
> int nlen, rlen;
> unsigned int offset = 0;
> char *top;
> @@ -2138,7 +2139,7 @@ int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
> return -EFSCORRUPTED;
> if (ext4_match(dir, fname, de))
> return -EEXIST;
> - 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 ? rlen - nlen : rlen) >= reclen)
> break;
[Severity: Critical]
Can changing nlen to ext4_dir_entry_len() in ext4_find_dest_de() allow
unsigned len in make_indexed_dir() to underflow and overflow data2 in
memcpy(data2, de, len)?
In __ext4_add_entry() -> add_dirent_to_buf() -> ext4_find_dest_de() on
block 0 of an unindexed directory (where buf_size = blocksize - csum_size),
the loop runs while (char *)de <= buf + buf_size - reclen. Because nlen now
includes dirdata via ext4_dir_entry_len(), the '.' entry (dot_de) on a
corrupted or crafted image can have rlen > buf_size - reclen while its
dirdata makes rlen - nlen < reclen. In that case, ext4_find_dest_de()
checks dot_de, advances de to dotdot_de at an offset past top, exits the
loop without running ext4_check_dir_entry() on dotdot_de, and returns
-ENOSPC.
__ext4_add_entry() then calls make_indexed_dir(), which validates dotdot_de
through ext4_check_dx_root() and dx_get_dx_info(). However, both check the
end of dotdot_de against blocksize rather than blocksize - csum_size:
fs/ext4/namei.c:ext4_check_dx_root() {
...
de = ext4_next_entry(dotdot_de, blocksize);
if ((char *)de >= (((char *)dot_de) + blocksize)) {
error_msg = "invalid rec_len for '..'";
goto corrupted;
}
...
}
When metadata_csum is enabled (csum_size = 12) and dotdot_de ends between
blocksize - csum_size + 1 and blocksize - 1, make_indexed_dir() computes:
fs/ext4/namei.c:make_indexed_dir() {
...
/* The 0th block becomes the root, move the dirents out */
len = ((char *)dot_de) + (blocksize - csum_size) - (char *)de;
...
memcpy(data2, de, len);
}
Since (char *)de > (char *)dot_de + (blocksize - csum_size), unsigned len
underflows and is passed to memcpy(data2, de, len) and memset(de, 0, len).
[ ... ]
> @@ -2206,9 +2207,27 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
>
> if (!de) {
> err = ext4_find_dest_de(dir, bh, bh->b_data,
> - blocksize - csum_size, fname, &de);
> + blocksize - csum_size, fname, &de, 0);
> if (err)
> return err;
> + } else {
> + /*
> + * do_split() hands over the last entry of a half. Its length
> + * comes from on-disk dirdata, so check that the new entry fits
> + * before anything is written.
> + */
> + unsigned int nlen = de->inode ?
> + ext4_dir_entry_len(de, blocksize, dir) : 0;
> + unsigned int rlen = ext4_rec_len_from_disk(de->rec_len,
> + blocksize);
> +
> + if (nlen > rlen ||
> + rlen - nlen < ext4_dirent_rec_len(fname_len(fname), dir)) {
> + EXT4_ERROR_INODE(dir,
> + "no room for new entry in split block %llu",
> + (unsigned long long)bh->b_blocknr);
> + return -EFSCORRUPTED;
> + }
> }
[Severity: High]
When this check in add_dirent_to_buf() returns -EFSCORRUPTED for a de
returned by do_split(), does it leave bh modified with a zeroed checksum
tail and without calling ext4_handle_dirty_dirblock()?
Before returning de to make_indexed_dir() or ext4_dx_add_entry(),
do_split() has already obtained write access to *bh, bh2, and frame->bh,
modified *bh and bh2 in-place via dx_move_dirents() and dx_pack_dirents(),
zeroed det_checksum in both blocks via ext4_initialize_dirent_tail(), and
dirtied bh2 and frame->bh:
fs/ext4/namei.c:do_split() {
...
if (csum_size) {
ext4_initialize_dirent_tail(*bh, blocksize);
ext4_initialize_dirent_tail(bh2, blocksize);
}
...
if (hinfo->hash >= hash2) {
swap(*bh, bh2);
de = de2;
}
dx_insert_block(frame, hash2 + continued, newblock);
err = ext4_handle_dirty_dirblock(handle, dir, bh2);
if (err)
goto journal_error;
err = ext4_handle_dirty_dx_node(handle, dir, frame->bh);
if (err)
goto journal_error;
...
}
do_split() relies on add_dirent_to_buf() to checksum and dirty *bh via
ext4_handle_dirty_dirblock(handle, dir, bh). If add_dirent_to_buf() returns
-EFSCORRUPTED early here, bh2 and frame->bh are committed in the journal
with the post-split state (for instance in make_indexed_dir() or when
mounted with errors=continue), while bh is never passed to
ext4_handle_dirty_dirblock().
[1] https://lore.kernel.org/r/20261029122413.230409-8-ablagodarenko@thelustrecollective.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103134921.610047-1-ablagodarenko@thelustrecollective.com?part=8
next prev parent reply other threads:[~2026-11-03 14:20 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 13:49 [PATCH v14 00/12] Data in direntry (dirdata) feature Artem Blagodarenko
2026-11-03 13:49 ` [PATCH v14 01/12] ext4: validate count against limit in ext4_dx_csum_verify/_set Artem Blagodarenko
2026-11-03 13:56 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 02/12] ext4: use the e2fsck limit for htree index nodes Artem Blagodarenko
2026-11-03 13:56 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 03/12] ext4: replace ext4_dir_entry with ext4_dir_entry_2 Artem Blagodarenko
2026-11-03 14:05 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 04/12] ext4: add ext4_dir_entry_is_tail() Artem Blagodarenko
2026-11-03 13:58 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 05/12] ext4: add dirdata format definitions and access helpers Artem Blagodarenko
2026-11-03 13:56 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 06/12] ext4: refactor dx_root to support variable dirent sizes Artem Blagodarenko
2026-11-03 14:18 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 07/12] ext4: add ext4_dir_entry_len() Artem Blagodarenko
2026-11-03 14:08 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 08/12] ext4: rename ext4_dir_rec_len() and clarify dirdata usage Artem Blagodarenko
2026-11-03 14:20 ` sashiko-bot [this message]
2026-11-03 13:49 ` [PATCH v14 09/12] ext4: dirdata feature Artem Blagodarenko
2026-11-03 14:27 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 10/12] ext4: add dirdata LUFID support for directory entry rename Artem Blagodarenko
2026-11-03 14:17 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 11/12] ext4: add dirdata set/get helpers Artem Blagodarenko
2026-11-03 14:18 ` sashiko-bot
2026-11-03 13:49 ` [PATCH v14 12/12] ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory entries Artem Blagodarenko
2026-11-03 14:29 ` 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-195439@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