--- a/sys/vfs/ufs/ffs_alloc.c +++ b/sys/vfs/ufs/ffs_alloc.c @@ -80,6 +80,7 @@ static ino_t ffs_nodealloccg (struct inode *, int, ufs_daddr_t, int); static ufs_daddr_t ffs_mapsearch (struct fs *, struct cg *, ufs_daddr_t, int); +static int ffs_chkcg (struct fs *, struct cg *, int); /* * Allocate a block in the filesystem. @@ -872,6 +873,16 @@ fs = ip->i_fs; /* + * The preferred cg derives from on-disk data (block preferences + * come from inode block pointers) and is used by the allocators + * to index fs_csp[] (fs_cs()) before anything else; clamp it. + */ + if (cg < 0 || cg >= fs->fs_ncg) { + cg = ino_to_cg(fs, ip->i_number); + if (cg < 0 || cg >= fs->fs_ncg) + cg = 0; + } + /* * 1: preferred cylinder group */ result = (*allocator)(ip, cg, pref, size); @@ -943,6 +954,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } cgp->cg_time = time_second; bno = dtogd(fs, bprev); blksfree = cg_blksfree(cgp); @@ -1021,6 +1036,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } cgp->cg_time = time_second; if (size == fs->fs_bsize) { bno = ffs_alloccgblk(ip, bp, bpref); @@ -1249,6 +1268,8 @@ cgp = (struct cg *)bp->b_data; if (!cg_chkmagic(cgp)) goto fail; + if (!ffs_chkcg(fs, cgp, cg)) + goto fail; /* * Check to see if a cluster of the needed size (or bigger) is @@ -1380,6 +1401,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } inosused = cg_inosused(cgp); icheckmiss = 0; @@ -1527,6 +1552,10 @@ brelse(bp); return; } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return; + } cgp->cg_time = time_second; bno = dtogd(fs, bno); blksfree = cg_blksfree(cgp); @@ -1787,6 +1816,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } cgp->cg_time = time_second; inosused = cg_inosused(cgp); ino %= fs->fs_ipg; @@ -1876,6 +1909,65 @@ } /* + * Validate an on-disk cylinder group header before any of its fields are + * used as array indexes or byte offsets into the cg buffer. Previously + * only cg_chkmagic() (a magic comparison) was applied, so a crafted + * filesystem image could turn cg_freeoff/cg_iusedoff/cg_boff/cg_btotoff/ + * cg_clustersumoff/cg_clusteroff, cg_rotor/cg_frotor/cg_irotor, cg_cgx, + * or cg_nclusterblks into out-of-bounds kernel reads and writes. + * + * Historic 4.2BSD ocg-format groups keep all maps inline in the header + * (no offsets), so they are accepted as before. + */ +static int +ffs_chkcg(struct fs *fs, struct cg *cgp, int cg) +{ + int64_t end; + + if (cgp->cg_magic != CG_MAGIC) /* historic ocg layout */ + return (1); + if (cgp->cg_cgx != cg) + return (0); + if (cgp->cg_rotor < 0 || cgp->cg_rotor >= fs->fs_fpg || + cgp->cg_frotor < 0 || cgp->cg_frotor >= fs->fs_fpg || + cgp->cg_irotor < 0 || cgp->cg_irotor >= fs->fs_ipg) + return (0); + if (cgp->cg_btotoff < offsetof(struct cg, cg_space) || + cgp->cg_boff < cgp->cg_btotoff || + cgp->cg_iusedoff < cgp->cg_boff || + cgp->cg_freeoff < cgp->cg_iusedoff) + return (0); + end = (int64_t)cgp->cg_btotoff + fs->fs_cpg * sizeof(int32_t); + if (end > cgp->cg_boff) + return (0); + end = (int64_t)cgp->cg_boff + fs->fs_cpg * fs->fs_nrpos * sizeof(int16_t); + if (end > cgp->cg_iusedoff) + return (0); + end = (int64_t)cgp->cg_iusedoff + howmany(fs->fs_ipg, NBBY); + if (end > cgp->cg_freeoff) + return (0); + end = (int64_t)cgp->cg_freeoff + howmany(fs->fs_fpg, NBBY); + if (end > fs->fs_cgsize) + return (0); + if (fs->fs_contigsumsize > 0) { + if (cgp->cg_nclusterblks < 0 || + cgp->cg_nclusterblks > fragstoblks(fs, fs->fs_fpg)) + return (0); + if (cgp->cg_clusteroff < cgp->cg_clustersumoff) + return (0); + end = (int64_t)cgp->cg_clustersumoff + + (fs->fs_contigsumsize + 1) * sizeof(int32_t); + if (end > cgp->cg_clusteroff) + return (0); + end = (int64_t)cgp->cg_clusteroff + + howmany(cgp->cg_nclusterblks, NBBY); + if (end > fs->fs_cgsize) + return (0); + } + return (1); +} + +/* * Update the cluster map because of an allocation or free. * * Cnt == 1 means free; cnt == -1 means allocating.