tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Bug in fs/cd9660 raises questions about inode number computing



Hi,

i am exploring a bug in fs/cd9660.
A fix seems straightforward after i found its 32 bit rollover
trigger in the code.

But my exploration raises questions:

- Is the inode computation in sys/fs/cd9660/cd9660_node.c:isodirino()
  faulty (additionally to its rollover) ?

- Are the inode numbers of cd9660 too 64-bit-happy ?

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

The bug shows up when mounting a multi-session ISO 9660 medium
which has its directory tree above 4 GiB. (Not possible with CD,
but with DVD or BD.)

  netbsd# mount -t cd9660 /dev/cd0a /mnt
  netbsd# ls -l /mnt
  -r-xr-xr-x  1 root  wheel  0 Jan  1  1970 /mnt
  netbsd# umount /mnt
  umount: /mnt: Invalid argument
  netbsd# umount /dev/cd0a
  umount: /mnt: Invalid argument
  netbsd# mount
  /dev/wd0a on / type ffs (local)
  kernfs on /kern type kernfs (local)
  ptyfs on /dev/pts type ptyfs (local)
  procfs on /proc type procfs (local)
  /dev/cd0a on /mnt type cd9660 (read-only, local)

I can get rid of the mount point only by reboot.

This does not happen with

  mount -t cd9660 -o norrip /dev/cd0a /mnt

making it clear that the bug sits in the Rock Ridge
interpretation. I did not find this spot yet.

But looking for possible 32 bit byte address rollovers i found
the initiating culprit in sys/fs/cd9660/cd9660_node.c :

  ino_t
  isodirino(struct iso_directory_record *isodir, struct iso_mnt *imp)
  {
        ino_t ino;

        ino = (isonum_733(isodir->extent) + isonum_711(isodir->ext_attr_length))
              << imp->im_bshift;
        return (ino);
  }

Although sizeof(ino_t) == 8, this rolls over at least on NetBSD i386
because none of the operands of the computation is 64 bit wide.

A test in userspace confirms:

  sizeof(ino_t) = 8
  ( 2113952 + 0 ) << 11 = 34406400
  ( ((uint64_t) 2113952) + 0 ) << 11 = 4329373696

Fixing the rollover makes the ISO mountable and umountable:

     ino = (((uint64_t) isonum_733(isodir->extent)) +
            isonum_711(isodir->ext_attr_length)) << imp->im_bshift;

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

The rollover seems not to be the only cause for the bad mount.
It would probably be harmless if not other inode computations
in cd9660 would have a 64 bit operand in their own expressions.

I suspect this causes inode numbers to be computed differently
on the same inode. Rock Ridge code obviously takes offense.

At least i can trigger the same problem without rollover just
by dividing the inode numbers of some computations by 32.
(This would still keep them unique because a directory record
 has at least 34 bytes. Idea learned from Linux iso9660.)

I tried to push the 32 bit limit to 128 TiB by this division,
because ls(1) option -i shows rolled over 32 bit numbers.
Probably other consumers of stat(2) don't expect true 64 bit
either.
I will try to consolidate all inode computations by a single macro
and then look whether it runs with division by 32.

And in general, what strange formula is used in isodirino() ?

My comment in my copy of NetBSD 6.99.40 says:

   This looks strange. Why add a byte offset to the block address
   and then multiply the sum by block size ?
        ino = (isonum_733(isodir->extent) +
               isonum_711(isodir->ext_attr_length)) << imp->im_bshift;

   One could fear that (isodir->extent << imp->im_bshift) coincides
   with the first directory entry which is not ".". So adding any
   number between 1 and 33 after multiplication/shifting would
   prevent the result from coinciding with the first or second entry.
   Well, ext_attr_length is normally 0 and "." is implicitely specified
   to be the first entry in a directory (by ECMA-119 6.8.2.2 and 9.3).

   The other inode computations look more plausible
     dbtob(bp->b_blkno) + entryoffsetinblock

So i changed the formula in isodirino() to what the others do.
It seems to work properly. Actually trivially because ext_attr_length
is 0 in my test ISO.

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

Because this mail is not yet long enough:

I am the upstream developer of libburn and libisofs.
NetBSD user since a few weeks by a virtual 6.1.3-i386 (with a
6.99.40 kernel meanwhile).
No kernel background, but ISO 9660 and SCSI/MMC for CD burners.

I recently ported libburn to NetBSD. My daring test user and
my friendly pkgsrc packager kept me interested in NetBSD cd9660
although i myself cannot get installed NetBSD on my real hardware
and thus have no real DVD drive to play with. Only qemu CD-ROM.

More proposals are to come, if you do not chase me away:

- Files larger than 4 GiB are misrepresented after mount_cd9660.

- mount_cd9660(8) lacks an option equivalent to FreeBSD's
  mount_cd9660 -s.

- Selfishly i could need ioctl(SCIOCCOMMAND) with ld(4) populated
  by virtio(4) so that i get a real DVD burner forwarded into my
  NetBSD. (virtio /dev/ld0d on DVD+RW writes with 16 KB/s. Ugh.)
  
---------------------------------------------------------------

Have a nice day :)

Thomas



Home | Main Index | Thread Index | Old Index