git: 0728695c63ef - main - fs/msdosfs: Fix potential panic and size calculations

From: Stefan Eßer <se_at_FreeBSD.org>
Date: Tue, 25 Apr 2023 08:00:36 UTC
The branch main has been updated by se:

URL: https://cgit.FreeBSD.org/src/commit/?id=0728695c63efda298feccefb3615c23cb6682929

commit 0728695c63efda298feccefb3615c23cb6682929
Author:     Stefan Eßer <se@FreeBSD.org>
AuthorDate: 2023-04-25 06:35:16 +0000
Commit:     Stefan Eßer <se@FreeBSD.org>
CommitDate: 2023-04-25 07:58:29 +0000

    fs/msdosfs: Fix potential panic and size calculations
    
    Some combinations of FAT12 file system parameters could cause a kernel
    panic due to an unmapped access if the size of the FAT was larger than
    the CPU page size. The reason is that FAT12 uses 3 bytes to store
    2 FAT pointers, leading to partial FAT pointers at the end of buffers
    of a size that is not a multiple of 3.
    
    With a typical page size of 4 KB, this caused the FAT entry at byte
    offsets 4095 and 4096 to cross the page boundary, with only the first
    page mapped. This was fixed by adjusting the mapping to always cover
    both bytes of each FAT entry.
    
    Testing revealed 2 other inconsistencies that are fixed by this commit:
    
    1) The calculation of the size of the data area did not take into
       account the fact that the first two data block numbers are reserved
       and that the data area starts with block 2. This could cause a
       FAT12 file system created with the maximum supported number of
       blocks to be incorrectly identified as FAT16.
    
    2) The root directory does not take up space in the data area of a
       FAT12 or FAT16 file system, since it is placed into a reserved
       area outside of that data area. This commits makes stat() report
       the logical size of the root directory, but with 0 blocks allocated
       from the data area.
    
    PR:             270587
    Reviewed by:    mckusick
    Differential Revision:  https://reviews.freebsd.org/D39386
---
 sys/fs/msdosfs/msdosfs_fat.c    | 12 +++++++++---
 sys/fs/msdosfs/msdosfs_vfsops.c | 15 +++++++++------
 sys/fs/msdosfs/msdosfs_vnops.c  |  7 +++++--
 3 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/sys/fs/msdosfs/msdosfs_fat.c b/sys/fs/msdosfs/msdosfs_fat.c
index e6d9b671e7d7..2d50d30f33b3 100644
--- a/sys/fs/msdosfs/msdosfs_fat.c
+++ b/sys/fs/msdosfs/msdosfs_fat.c
@@ -89,11 +89,17 @@ static void
 fatblock(struct msdosfsmount *pmp, u_long ofs, u_long *bnp, u_long *sizep,
     u_long *bop)
 {
-	u_long bn, size;
+	u_long bn, size, fatblocksec;
 
+	fatblocksec = pmp->pm_fatblocksec;
+	if (FAT12(pmp) && fatblocksec % 3 != 0) {
+		fatblocksec *= 3;
+		if (fatblocksec % 6 == 0)
+			fatblocksec /= 2;
+	}
 	bn = ofs / pmp->pm_fatblocksize * pmp->pm_fatblocksec;
-	size = min(pmp->pm_fatblocksec, pmp->pm_FATsecs - bn)
-	    * DEV_BSIZE;
+	size = roundup(min(fatblocksec, pmp->pm_FATsecs - bn) * DEV_BSIZE,
+	    pmp->pm_BlkPerSec * DEV_BSIZE);
 	bn += pmp->pm_fatblk + pmp->pm_curfat * pmp->pm_FATsecs;
 
 	if (bnp)
diff --git a/sys/fs/msdosfs/msdosfs_vfsops.c b/sys/fs/msdosfs/msdosfs_vfsops.c
index e2b1fd6b91f5..87e150e0e18a 100644
--- a/sys/fs/msdosfs/msdosfs_vfsops.c
+++ b/sys/fs/msdosfs/msdosfs_vfsops.c
@@ -700,11 +700,14 @@ mountmsdosfs(struct vnode *odevvp, struct mount *mp)
 	}
 	pmp->pm_maxcluster = (pmp->pm_HugeSectors - pmp->pm_firstcluster) /
 	    SecPerClust + 1;
-	pmp->pm_fatsize = pmp->pm_FATsecs * DEV_BSIZE;	/* XXX not used? */
+	pmp->pm_fatsize = pmp->pm_FATsecs * DEV_BSIZE;
 
 	if (pmp->pm_fatmask == 0) {
-		if (pmp->pm_maxcluster <= ((CLUST_RSRVD - CLUST_FIRST) &
-		    FAT12_MASK)) {
+		/*
+		 * The last 10 (or 16?) clusters are reserved and must not
+		 * be allocated for data.
+		 */
+		if (pmp->pm_maxcluster < (CLUST_RSRVD & FAT12_MASK)) {
 			/*
 			 * This will usually be a floppy disk. This size makes
 			 * sure that one FAT entry will not be split across
@@ -720,11 +723,11 @@ mountmsdosfs(struct vnode *odevvp, struct mount *mp)
 		}
 	}
 
-	clusters = (pmp->pm_fatsize / pmp->pm_fatmult) * pmp->pm_fatdiv;
+	clusters = (pmp->pm_fatsize / pmp->pm_fatmult) * pmp->pm_fatdiv ;
 	if (pmp->pm_maxcluster >= clusters) {
 #ifdef MSDOSFS_DEBUG
 		printf("Warning: number of clusters (%ld) exceeds FAT "
-		    "capacity (%ld)\n", pmp->pm_maxcluster + 1, clusters);
+		    "capacity (%ld)\n", pmp->pm_maxcluster - 1, clusters);
 #endif
 		pmp->pm_maxcluster = clusters - 1;
 	}
@@ -1045,7 +1048,7 @@ msdosfs_statfs(struct mount *mp, struct statfs *sbp)
 	pmp = VFSTOMSDOSFS(mp);
 	sbp->f_bsize = pmp->pm_bpcluster;
 	sbp->f_iosize = pmp->pm_bpcluster;
-	sbp->f_blocks = pmp->pm_maxcluster + 1;
+	sbp->f_blocks = pmp->pm_maxcluster - CLUST_FIRST + 1;
 	sbp->f_bfree = pmp->pm_freeclustercount;
 	sbp->f_bavail = pmp->pm_freeclustercount;
 	sbp->f_files =	howmany(pmp->pm_rootdirsize * DEV_BSIZE,
diff --git a/sys/fs/msdosfs/msdosfs_vnops.c b/sys/fs/msdosfs/msdosfs_vnops.c
index f095a4abea62..bc47f9dde574 100644
--- a/sys/fs/msdosfs/msdosfs_vnops.c
+++ b/sys/fs/msdosfs/msdosfs_vnops.c
@@ -313,8 +313,11 @@ msdosfs_getattr(struct vop_getattr_args *ap)
 		vap->va_flags |= UF_SYSTEM;
 	vap->va_gen = 0;
 	vap->va_blocksize = pmp->pm_bpcluster;
-	vap->va_bytes =
-	    (dep->de_FileSize + pmp->pm_crbomask) & ~pmp->pm_crbomask;
+	if (dep->de_StartCluster != MSDOSFSROOT)
+		vap->va_bytes =
+		    (dep->de_FileSize + pmp->pm_crbomask) & ~pmp->pm_crbomask;
+	else
+		vap->va_bytes = 0; /* FAT12/FAT16 root dir in reserved area */
 	vap->va_type = ap->a_vp->v_type;
 	vap->va_filerev = dep->de_modrev;
 	return (0);