Linux EXT4 FS development
 help / color / mirror / Atom feed
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 05/11] ext4: refactor dx_root to support variable dirent sizes
Date: Thu, 29 Oct 2026 12:49:15 +0000	[thread overview]
Message-ID: <sashiko-outbox-189313@kernel.org> (raw)
In-Reply-To: <20261029122413.230409-6-ablagodarenko@thelustrecollective.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] ext4: dx_get_dx_info() steps over dot entry using ext4_dir_entry_len()
- [High] ext4: dx_node_limit() change breaks existing encrypted casefold htree dirs
- [High] ext4: make_indexed_dir() corrupts directory state if dx_get_dx_info() fails
--

commit ac1861c16d9ece959b6431b7c2c9637e888e44a0
Author: Artem Blagodarenko <artem.blagodarenko@gmail.com>

ext4: refactor dx_root to support variable dirent sizes

This commit replaces the monolithic struct dx_root definition with a separate
struct dx_root_info and adds dx_get_dx_info() and ext4_dir_entry_len() so
that "." and ".." directory entries in the htree root block can have variable
lengths when carrying dirdata.

> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 533340c5dbc0d..59cc01b91c418 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
[ ... ]
> @@ -539,6 +530,31 @@ ext4_next_entry(struct ext4_dir_entry_2 *p, unsigned long blocksize)
>   * Future: use high four bits of block for coalesce-on-delete flags
>   * Mask them off for now.
>   */
> +static struct dx_root_info *dx_get_dx_info(struct inode *dir, void *de_buf)
> +{
> +	unsigned int blocksize = dir->i_sb->s_blocksize;
> +	void *base = de_buf;
> +
> +	/* '.' and '..' never carry the casefold+fscrypt hash, so pass NULL
> +	 * for dir regardless of the directory's flags */
> +	if (ext4_rec_len_from_disk(((struct ext4_dir_entry_2 *)de_buf)->rec_len,
> +				   blocksize) < EXT4_BASE_DIR_LEN)
> +		return ERR_PTR(-EFSCORRUPTED);
> +	de_buf += ext4_dir_entry_len(de_buf, blocksize, NULL);

[Severity: High]
Should dx_get_dx_info() advance past the "." entry using ext4_next_entry()
(or dot_de->rec_len) instead of ext4_dir_entry_len()?

Unlike "..", whose rec_len in an indexed root block is set to cover the rest
of the block, "." always stores the actual offset to ".." in dot_de->rec_len.
Other callers locate ".." via dot_de->rec_len, such as make_indexed_dir():

	dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
	...
	dotdot_de = ext4_next_entry(dot_de, blocksize);

and ext4_htree_fill_tree():

	de = (struct ext4_dir_entry_2 *) frames[0].bh->b_data;
	de = ext4_next_entry(de, dir->i_sb->s_blocksize);

If "." has slack space so that dot_de->rec_len is larger than
ext4_dir_entry_len(dot_de, blocksize, NULL) (for example, dot_de->rec_len is
24 while its minimum entry length is 12), make_indexed_dir() leaves dotdot_de
at offset 24 and preserves dot_de->rec_len:

	dotdot_de->rec_len =
		ext4_rec_len_to_disk(blocksize - le16_to_cpu(dot_de->rec_len),
				     blocksize);

	/* initialize hashing info */
	dx_info = dx_get_dx_info(dir, dot_de);

When dx_get_dx_info() then steps over "." by ext4_dir_entry_len() (12 bytes)
instead of dot_de->rec_len (24 bytes), won't it read the unzeroed slack bytes
after "." as the ".." entry and return a dx_info pointer that overlaps and
overwrites dotdot_de when dx_info and entries[] are initialized?

> +
> +	/* dx root info is after dotdot entry */
> +	if (de_buf < base || (char *)de_buf - (char *)base +
> +	    EXT4_BASE_DIR_LEN > blocksize)
> +		return ERR_PTR(-EFSCORRUPTED);
> +	de_buf += ext4_dir_entry_len(de_buf, blocksize, NULL);
> +
> +	if (de_buf < base || (char *)de_buf - (char *)base +
> +			      sizeof(struct dx_root_info) +
> +			      sizeof(struct dx_entry) > blocksize)
> +		return ERR_PTR(-EFSCORRUPTED);
> +
> +	return (struct dx_root_info *)de_buf;
> +}
>  
>  static inline ext4_lblk_t dx_get_block(struct dx_entry *entry)
>  {
> @@ -593,8 +609,10 @@ static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize)
>  
>  static inline unsigned dx_node_limit(struct inode *dir)
>  {
> +	/* dx_node fake_dirent is always 8 bytes — it never carries a casefold
> +	 * hash, so pass NULL to suppress the hash-size term. */
>  	unsigned int entry_space = dir->i_sb->s_blocksize -
> -			ext4_dir_rec_len(0, dir);
> +			ext4_dir_rec_len(0, NULL);

[Severity: High]
Won't changing ext4_dir_rec_len(0, dir) to ext4_dir_rec_len(0, NULL) in
dx_node_limit() break existing multi-level encrypted and casefolded htree
directories?

In fs/ext4/ext4.h:

	static inline unsigned int ext4_dir_rec_len(__u8 name_len,
							const struct inode *dir)
	{
		int rec_len = (name_len + 8 + EXT4_DIR_ROUND);

		if (dir && ext4_hash_in_dirent(dir))
			rec_len += sizeof(struct ext4_dir_entry_hash);
		return (rec_len & ~EXT4_DIR_ROUND);
	}

For directories where ext4_hash_in_dirent(dir) is true, ext4_dir_rec_len(0,
dir) returns 16, whereas ext4_dir_rec_len(0, NULL) returns 8. Passing NULL
increases dx_node_limit(dir) by 1 entry (for example, from 509 to 510 on 4KB
blocks with metadata_csum enabled).

Existing multi-level encrypted + casefolded directories created since commit
471fbbea7ff7 ("ext4: handle casefolding with encryption") have the smaller
limit recorded on disk in each internal dx_node block. When dx_probe() reads
an internal node, it enforces an exact match:

	entries = ((struct dx_node *) frame->bh->b_data)->entries;

	if (dx_get_limit(entries) != dx_node_limit(dir)) {
		ext4_warning_inode(dir,
			"dx entry: limit %u != node limit %u",
			dx_get_limit(entries), dx_node_limit(dir));
		goto fail;
	}

Won't dx_probe() reject all existing multi-level encrypted + casefolded
directories as corrupt with ERR_BAD_DX_DIR, causing __ext4_add_entry() to
trigger EXT4_ERROR_INODE() when metadata_csum is enabled?

[ ... ]
> @@ -2286,24 +2317,32 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
>  		ext4_initialize_dirent_tail(bh2, blocksize);
>  
>  	/* Initialize the root; the dot dirents already exist */
> -	de = (struct ext4_dir_entry_2 *) (&root->dotdot);
> -	de->rec_len = ext4_rec_len_to_disk(
> -			blocksize - ext4_dir_rec_len(2, NULL), blocksize);
> -	memset (&root->info, 0, sizeof(root->info));
> -	root->info.info_length = sizeof(root->info);
> +	dotdot_de->rec_len =
> +		ext4_rec_len_to_disk(blocksize - le16_to_cpu(dot_de->rec_len),
> +				     blocksize);
> +
> +	/* initialize hashing info */
> +	dx_info = dx_get_dx_info(dir, dot_de);
> +	if (IS_ERR(dx_info)) {
> +		brelse(bh2);
> +		brelse(bh);
> +		return PTR_ERR(dx_info);
> +	}

[Severity: High]
If dx_get_dx_info() fails here in make_indexed_dir(), is it safe to return
early after the directory block and inode have already been modified?

By the time dx_get_dx_info() is called, make_indexed_dir() has already
appended bh2 (extending dir->i_size and marking the inode dirty in the
running handle), set EXT4_INODE_INDEX on dir, wiped the original entries in
block 0 (bh) with memset(de, 0, len), and updated dotdot_de->rec_len to span
the rest of block 0:

	bh2 = ext4_append(handle, dir, &block);
	if (IS_ERR(bh2)) {
		brelse(bh);
		return PTR_ERR(bh2);
	}
	ext4_set_inode_flag(dir, EXT4_INODE_INDEX);
	data2 = bh2->b_data;

	memcpy(data2, de, len);
	memset(de, 0, len); /* wipe old data */

Returning PTR_ERR(dx_info) at this point leaves dir with EXT4_INODE_INDEX set
and an extended i_size, while block 0 has its entries wiped and no valid
dx_root_info initialized. Could dx_get_dx_info(dir, dot_de) be validated
before ext4_append() and memset() modify the directory?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029122413.230409-1-ablagodarenko@thelustrecollective.com?part=5

  reply	other threads:[~2026-10-29 12:49 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 [this message]
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
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-189313@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