Bug 2535353 - Stricter isofs checks in kernel 7.3 cause read failures on Server DVD ISOs [NEEDINFO]
Summary: Stricter isofs checks in kernel 7.3 cause read failures on Server DVD ISOs
Keywords:
Status: NEW
Alias: None
Product: Fedora
Classification: Fedora
Component: kernel
Version: rawhide
Hardware: x86_64
OS: Linux
unspecified
high
Target Milestone: ---
Assignee: Justin M. Forbes
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
: 2532536 (view as bug list)
Depends On:
Blocks: F46BetaBlocker
TreeView+ depends on / blocked
 
Reported: 2026-09-16 12:15 UTC by Lukáš Růžička (Fedora Quality)
Modified: 2026-09-18 18:36 UTC (History)
19 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Type: ---
Embargoed:
scdbackup: needinfo?


Attachments (Terms of Use)

Description Lukáš Růžička (Fedora Quality) 2026-09-16 12:15:25 UTC
Currently, we have lots of failed tests based on the Server DVD of the 20260916 nightly compose in openQA, see https://openqa.fedoraproject.org/tests/5324997.
The installation fails when Anaconda tries to read packages from the installation ISO.

The affected ISO:
Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso (https://kojipkgs.fedoraproject.org/compose/rawhide/Fedora-Rawhide-20260916.n.0/compose/Server/x86_64/iso/Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso)

SHA256:
c9b91b528494359ff65f0097836202aa939d798ed98e1e4c26857d72ebb92f84

The ISO is not corrupted during download or copying. The SHA256 is the same
on the openQA server and on my local machine, which means that this is the exact copy from Koji.

For that asset, checkisomd5 reports: The media check is complete, the result is: PASS. It is OK to use this media.

However, when the ISO is booted with the kernel included on the media:

7.3.0-0.rc3.260914g704340f1cd0d.32.fc46.x86_64

the kernel reports:

iso9660: Corrupted directory entry in block 0 of inode 9024

Inode 9024 is the Packages/e directory on the ISO.

Shortly after that, Anaconda/libdnf fails to read:

ethtool-7.1-1.fc45.x86_64.rpm

and reports:

Failed to download 'ethtool-2:7.1-1.fc45.x86_64':
No more mirrors to try - All mirrors were already tried without success

This message is misleading because Anaconda is using the local repository:

file:///run/install/repo

There is no network repository involved.

I verified the RPM from the same ISO on another system:

rpm -K --nosignature /mnt/f45/Packages/e/ethtool-7.1-1.fc45.x86_64.rpm

returns:

OK

The whole ISO tree can also be read successfully on that system. The same ISO is therefore readable with an older kernel (7.2.4), but kernel 7.3 rejects
the directory structure.

This looks related to the recent isofs change in Linux 7.3 which stopped
accepting ISO9660 directory entries crossing block boundaries.

Anaconda later fails to access the ethtool RPM and aborts the installation.



Reproducible: Always

Steps to Reproduce:
1. Boot:
   Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso

2. Switch to a console.

3. Check the kernel:

   uname -r

   The ISO currently boots:
   7.3.0-0.rc3.260914g704340f1cd0d.32.fc46.x86_64

4. Access the directory:

   ls -li /run/install/repo/Packages/e

5. Check the kernel log:

   dmesg | grep -i iso9660
Actual Results:
iso9660: Corrupted directory entry in block 0 of inode 9024

Expected Results:
The Fedora installation ISO should contain an ISO9660 filesystem which can be
read successfully by current Linux kernels.

Additional Information:
Additional information:

The exact same ISO file passes checkisomd5 and has the same SHA256 on multiple
machines. This does not appear to be an openQA, network, or damaged-download
problem.

I am unaware which version of libisofs and on which machine produced the ISO file.

Comment 1 Kamil Páral (Red Hat) 2026-09-16 12:31:37 UTC
Sounds like F45 blocker candidate, proposing.

Comment 2 Robert Scheck 2026-09-16 12:38:45 UTC
Well, libisofs was not updated in Fedora since April 2026 (v1.5.8), thus I treat this more as a kernel bug/issue - reassigning.

Comment 3 Thomas Schmitt 2026-09-16 13:47:22 UTC
Hi,

Lukáš Růžička wrote:
> Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso
> iso9660: Corrupted directory entry in block 0 of inode 9024

The message probably stems from
  https://github.com/torvalds/linux/blob/master/fs/isofs/dir.c
line 137.
Cause for this occasion is isofs_dir_record_valid()
  https://github.com/torvalds/linux/blob/master/fs/isofs/namei.c
line 133 ff:
-----------------------------------------------------------------------
bool isofs_dir_record_valid(struct iso_directory_record *de,
                        unsigned long offset,
                        unsigned long bufsize)
{
        unsigned int len;
        unsigned int name_len;
        unsigned long min_len = offsetof(struct iso_directory_record, name);

        if (offset > bufsize || bufsize - offset < min_len)
                return false;
        len = isonum_711(de->length);
        name_len = isonum_711(de->name_len);
        if (len < min_len || name_len > len - min_len)
                return false;
        if (len > bufsize - offset)
                return false;
        return true;
}
-----------------------------------------------------------------------

"inode 9024" probably means that the offending directory record is near
to byte offset 9024*32 = 288768 = ISO 9660 block 141.
(See
   https://github.com/torvalds/linux/blob/master/fs/isofs/isofs.h
 line 152: isofs_get_ino() .
)

I don't feel apt to diagnose a possible kernel problem, but i can check
the offending ISO 9660 directory record for any irregularity and try
to find out what the test criteria in above function mean.

So where can i download
  Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso
(and what are its checksums) ?

-----------------------------------------------------------------------

The only other idea to find out whether a recent change in libisofs is
involved would be to test ISO production with GNU xorriso versions
older than 1.5.8.

The tarballs are available at

  http://ftpmirror.gnu.org/xorriso/

and don't have many dependencies. See their README for instructions.
(Obtaining older versions of libisofs and linking them with older
libisoburn is most probably more cumbersome than the all-in-one
tarballs of statically linked GNU xorriso. New libisoburn.so and
xorriso insist in new libisofs.so .)

At some degree of anciennity the old xorriso's might not be able
to do what is needed for a Fedora ISO. (Do you remember when xorriso
took over the job from mkisofs or genisoimage ? Back to that date
the versions should be capable enough.)


Have a nice day :)

Thomas

Comment 4 Kamil Páral (Red Hat) 2026-09-16 14:06:48 UTC
> So where can i download Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso (and what are its checksums) ?

https://kojipkgs.fedoraproject.org/compose/rawhide/Fedora-Rawhide-20260916.n.0/compose/Server/x86_64/iso/

Please note that composes get cleaned up after ~2 weeks.

Comment 5 Adam Williamson (Fedora) 2026-09-16 14:54:21 UTC
I already reported this days ago, but didn't notice any connection to the kernel - https://bugzilla.redhat.com/show_bug.cgi?id=2532536 . I was checking the packages on the image by using ls within the booted image, so it appeared as if they weren't present. If I instead mount the image on my own system using a 7.2.4 kernel, I can see the "missing" packages.

I guess I'll close that as a dupe of this.

Comment 6 Adam Williamson (Fedora) 2026-09-16 14:57:54 UTC
*** Bug 2532536 has been marked as a duplicate of this bug. ***

Comment 7 Adam Williamson (Fedora) 2026-09-16 15:07:31 UTC
So, let's keep track of what happened here.

The trigger is a kernel update. This bug started happening between Fedora-Rawhide-20260910.n.0 and Fedora-Rawhide-20260911.n.0 . libisoburn didn't change in that compose, nor anything else involved in *building* the image AFAICS. But the *kernel* did change: it went from kernel-7.3.0-0.rc0.260819gbd5f485f3f02.5.fc46 to kernel-7.3.0-0.rc2.24.fc46 . Now, let's look what changed there:

[adamw@toolbx fedora-toolbox-44 linux ((v7.3-rc2) %)]$ git log --oneline bd5f485f3f02.. fs/isofs/dir.c
b2eb2e288604 isofs: Drop support of directory entries straddling blocks
e2ee4078ec58 isofs: validate directory records consistently

So the thing that changed is, the kernel tightened its validation of ISO files. Clearly, our built-with-lorax-then-hacked-up-with-xorriso 'DVD' ISOs do not pass the checks. But we don't know *for how long* they have been not passing the checks, yet. We can't pin it on a specific version of libisoburn/xorriso, I don't think.

I'll run some tests with older ISO files I have lying around and see whether they all have issues with recent kernels.

Comment 8 Adam Williamson (Fedora) 2026-09-16 15:12:36 UTC
Specifically I think it's "isofs: Drop support of directory entries straddling blocks" - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b2eb2e28860423c428fcfdd8b2bc86915418042d - that we're failing, here. Our ISOs *could* have been violating that rule for years without anyone noticing until the kernel added the check, I guess. We could argue that the check is therefore wrong, or we could try and fix the ISOs, or both.

I don't think we can call this a blocker for Fedora *45* unless we intend to ship 45 with kernel 7.3. It's definitely a blocker for 46, though.

Comment 9 Lukáš Růžička (Fedora Quality) 2026-09-16 15:17:35 UTC
Yes, that's what I meant with "this looks related to the recent isofs change in Linux 7.3 which stopped
accepting ISO9660 directory entries crossing block boundaries." which a web search suggested, but I did not go very deep into this.

Also, I am changing the version to Rawhide as it is connected with the Rawhide kernel.

Comment 10 Thomas Schmitt 2026-09-16 17:05:10 UTC
Hi,

i cannot identify the reason for the Linux error message in the ISO,
which xorriso -indev loads without error indication.
We need a better error message from Linux.


Kamil Páral wrote:
> https://kojipkgs.fedoraproject.org/compose/rawhide/Fedora-Rawhide-20260916.n.0/compose/Server/x86_64/iso/

I got Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso
with the announced size and SHA256.

Deducing from "inode 9024" the byte offset of the directory record in
the ISO, i get to the '.' entry of a directory named "e" in block 141.
That block is tightly filled with 13 directory records which all look
ok to me and should pass the criteria in Linux isofs function
isofs_dir_record_valid() .

(I estimate the chance that the 13 records fill their block up to the
last byte as about 1:150. So this is rare enough to not happen in
small tests where less than 150 directory blocks existed. But manually
exercising the function does not look like it's a problem.)


So i can only ask the Linux developers to clearer state what's wrong
when isofs_dir_record_valid() decides to call a record invalid.
The error message from do_isofs_readdir() does not tell the
value of offset_saved in the call:
  if (!isofs_dir_record_valid(de, offset_saved, bufsize)) {
Actually it should not force me to guess the block number of the
directory's own record from the reported inode number.

So the error message should tell the block number in the filesystem
rather than the block number relative to the start of the inode's
data extent. Further it should tell the byte offset in that block.


I also think that i can prove the condition in the first if() of
isofs_dir_record_valid() to be redundant with the others in that
function:
        if (offset > bufsize || bufsize - offset < min_len)
                return false;

=======================================================================
TLDR;

The function which says that the directory entry is invalid
is of Jul 28, 2026
https://github.com/torvalds/linux/commit/e2ee4078ec58d97abcb720d566835ad0d107bc23
----------------------------------------------------------------------
isofs: validate directory records consistently

isofs_export_get_parent() assumes that the first two directory records
are valid "." and ".." entries.  A malformed image can provide an
invalid length for the first entry, causing the computed ".." offset to
point outside the received block.

Add a shared directory record validator and use it in NFS get_parent,
readdir and lookup.  This keeps the basic directory record length checks
consistent across all directory users before they consume the name field
or use one record length to find the next entry.

Signed-off-by: Yichong Chen <chenyichong>
Link: https://patch.msgid.link/20260728074349.417508-1-chenyichong@uniontech.com
Signed-off-by: Jan Kara <jack>
----------------------------------------------------------------------

The stated problem of an oversized first entry can hardly be caused
by an unaltered libisofs.

Whatever, the test is not only applied to the first directory record in
the data extent of a directory but to all records in there.

The directory's own directory record is probably the one at byte offset
9024*32 = 288768. There we could learn the block number of the data
extent (4-byte little endian at offset 2 in the directory record).
The bad directory record is then in block 0 of the data extent with
unknown offset.

(I.e. the error message is incomplete by not telling the value of
function parameter  offset_saved .)

-------------

Variables in isofs_dir_record_valid():

len       length of directory record (1 byte, offset 0)
name_len  length of name field (1 byte, offset 32)
min_len   33 (byte offset of name field)

offset    unknown, should be between 0 and 2047
bufsize   2048 (ISO 9660 block size)

-------------

Examining Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso :

At offset 9024*32 = 288768 = ISO 9660 block 141 is the '.' entry of
a directory named "e" (own named record at bye offset 229976).
I am somewhat puzzled that this shall be the parent of the bad
directory entry. But well, i understand the error message as saying
"somewhere in block 0 of the directory pointed to by inode 9024".
That would be 2048-byte block 141.

Directory records in that block are:

offset    len name_len  Result RockRidge-Name
---------------------------------------------
     0 :   96        1  true   .
    96 :   96        1  true   ..
   192 :  168       33  true   e2fsprogs-1...
   360 :  172       33  true   e2fsprogs-l...
   532 :  156       29  true   ed-...
   688 :  166       33  true   efibootmgr...
   854 :  166       33  true   efivar...
  1020 :  168       33  true   efi-filesystem...
  1188 :  168       33  true   efi-srpm...
  1356 :  166       33  true   elfutils-0.196-1.fc46.x86_64.rpm
  1522 :  184       33  true   elfutils_debug...
  1706 :  170       33  true   elfutils-libelf-0.196-1.fc46.i686.rpm
  1876 :  172       33  true   elfutils-libelf-0.196-1.fc46.x86_64.rpm
  2048

The record list fits ecactly into the block. That's quite rare.
So this might have not happened in kernel tests.

But i don't see how the numbers in above list could trigger the
conditions for returning false.

--------------------------------------------------------------------
Becoming bored while manually exercising the function i learned that

        if (offset > bufsize || bufsize - offset < min_len)
                return false;

is redundant with the other tests.

Its first condition (offset > bufsize) is redundant with the second:
If (offset > bufsize)
then (offset > bufsize - min_len)
which is equivalent to (bufsize - offset < min_len).

The other (bufsize - offset < min_len) is redundant with
(len > bufsize - offset) && !(len < min_len)
because then len >= min_len > bufsize - offset).
I.e. it is covered by

        if (len < min_len || name_len > len - min_len)
                return false;
        if (len > bufsize - offset)
                return false;

--------------------------------------------------------------------

Have a nice day :)

Thomas

Comment 11 Adam Williamson (Fedora) 2026-09-16 17:27:24 UTC
I'll email the author of these patches and ask if they can look at this and see if they think the test is valid and our ISO is bad, or the test needs revising.

Comment 12 Thomas Schmitt 2026-09-16 17:41:00 UTC
Hi,

wild theory:

The loop in fs/isofs/dir.c do_isofs_readdir() reads a new block
only after it has seen an (invalid) directory record with length 0,
which would normally be a gap filler at the end of the current block.

See
  https://github.com/torvalds/linux/blob/master/fs/isofs/dir.c
line 123
  if (de_len == 0) {
which causes
  bh = NULL
which causes in line 108
  bh = isofs_bread(inode, block);

The loop reaches in directory "e" of the problem ISO the offset value
2048 and thus reads over the end of the current block buffer.
If the first byte found there is not 0, then offset 2048 reaches the
call to isofs_dir_record_valid().

This triggers
  if (offset > bufsize || ...)
          return false;


-----------------------------------------------------------------------

In an old linux-source-4.19 i see between
                offset += de_len;
and the predecessor of
                if (!isofs_dir_record_valid(de, offset_saved, bufsize)) {
a code piece
                /* Make sure we have a full directory entry */
                if (offset >= bufsize) {
                     ...
                        block++;
                        brelse(bh);
                        bh = NULL;

which would read the next block.
It does not exist any more in torvalds/linux.

Do young kernels before july 2026 show all files in directory
  "$mount_point"/Packages/e
of the mounted ISO ?


Have a nice day :)

Thomas

Comment 13 Thomas Schmitt 2026-09-16 17:45:31 UTC
Hi,

the presentation of my theory needs mending.

The offset 2048 not be caught by isofs_dir_record_valid()

  if (offset > bufsize || ...

but by

  if ( ... || bufsize - offset < min_len)

which is with offset 2048:

  if ( ... || 2048 - 2048 < 33)


Have a nice day :)

Thomas

Comment 14 Adam Williamson (Fedora) 2026-09-16 17:47:26 UTC
The "/* Make sure we have a full directory entry */" block was dropped in the commit I identified, https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b2eb2e28860423c428fcfdd8b2bc86915418042d . We've already decided we're pretty sure one or both of these two commits is the issue, right? We know that kernels before those changes read the packages successfully.

Comment 15 Thomas Schmitt 2026-09-16 18:23:26 UTC
Hi,

Adam Williamson wrote:
> The "/* Make sure we have a full directory entry */" block was dropped in
> the commit I identified,
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b2eb2e28860423c428fcfdd8b2bc86915418042d

Ah yes. It seems that this one is misled.

I don't see the deleted lines as support for directory entries spanning
over block boundaries but rather as the provision for the rare case
that a part of the list of records exactly fits into a block.

The old ">=" was misleading. It should have been

  if (offset == bufsize) {

because that is the only specs compliant occasion where this block
reader is needed. (offset > bufsize) would be anti-specs.


> We've already decided we're pretty sure one or both of these two commits
> is the issue, right?

Yes. One commit causes the wrong offset and the other detects it.
What was needed to trigger the problem was a sufficiently large number
of files in the ISO to achieve a tightly filled directory block.


Have a nice day :)

Thomas

Comment 16 Adam Williamson (Fedora) 2026-09-16 19:00:40 UTC
Thanks. So, can you propose a fix for this?

Comment 17 Thomas Schmitt 2026-09-16 19:43:10 UTC
Hi,

Adam Williamson wrote:
> So, can you propose a fix for this?

If it shall keep the purpose which is told in the commit message,
then i propose to first revert commit
b2eb2e28860423c428fcfdd8b2bc86915418042d and for a beginning to just
change the two occasions where

  if (offset >= bufsize) {

gives the impression that block spanning directory records were
intentionally supported. (It wouldn't work anyways since the rest of
the code assumes the record to be entirely in the current buffer.)

Instead

  if (offset == bufsize) {

should cover only the legit case of a tightly filled directory block.
Any specs-violating offset would then be caught by
isofs_dir_record_valid().

This state of the code should be tested with
  Fedora-Server-dvd-x86_64-Rawhide-20260916.n.0.iso
and other ISOs which contain many files in directories with not less
than around 60 files each.
The size of the unused remainder of a directory block is quite random.
It can range from 255 to 0 bytes. A record can have 34 to 255 bytes,
depending on file name length and presence of SUSP entries for
Rock Ridge, zisofs, or AAIP.
Given the average length of a record i'd say that about 150 to 200
full directory blocks are needed to get one with a remainder of
0 bytes. YMMV.


From there on, one could try to regain any other benefits which the
commit b2eb2e28 possibly provides.

Catches by isofs_dir_record_valid() still need better error messages
unless their meaning shall be a secret to a handful of experts of whom
possibly more than one isn't alive any more.


Have a nice day :)

Thomas

Comment 18 Kamil Páral (Red Hat) 2026-09-17 06:40:56 UTC
(In reply to Adam Williamson (Fedora) from comment #8)
> I don't think we can call this a blocker for Fedora *45* unless we intend to
> ship 45 with kernel 7.3. It's definitely a blocker for 46, though.

Sorry, I assumed this also affects F45. Dropping the F45 proposal.

Comment 19 Thomas Schmitt 2026-09-17 11:14:58 UTC
Hi,

i made a script to create an ISO of 68 KiB size which contains a
tightly filled directory record.
It would be interesting to know whether this ISO causes an error
on kernel 7.3.0 when its directory "/e" is listed.

-----------------------------------------------------------------------

#!/bin/sh
# Tested with xorriso 1.5.4 to 1.5.9. Should work with older, too.

# Path to the xorriso binary
# xorriso="/home/me/test/xorriso-1.5.8/xorriso/xorriso"
# xorriso=/usr/bin/xorriso
xorriso=xorriso

# Path for emerging test ISO image
iso=/tmp/tight_dir_rec.iso

# A data file which becomes 12 files in the ISO
disk_file=/tmp/tight_dir_rec_file

if test -e "$iso"
then
  echo "$0 : ISO image '$iso' must not yet exist" >&2
  exit 1
fi
if test -e "$disk_file"
then
  echo "$0 : disk_file '$disk_file' must not yet exist" >&2
  exit 1
fi
echo dummy > "$disk_file"

"$xorriso" \
  -report_about warning \
  -outdev "$iso" \
  -map "$disk_file" /e/e2fsprogs-1.47.4-2.fc45.x86_64.rpm \
  -map "$disk_file" /e/e2fsprogs-libs-1.47.4-2.fc45.x86_64.rpm \
  -map "$disk_file" /e/ed-1.22.6-1.fc46.x86_64.rpm \
  -map "$disk_file" /e/efibootmgr-18-13.fc45.x86_64.rpm \
  -map "$disk_file" /e/efivar-libs-39-13.fc45.x86_64.rpm \
  -map "$disk_file" /e/efi-filesystem-6-9.fc45.noarch.rpm \
  -map "$disk_file" /e/efi-srpm-macros-6-9.fc45.noarch.rpm \
  -map "$disk_file" /e/elfutils-0.196-1.fc46.x86_64.rpm \
  -map "$disk_file" /e/elfutils-debuginfod-client-0.196-1.fc46.x86_64.rpm \
  -map "$disk_file" /e/elfutils-libelf-0.196-1.fc46.i686.rpm \
  -map "$disk_file" /e/elfutils-libelf-0.196-1.fc46.x86_64.rpm \
  -map "$disk_file" /e/zzz_for_next_directory_block \
  -padding 0 \
  -compliance no_emul_toc \
  -report_about note

rm "$disk_file"

-----------------------------------------------------------------------

Have a nice day :)

Thomas

Comment 20 Adam Williamson (Fedora) 2026-09-17 15:34:54 UTC
Thanks, I'll check that later. In the mean time, just an update on status: Justin reverted "isofs: Drop support of directory entries straddling blocks" in yesterday's kernel update - https://bodhi.fedoraproject.org/updates/FEDORA-2026-40c2c975b9 - which works around this issue for now (confirmed by the openQA results on today's Rawhide). I've mailed the various upstream folks mentioned on the two commits about this too, CCing Thomas and Justin.

Comment 21 Adam Williamson (Fedora) 2026-09-18 01:23:23 UTC
No, that ISO - built with xorriso 1.5.8 - does not seem to trigger the problem on a pre-revert kernel.

Comment 22 Thomas Schmitt 2026-09-18 08:08:08 UTC
Hi,

Adam Williamson wrote:
> No, that ISO - built with xorriso 1.5.8 - does not seem to trigger the
> problem on a pre-revert kernel.

Puzzling.

Can you open the files
   /e/elfutils-libelf-0.196-1.fc46.x86_64.rpm
   /e/zzz_for_next_directory_block
?

Do you get from

  dd if=/tmp/tight_dir_rec.iso bs=1 skip=42969 count=39 | od -c

these lines

  0000000   e   l   f   u   t   i   l   s   -   l   i   b   e   l   f   -
  0000020   0   .   1   9   6   -   1   .   f   c   4   6   .   x   8   6
  0000040   _   6   4   .   r   p   m

?
(I.e. the final "m" sitting at byte 43007, the last one of block 20.)


Have a nice day :)

Thomas

Comment 23 Thomas Schmitt 2026-09-18 10:04:15 UTC
Hi,

mentally stepping through the while-loop of do_isofs_readdir() in
fs/isofs/dir.c with directory "/e" of my test ISO i believe to see a
read operation one byte after the end of the read-in block buffer.

Namely in
  https://raw.githubusercontent.com/torvalds/linux/master/fs/isofs/dir.c
as of 18 Sep 2026 8:45 UTC
Line 115:

                de_len = *(unsigned char *)de;

This might yield 0 in my test ISO and non-zero in the Fedora ISO.
It might even depend on the whole work history of the running kernel.

If it yields 0, then offset is set to 0 and the next block gets loaded.
isofs_dir_record_valid() will not see offset 2048 and thus not raise
protest.

Preliminary this is my best excuse for not reproducing the problem with
the small ISO.

-----------------------------------------------------------------------
Reasoning:

I consider the situation when the last record of the tightly filled
directory block (LBA 20 in test ISO, 141 in Fedora ISO) is processed:

Line 134:
offset is 1876, de_len is 172, offset_saved is equal to offset 1872

                offset += de_len;

offset is afterwards 2048, i.e. 1 higher than permissible for the
currently processed block.
The test isofs_dir_record_valid() is done with offset_saved.
So it returns "true".

Thus processing goes on down to the end of the while-loop at line 223
from where it hops up to line 104 ("bh" is not NULL):

        while (ctx->pos < inode->i_size) {
                int de_len;

                if (!bh) {
                        bh = isofs_bread(inode, block);
                        if (!bh)
                                return 0;
                }

                de = (struct iso_directory_record *) (bh->b_data + offset);

I understand that bh->b_data is the block buffer of 2048 bytes.
So "de" gets pointed to the first byte after that buffer.
Next step is line 115:

                de_len = *(unsigned char *)de;

which - if it does not bonk against a memory guard - reads a byte of
hard-to-predict value.

-----------------------------------------------------------------------

I have to confess that i still do not fully understand the removed code
piece

                /* Make sure we have a full directory entry */
                if (offset >= bufsize) {
                        ...
                }

which really seems to enable processing of block spanning records.
At least i understand that it would have been triggered and then

                        offset &= bufsize - 1;

would have set offset to (2048 & 2047) = 0.


Without that removed code piece, handling of offset 2048 would probably 
have to sit higher in the while-loop.  
Like:

         while (ctx->pos < inode->i_size) {
                 int de_len;
 
                 if (!bh) {
                         bh = isofs_bread(inode, block);
                         if (!bh)
                                 return 0;
                 }
 
-                de = (struct iso_directory_record *) (bh->b_data + offset);
-
-                de_len = *(unsigned char *)de;
+                if (offset == bufsize) {
+                        /* bh is exhausted. Trigger loading of next block. */
+                        de_len = 0;
+                } else {
+                        de = (struct iso_directory_record *) (bh->b_data + offset);
+                        de_len = *(unsigned char *)de;
+                }

                 /*
                  * If the length byte is zero, we should move on to the next
                  * CDROM sector.  If we are at the end of the directory, we



Have a nice day :)

Thomas

Comment 24 Thomas Schmitt 2026-09-18 10:20:37 UTC
Hi,

... and to prevent the bad read operation in case of a bad ISO:

+                if (offset > bufsize) {
+                        printk(KERN_NOTICE "iso9660: Corrupted directory entry"
+                               " in block %lu of inode %llu"
+                               " yields offset %lu\n",
+                               block, inode->i_ino, offset);
+                        brelse(bh);
+                        return -EIO;
+                }

in line 113 before

+                if (offset == bufsize) {


Have a nice day :)

Thomas

Comment 25 Adam Williamson (Fedora) 2026-09-18 17:38:40 UTC
> Can you open the files
>    /e/elfutils-libelf-0.196-1.fc46.x86_64.rpm
>    /e/zzz_for_next_directory_block
> ?

Oh this is interesting! The first one, yes, but the second one, no. But it doesn't behave like the DVD ISOs exactly. On the DVD ISOs, the system is aware of the file on *some* level. It works for tab completion, for instance. And if you do `ls -R`, you get error messages for the affected files that include their name, and kernel error messages appear when you do this.

But with this ISO, it's not like that. The zz_for_next_directory_block file is just...*not there*, basically. Tab completion doesn't know about it. ls -R doesn't show it. Direct `ls /mnt/temp/e/zzz_for_next_directory_block` says "No such file or directory". None of these things cause the isofs error messages to appear in the journal. It's just as if the file simply didn't exist at all.

But if I boot a 7.2 kernel, or the reverted 7.3 kernel, the file is there and readable.

> Do you get from
> 
>   dd if=/tmp/tight_dir_rec.iso bs=1 skip=42969 count=39 | od -c
> 
> these lines

Yes I do.

Comment 26 Adam Williamson (Fedora) 2026-09-18 17:42:28 UTC
Sorry, when I say on the Server ISOs the filenames "work" for tab completion, what I mean is that I can do e.g. `ls /mnt/temp/Packages/l/elfutils-libs-<tab>` and it completes the filename correctly. But if I then hit enter, I get a "No such file or directory" error, and a "Corrupted directory entry" error in the journal.

On your test ISO, if I do `ls /mnt/temp/e/zzz_<tab>`, no completion occurs, and if I do `ls /mnt/temp/e/zzz_for_next_directory_block`, I get a "No such file or directory error" but *no* "Corrupted directory entry" error in the journal.

Comment 27 Thomas Schmitt 2026-09-18 18:36:10 UTC
Hi,

first:

My comment #24 is obsolete.

isofs_dir_record_valid() of the previous loop cycle would have detected

+                if (offset > bufsize) {

and would already have thrown EIO.
(But the code change in my comment #23 still seems to be the right
thing to do.)

-----------------------------------------------------------------------

I wrote:
> Can you open the files
>    /e/elfutils-libelf-0.196-1.fc46.x86_64.rpm
>    /e/zzz_for_next_directory_block
> ?

Adam Williamson wrote:
> Oh this is interesting!

That's what keeps me alive and busy. :))

> The first one, yes, but the second one, no. But it doesn't behave
> like the DVD ISOs exactly.i
> [... various weird effects ...]

I explain this currently by the semi-random results of line 115 in
fs/isofs/dir.c when offset is 2048:
   de_len = *(unsigned char *)de;

If the requested operations trigger repeated reading of the directory
data, then the results might differ and confuse the caller.


>  I get a "No such file or directory error" but *no* "Corrupted
> directory entry" error in the journal.

There is this other removed occasion of

               /* Make sure we have a full directory entry */
               if (offset >= bufsize) {

in fs/isofs/namei.c function isofs_find_entry().

Both while-loops seem to do the same, as far as iterating over the
records is concerned.
But  isofs_find_entry()  returns 0 instead of -EIO if
isofs_dir_record_valid() raises protest. So the errno of a failure is
chosen by some entity higher in the caller stack.

-----------------------------------------------------------------------
History musings:

I meanwhile believe to understand how the removed code indeed enabled
the processing of block-spanning records. But it might be that the
problem with an exactly fitting last record was the original reason
why this tolerance was implemented.

The way how (offset == bufsize) was handled in this code indicates
that it was deemed needy of special buffering, whereas this case
actually was in need of special handling around

                de = (struct iso_directory_record *) (bh->b_data + offset);

                de_len = *(unsigned char *)de;

as in my comment #23.


Have a nice day :)

Thomas


Note You need to log in before you can comment on or make changes to this bug.