--- 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.
