Re: [PATCH v2 3/3] ocfs2: reject non-inline dinodes with i_size and zero i_clusters

Joseph Qi <[email protected]>
Newsgroups dev.linux.lists.ocfs2-devel,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>

On 5/19/26 7:04 PM, Michael Bommarito wrote:
> On a volume mounted without OCFS2_FEATURE_INCOMPAT_SPARSE_ALLOC, a
> non-inline regular file with non-zero i_size and zero i_clusters is
> structurally malformed: the extent map declares no allocated clusters
> yet the size header claims content exists.  Keep rejecting that shape,
> but express it through a shared predicate so the same invariant is
> available to normal inode reads and online filecheck.
> 
> The same zero-cluster shape is also malformed for non-inline directories.
> ocfs2 directory growth allocates backing storage before advancing
> i_size, and ocfs2_dir_foreach_blk_el() later walks until ctx->pos reaches
> i_size_read(inode).  A forged directory dinode with a huge i_size and no
> clusters would repeatedly fail on holes while advancing through the
> claimed size.
> 
> Sparse regular files remain exempt: on sparse-alloc volumes, truncate can
> legitimately grow i_size without allocating clusters.  System inodes and
> inline-data dinodes also retain their separate storage rules.
> 
> Mirror the check in ocfs2_filecheck_validate_inode_block() as well.
> filecheck reports through its own error namespace, so malformed
> size/cluster state is logged as a filecheck invalid-inode result rather
> than via ocfs2_error(), but it must not proceed into
> ocfs2_populate_inode().
> 
> Fixes: b657c95c1108 ("ocfs2: Wrap inode block reads in a dedicated function.")
> Cc: [email protected]
> Link: https://sashiko.dev/#/patchset/20260517111015.3187935-1-michael.bommarito%40gmail.com
> Signed-off-by: Michael Bommarito <[email protected]>
> Assisted-by: Claude:claude-opus-4-7

Reviewed-by: Joseph Qi <[email protected]>
> ---
>  fs/ocfs2/inode.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 60 insertions(+)
> 
> diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
> index 992980ea98046..432eac01c1763 100644
> --- a/fs/ocfs2/inode.c
> +++ b/fs/ocfs2/inode.c
> @@ -82,6 +82,24 @@ static bool ocfs2_dinode_has_unexpected_rdev(struct ocfs2_dinode *di)
>  	return !S_ISCHR(mode) && !S_ISBLK(mode) && di->id1.dev1.i_rdev != 0;
>  }
>  
> +static bool ocfs2_dinode_has_size_without_clusters(struct super_block *sb,
> +						   struct ocfs2_dinode *di)
> +{
> +	umode_t mode = le16_to_cpu(di->i_mode);
> +
> +	if (le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL)
> +		return false;
> +	if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL)
> +		return false;
> +	if (!le64_to_cpu(di->i_size) || le32_to_cpu(di->i_clusters))
> +		return false;
> +
> +	if (S_ISDIR(mode))
> +		return true;
> +
> +	return !ocfs2_sparse_alloc(OCFS2_SB(sb)) && S_ISREG(mode);
> +}
> +
>  void ocfs2_set_inode_flags(struct inode *inode)
>  {
>  	unsigned int flags = OCFS2_I(inode)->ip_attr;
> @@ -1563,6 +1581,33 @@ int ocfs2_validate_inode_block(struct super_block *sb,
>  		goto bail;
>  	}
>  
> +	/*
> +	 * Non-inline directories must not have i_size without allocated
> +	 * clusters: directory growth adds storage before advancing i_size,
> +	 * and readdir walks i_size block-by-block.  A forged directory
> +	 * with zero clusters and a huge i_size would repeatedly fault on
> +	 * holes while advancing through the claimed size.
> +	 *
> +	 * Non-inline regular files have the same invariant on non-sparse
> +	 * volumes.  Sparse regular files are different: truncate can
> +	 * legitimately grow i_size without allocating clusters, so keep
> +	 * the sparse-alloc carveout for S_IFREG only.  System inodes and
> +	 * inline-data dinodes have their own storage rules.
> +	 */
> +	if (ocfs2_dinode_has_size_without_clusters(sb, di)) {
> +		if (S_ISDIR(le16_to_cpu(di->i_mode)))
> +			rc = ocfs2_error(sb,
> +					 "Invalid dinode #%llu: directory i_size %llu with i_clusters 0 and no inline-data flag\n",
> +					 (unsigned long long)bh->b_blocknr,
> +					 (unsigned long long)le64_to_cpu(di->i_size));
> +		else
> +			rc = ocfs2_error(sb,
> +					 "Invalid dinode #%llu: regular file i_size %llu with i_clusters 0 and no inline-data flag on non-sparse volume\n",
> +					 (unsigned long long)bh->b_blocknr,
> +					 (unsigned long long)le64_to_cpu(di->i_size));
> +		goto bail;
> +	}
> +
>  	if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) {
>  		struct ocfs2_inline_data *data = &di->id2.i_data;
>  
> @@ -1712,6 +1757,21 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
>  		     le16_to_cpu(di->i_mode),
>  		     (unsigned long long)le64_to_cpu(di->id1.dev1.i_rdev));
>  		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
> +		goto bail;
> +	}
> +
> +	if (ocfs2_dinode_has_size_without_clusters(sb, di)) {
> +		if (S_ISDIR(le16_to_cpu(di->i_mode)))
> +			mlog(ML_ERROR,
> +			     "Filecheck: invalid dinode #%llu: directory i_size %llu with i_clusters 0 and no inline-data flag\n",
> +			     (unsigned long long)bh->b_blocknr,
> +			     (unsigned long long)le64_to_cpu(di->i_size));
> +		else
> +			mlog(ML_ERROR,
> +			     "Filecheck: invalid dinode #%llu: regular file i_size %llu with i_clusters 0 and no inline-data flag on non-sparse volume\n",
> +			     (unsigned long long)bh->b_blocknr,
> +			     (unsigned long long)le64_to_cpu(di->i_size));
> +		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
>  	}
>  
>  bail:
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.