When a file's extension-block count outgrows the linear cache, affs_grow_extcache() raises i_lc_shift and then "shrinks the old cache to make more space": off = 1 << (lc_shift - AFFS_I(inode)->i_lc_shift); for (i = 1, j = off; j < AFFS_LC_SIZE; i++, j += off) AFFS_I(inode)->i_ac[i] = AFFS_I(inode)->i_ac[j]; That loop is the compaction of the linear cache i_lc, which is indexed by ext >> i_lc_shift and has AFFS_LC_SIZE entries. The associative cache i_ac is keyed by the extension number stored in each entry and needs no compaction. Applying the loop to i_ac has two effects: - i_ac has AFFS_AC_SIZE entries, half of AFFS_LC_SIZE, so the loop reads up to about 2 KiB past the end of the cache page. - i_lc is never compacted, so after the shift changes, lookups through it return the wrong extension block, and reads return wrong data. This happens once an open file grows past AFFS_LC_SIZE extension blocks (512 with 4K pages, about 18 MB with 512-byte blocks). Compact i_lc instead; the bound stays the same. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Matthias Goergens --- Reproduced under QEMU with KASAN on a crafted 45 MiB FFS image with 512-byte blocks: after an open file grows from 320 to 700 extension blocks, KASAN reports a slab-use-after-free read of 8 bytes in affs_get_extblock_slow() at the shift change, and 3990 of 4000 random reads return data from the wrong block. With this patch, KASAN reports nothing in affs and all 4000 reads are correct. Image generator, test program and QEMU runner: https://github.com/matthiasgoergens/linux/tree/reproducer/2026-09-26-affs-extcache fs/affs/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/affs/file.c b/fs/affs/file.c index 23e088a7ed4f..4fd9ec3b3d79 100644 --- a/fs/affs/file.c +++ b/fs/affs/file.c @@ -82,7 +82,7 @@ affs_grow_extcache(struct inode *inode, u32 lc_idx) /* first shrink old cache to make more space */ off = 1 << (lc_shift - AFFS_I(inode)->i_lc_shift); for (i = 1, j = off; j < AFFS_LC_SIZE; i++, j += off) - AFFS_I(inode)->i_ac[i] = AFFS_I(inode)->i_ac[j]; + AFFS_I(inode)->i_lc[i] = AFFS_I(inode)->i_lc[j]; AFFS_I(inode)->i_lc_shift = lc_shift; AFFS_I(inode)->i_lc_mask = lc_mask; base-commit: 6812ce4e4379ffc99c52401ec28f0d7ffbc36206 -- 2.55.0