]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ocfs2: fix UBSAN array-index-out-of-bounds in ocfs2_sum_rightmost_rec
authorIan Bridges <icb@fastmail.org>
Thu, 11 Jun 2026 00:23:11 +0000 (19:23 -0500)
committerAndrew Morton <akpm@linux-foundation.org>
Wed, 17 Jun 2026 22:37:46 +0000 (15:37 -0700)
[BUG]
On-disk corruption setting l_next_free_rec to 0 in an inode's embedded
extent list triggers a UBSAN panic on the next write to that file.

[CAUSE]
ocfs2_sum_rightmost_rec() computes
i = le16_to_cpu(el->l_next_free_rec) - 1
and accesses el->l_recs[i] without validating i. When l_next_free_rec
is 0, i becomes -1; when l_next_free_rec exceeds l_count, i falls
past the end of the array. Either case violates the
__counted_by_le(l_count) annotation on l_recs[] and triggers UBSAN.

[FIX]
Validate the inode's embedded extent list when the inode is read, in
ocfs2_validate_inode_block(): l_count must be non-zero and no larger
than the inode block can hold, and l_next_free_rec must not exceed
l_count. A corrupt list is rejected at read time, before the b-tree
code can index l_recs[] out of bounds.

Link: https://lore.kernel.org/ain_780qc0P4ypNd@dev
Signed-off-by: Ian Bridges <icb@fastmail.org>
Reported-by: syzbot+be16e33db01e6644db7a@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=be16e33db01e6644db7a
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

index 6fc29920ecb2d76b82f737482c812cf66592166e..662dbc845b8bd220a52829bad2cbca80b6203a31 100644 (file)
@@ -1696,6 +1696,38 @@ int ocfs2_validate_inode_block(struct super_block *sb,
                goto bail;
        }
 
+       if (ocfs2_dinode_has_extents(di)) {
+               struct ocfs2_extent_list *el = &di->id2.i_list;
+               u16 count = le16_to_cpu(el->l_count);
+               u16 next_free = le16_to_cpu(el->l_next_free_rec);
+
+               if (count == 0) {
+                       rc = ocfs2_error(sb,
+                                        "Invalid dinode %llu: extent list l_count is zero\n",
+                                        (unsigned long long)bh->b_blocknr);
+                       goto bail;
+               }
+               /*
+                * The exact capacity depends on i_xattr_inline_size, another
+                * unvalidated on-disk field. Inline xattrs only shrink the
+                * list, so the no-xattr maximum is a safe upper bound that a
+                * valid l_count never exceeds.
+                */
+               if (count > ocfs2_extent_recs_per_inode(sb)) {
+                       rc = ocfs2_error(sb,
+                                        "Invalid dinode %llu: extent list l_count %u exceeds max %u\n",
+                                        (unsigned long long)bh->b_blocknr, count,
+                                        ocfs2_extent_recs_per_inode(sb));
+                       goto bail;
+               }
+               if (next_free > count) {
+                       rc = ocfs2_error(sb,
+                                        "Invalid dinode %llu: extent list l_next_free_rec %u exceeds l_count %u\n",
+                                        (unsigned long long)bh->b_blocknr, next_free, count);
+                       goto bail;
+               }
+       }
+
        rc = 0;
 
 bail: