* [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.