Hi, sys/msdosfs is vulnerable to a division by zero when certain FAT file systems are mounted. This happens due to an overflow of an unsigned 8 bit value (sectors per cluster).
How to repeat (BEWARE, KERNEL WILL CRASH!!): # dd if=/dev/zero of=nuke.iso bs=1K count=100 # vnconfig vnd0c nuke.iso # newfs_msdos -S 1024 -c 128 vnd0c ### BEWARE, DON'T DO THIS! # mount -t msdos /dev/vnd0c /mnt So what happens? In msdosfs_vfsops, the sector size (-S) is divided by DEV_BSIZE (512) to retrieve "physical blocks in a sector". In this example it's 2. Next critical part is the amount of sectors per cluster (-c). This value is stored as an unsigned 8 bit in file system. In this example it's 128. Internally, the code seems to be quite dependent on the idea that sectors must be of block size, i.e. 512. So it "corrects" the amount of sectors in cluster (and many other values by the way) through a multiplication of physical blocks in a logical sector: 128 * 2 = 256 That is too large for unsigned 8 bit, the value flips to 0. All previous checks didn't spot it and eventually, that value is used in a division: Good bye. ;) My fix: The largest allowed sector size is 4096 bytes. A division by 512 can therefore be at worst 8. It's enough to increase SecPerClust to an unsigned 16 bit value. Also, don't accept file systems with sector sizes beyond 4096 bytes. By the way: The introduced cast is important because the header files state that "bpbSecPerClust" is signed... Tobias PS: Yes, this FAT file system as generated by newfs_msdos is invalid; a cluster shall never be larger than 655356 bytes. Yet, a kernel shouldn't crash on it. Index: msdosfs_vfsops.c =================================================================== RCS file: /cvs/src/sys/msdosfs/msdosfs_vfsops.c,v retrieving revision 1.66 diff -u -p -r1.66 msdosfs_vfsops.c --- msdosfs_vfsops.c 18 Jun 2014 17:24:46 -0000 1.66 +++ msdosfs_vfsops.c 20 Jun 2014 19:07:39 -0000 @@ -284,7 +284,7 @@ msdosfs_mountfs(struct vnode *devvp, str struct byte_bpb50 *b50; struct byte_bpb710 *b710; extern struct vnode *rootvp; - u_int8_t SecPerClust; + uint16_t SecPerClust; int ronly, error, bmapsiz; uint32_t fat_max_clusters; @@ -331,7 +331,7 @@ msdosfs_mountfs(struct vnode *devvp, str * bootsector. Copy in the dos 5 variant of the bpb then fix up * the fields that are different between dos 5 and dos 3.3. */ - SecPerClust = b50->bpbSecPerClust; + SecPerClust = (u_int8_t)b50->bpbSecPerClust; pmp->pm_BytesPerSec = getushort(b50->bpbBytesPerSec); pmp->pm_ResSectors = getushort(b50->bpbResSectors); pmp->pm_FATs = b50->bpbFATs; @@ -378,12 +378,13 @@ msdosfs_mountfs(struct vnode *devvp, str /* * More sanity checks: * MSDOSFS sectors per cluster: >0 && power of 2 - * MSDOSFS sector size: >= DEV_BSIZE && power of 2 + * MSDOSFS sector size: >= DEV_BSIZE && <= 4096 && power of 2 * HUGE sector count: >0 * FAT sectors: >0 */ if ((SecPerClust == 0) || (SecPerClust & (SecPerClust - 1)) || (pmp->pm_BytesPerSec < DEV_BSIZE) || + (pmp->pm_BytesPerSec > 4096) || (pmp->pm_BytesPerSec & (pmp->pm_BytesPerSec - 1)) || (pmp->pm_HugeSectors == 0) || (pmp->pm_FATsecs == 0)) { error = EINVAL;