[PATCH 06/10] ufs: fix fragment relocation offsets within a folio
From: Ali Ahmet Memis
Date: Sat Aug 01 2026 - 19:01:10 EST
ufs_change_blocknr() walks the buffers covering the fragments being
moved from oldb to newb. Inside a folio it starts at the buffer holding
the first fragment:
pos = i & mask;
for (j = 0; j < pos; ++j)
bh = bh->b_this_page;
so j is a buffer index within the folio, not an offset into the range
being relocated. The body then uses it as if it were one:
pos = (i - beg) + j;
On the first folio i equals beg, so pos comes out as beg & mask where it
should be zero. Every fragment is then shifted by that amount, both the
source it is read from, oldb + pos, and the destination recorded in
bh->b_blocknr, newb + pos. The tail of the relocation ends up past the
end of the new allocation, which is only count fragments long, leaving
dirty buffers pointing at fragments that can belong to another file or
to metadata. Later folios start folio aligned and are unaffected.
Reaching it needs a page larger than the filesystem block. beg is the
start of the existing tail and so is always a multiple of fs_frag, while
mask is blks_per_page - 1. When PAGE_SIZE is at most fs_bsize the two
are powers of two with fs_frag the larger, beg & mask is zero and
nothing goes wrong. ufs_fill_super() already refuses fs_bsize below
4096, so every 4 KiB page system is in that case. A 16 KiB or 64 KiB
page kernel, as used on arm64 and ppc64, mounting a filesystem with a
smaller block is not.
Keep the starting buffer index and subtract it, so pos counts from the
start of the relocated range again.
Reproduced on an arm64 64 KiB page kernel with a 4096/512 UFS1
filesystem, where a block is 8 fragments and blks_per_page is 128. A
file with a one fragment tail in its second block has beg 8, so
beg & mask is 8. Filling the fragments that physically follow that tail
and then extending the file forces the relocation:
ufs: RELOC: beg=8 count=1 oldb=1457 newb=1528 mask=127 beg&mask=8
Before this patch the first byte of the relocated tail reads back as 0
instead of the written data; after it the file compares equal. The same
test on a 4 KiB page kernel never gets beg & mask nonzero, as expected.
Fixes: 5431bf97ce69 ("[PATCH] ufs: prepare write + change blocks on the fly")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Ali Ahmet Memis <ali@xxxxxxxxxxxxxx>
---
fs/ufs/balloc.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/fs/ufs/balloc.c b/fs/ufs/balloc.c
index 628edfde3a9f..f617df64c45b 100644
--- a/fs/ufs/balloc.c
+++ b/fs/ufs/balloc.c
@@ -241,7 +241,7 @@ static void ufs_change_blocknr(struct inode *inode, sector_t beg,
const unsigned mask = blks_per_page - 1;
struct address_space * const mapping = inode->i_mapping;
pgoff_t index, cur_index, last_index;
- unsigned pos, j, lblock;
+ unsigned int pos, j, lblock, first;
sector_t end, i;
struct buffer_head *head, *bh;
@@ -272,8 +272,8 @@ static void ufs_change_blocknr(struct inode *inode, sector_t beg,
head = folio_buffers(folio);
bh = head;
- pos = i & mask;
- for (j = 0; j < pos; ++j)
+ first = i & mask;
+ for (j = 0; j < first; ++j)
bh = bh->b_this_page;
if (unlikely(index == last_index))
@@ -284,7 +284,7 @@ static void ufs_change_blocknr(struct inode *inode, sector_t beg,
do {
if (j >= lblock)
break;
- pos = (i - beg) + j;
+ pos = (i - beg) + (j - first);
if (!buffer_mapped(bh))
map_bh(bh, inode->i_sb, oldb + pos);
--
2.55.0