All of lore.kernel.org
 help / color / mirror / Atom feed
* [to-be-updated] ocfs2-reject-regular-files-with-non-zero-i_size-and-zero-i_clusters.patch removed from -mm tree
@ 2026-05-19 17:41 Andrew Morton
  0 siblings, 0 replies; only message in thread
From: Andrew Morton @ 2026-05-19 17:41 UTC (permalink / raw)
  To: mm-commits, michael.bommarito, akpm


The quilt patch titled
     Subject: ocfs2: reject regular files with non-zero i_size and zero i_clusters
has been removed from the -mm tree.  Its filename was
     ocfs2-reject-regular-files-with-non-zero-i_size-and-zero-i_clusters.patch

This patch was dropped because an updated version will be issued

------------------------------------------------------
From: Michael Bommarito <michael.bommarito@gmail.com>
Subject: ocfs2: reject regular files with non-zero i_size and zero i_clusters
Date: Sun, 17 May 2026 07:10:14 -0400

On a volume mounted WITHOUT OCFS2_FEATURE_INCOMPAT_SPARSE_ALLOC, a regular
file with non-zero i_size, zero i_clusters, and no OCFS2_INLINE_DATA_FL
flag is structurally malformed: the extent map declares no allocated
clusters yet the size header claims the file has content. 
ocfs2_populate_inode() copies i_size into the in-core inode and dispatches
to ocfs2_aops; subsequent reads or truncates then operate on an
inconsistent extent state.

This is the shape an attacker who keeps the rest of the extent list intact
(to satisfy the inline-data, refcount, chain-list, and per-field
validators already in this function) would produce when forging only the
inode header to publish a synthetic file size on a victim node.  It is
also the shape on-disk corruption of the i_clusters field produces. 
Reject early in the validator.

The check is restricted to non-sparse volumes (ocfs2_sparse_alloc()
returns false).  On non-sparse mounts the allocator path always grows
clusters before i_size: ocfs2_extend_file() takes the !sparse branch into
ocfs2_extend_no_holes(), which calls ocfs2_extend_allocation() to journal
new clusters first, and only then ocfs2_simple_size_update() journals the
larger i_size.  The truncate path likewise lowers i_size in
ocfs2_orphan_for_truncate() and then frees clusters in
ocfs2_commit_truncate(), which uses ocfs2_clusters_for_bytes(new_i_size)
as its new_highest_cpos: when new_i_size > 0 the floor is at least one
cluster, so the on-disk dinode never legitimately exposes a non-inline
regular file with i_size > 0 and i_clusters == 0 on a non-sparse volume.

On sparse-alloc volumes the same shape is legitimate: an
ocfs2_extend_file() call goes through ocfs2_zero_extend() +
ocfs2_simple_size_update(), which grows i_size on its own without changing
i_clusters; a freshly truncate -s 1M of a sparse regular file is therefore
on-disk (i_size = 1048576, i_clusters = 0).  The check therefore opts out
via ocfs2_sparse_alloc(OCFS2_SB(sb)).

System inodes (OCFS2_SYSTEM_FL) carry their own size and cluster
invariants validated by the allocator, journal, quota, and truncate-log
subsystems; skip them here.  The inline-data fast path is filtered
separately by its own dedicated branch below: its well-formed case is
exactly i_clusters == 0 with i_size <= id_count.  Symlinks legitimately
keep i_clusters == 0 with non-zero i_size (fast symlinks), so this check
is restricted to S_IFREG.

Link: https://lore.kernel.org/20260517111015.3187935-4-michael.bommarito@gmail.com
Fixes: b657c95c1108 ("ocfs2: Wrap inode block reads in a dedicated function.")
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-7
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Cc: Mark Fasheh <mark@fasheh.com>
Cc: Joel Becker <jlbec@evilplan.org>
Cc: Junxiao Bi <junxiao.bi@oracle.com>
Cc: Changwei Ge <gechangwei@live.cn>
Cc: Jun Piao <piaojun@huawei.com>
Cc: Heming Zhao <heming.zhao@suse.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---

 fs/ocfs2/inode.c |   41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

--- a/fs/ocfs2/inode.c~ocfs2-reject-regular-files-with-non-zero-i_size-and-zero-i_clusters
+++ a/fs/ocfs2/inode.c
@@ -1571,6 +1571,47 @@ int ocfs2_validate_inode_block(struct su
 		goto bail;
 	}
 
+	/*
+	 * On a non-sparse volume, a regular file with non-zero i_size
+	 * and zero i_clusters that is not marked as inline data is
+	 * structurally malformed: the extent map declares no allocated
+	 * clusters yet the size header claims the file has content.
+	 * ocfs2_populate_inode() would still publish i_size to VFS and
+	 * leave the extent state inconsistent for any later read or
+	 * truncate.  This is the shape an attacker who keeps the rest
+	 * of the extent list intact (to satisfy the inline-data,
+	 * refcount, chain-list, and per-field validators above) would
+	 * produce when forging only the inode header to publish a
+	 * synthetic file size on a victim node.  It is also the shape
+	 * on-disk corruption of the i_clusters field produces.
+	 *
+	 * The check opts out on sparse-alloc volumes, where the
+	 * extend path (ocfs2_extend_file -> ocfs2_zero_extend ->
+	 * ocfs2_simple_size_update) legitimately grows i_size without
+	 * allocating clusters.  On non-sparse volumes the equivalent
+	 * path (ocfs2_extend_no_holes) journals clusters first and
+	 * i_size second, and truncate-down floors i_clusters at
+	 * ocfs2_clusters_for_bytes(new_i_size) which is >= 1 whenever
+	 * new_i_size > 0, so the rejected shape never appears on disk.
+	 *
+	 * Skip system inodes (OCFS2_SYSTEM_FL) and the inline-data
+	 * fast path (handled below).  Symlinks legitimately keep
+	 * i_clusters == 0 with non-zero i_size (fast symlinks), so
+	 * restrict to S_IFREG.
+	 */
+	if (!ocfs2_sparse_alloc(OCFS2_SB(sb)) &&
+	    S_ISREG(le16_to_cpu(di->i_mode)) &&
+	    !(le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL) &&
+	    !(le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) &&
+	    le64_to_cpu(di->i_size) != 0 &&
+	    le32_to_cpu(di->i_clusters) == 0) {
+		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;
 
_

Patches currently in -mm which might be from michael.bommarito@gmail.com are



^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-05-19 17:41 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-19 17:41 [to-be-updated] ocfs2-reject-regular-files-with-non-zero-i_size-and-zero-i_clusters.patch removed from -mm tree Andrew Morton

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.