[PATCH] isofs: bound empty directory blocks in isofs_read_level3_size()

From: Matthias Goergens

Date: Sat Sep 26 2026 - 04:35:36 EST


isofs_read_level3_size() walks the sections of a level-3 (multi-extent)
directory record and, on a zero length byte, moves on to the next
block with no limit. A crafted image can put a long run of empty
blocks between two sections of a multi-extent file, and the walk reads
every one of them before it gives up, all the way to the end of the
device if it has to.

Real discs do have trailing empty directory blocks, written by tools
such as Easy CD Creator and Nero, but always at the end of the
directory, never between two sections of a multi-extent record, so
they do not exercise this path [1].

Jan Kara suggested treating an empty block like a section: count it
towards the existing 100-section limit rather than adding a separate
one [2]. Do that: both the section count and the empty-block count
are checked against their combined total, so neither one alone can
reach 100 while the other keeps growing. Only a zero length byte at
the start of a block counts as an empty block; the same byte later in
a block still just ends that block's records, as it does today, and
is not counted.

Hui Peng's earlier patch for this function added its own, separate
limit on the number of empty blocks [3]; this uses the combined limit
Jan suggested instead.

Suggested-by: Jan Kara <jack@xxxxxxx>
Signed-off-by: Matthias Goergens <matthias.goergens@xxxxxxxxx>

[1] https://lore.kernel.org/all/20260925043804.2091174-1-matthias.goergens@xxxxxxxxx/
[2] https://lore.kernel.org/all/cmlro2xzle2aa7ebflvxhbcv74mea7p6qvxhin6egpdvyzyuxh@s45tpgxdal3n/
[3] https://lore.kernel.org/all/20260919222553.3792320-1-benquike@xxxxxxxxx/
---
Tested with fs/isofs built as a userspace program under ASan and UBSan,
and in a KASAN VM on Jan's for_next:

- The real discs from [1] (DM_BXL2, Comdex_05, ITSOFTCD_39, each with
trailing empty directory blocks) and the level-3 images from the
earlier patches list and read the same with and without this patch.
- A crafted multi-extent file with 150 empty blocks between two sections
now stops with "More than 100 file sections/empty blocks ?!?" instead
of reading all of them.
- A crafted file with 60 sections and 50 empty blocks interleaved (110
in all) is rejected; without this patch it is accepted.

The image generators are at
https://github.com/matthiasgoergens/linux/tree/reproducer/2026-09-26-isofs-empty-blocks

fs/isofs/inode.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
index 184350d2e6ad..e884618c0c53 100644
--- a/fs/isofs/inode.c
+++ b/fs/isofs/inode.c
@@ -1175,6 +1175,7 @@ static int isofs_read_level3_size(struct inode *inode)
struct buffer_head *bh = NULL;
unsigned long block, offset, block_saved, offset_saved;
int i = 0;
+ int empty_blocks = 0;
int more_entries = 0;
struct iso_inode_info *ei = ISOFS_I(inode);

@@ -1202,9 +1203,14 @@ static int isofs_read_level3_size(struct inode *inode)

/*
* If we are at the end of a block (or at its zero-padded
- * tail), move on to the next block.
+ * tail), move on to the next block. A zero length byte at
+ * the start of a block means the whole block is empty;
+ * count that towards the same limit as sections below, or a
+ * chain of empty blocks could be walked without bound.
*/
if (offset >= bufsize || de->length[0] == 0) {
+ if (offset == 0 && ++empty_blocks + i > 100)
+ goto out_toomany;
brelse(bh);
bh = NULL;
++block;
@@ -1233,7 +1239,7 @@ static int isofs_read_level3_size(struct inode *inode)
more_entries = de->flags[-high_sierra] & 0x80;

i++;
- if (i > 100)
+ if (i + empty_blocks > 100)
goto out_toomany;
} while (more_entries);
out:
@@ -1245,7 +1251,7 @@ static int isofs_read_level3_size(struct inode *inode)
return -EIO;

out_toomany:
- printk(KERN_INFO "%s: More than 100 file sections ?!?, aborting...\n"
+ printk(KERN_INFO "%s: More than 100 file sections/empty blocks ?!?, aborting...\n"
"isofs_read_level3_size: inode=%llu\n",
__func__, inode->i_ino);
goto out;

base-commit: f622f21cddacf8a0ef3b0344382924dc52c72a72
--
2.55.0