Illegal fragmentation block sizes can trigger division by zero in the
disklabel and fsck_ffs tools.
See this sequence of commands to reproduce:
# dd if=/dev/zero of=nofrag.iso bs=1M count=1
# vnconfig vnd0 nofrag.iso
# disklabel -e vnd0 # create 'a' and set fsize = bsize = 1
# fsck_ffs vnd0a
** /dev/vnd0a (vnd0a)
BAD SUPER BLOCK: MAGIC NUMBER WRONG
Floating point exception (core dumped)
# disklabel -E vnd0
Label editor (enter '?' for help at any prompt)
> m a
offset: [0]
size: [2048]
FS type: [4.2BSD]
Floating point exception (core dumped)
# vnconfig -u vnd0
A fragmentation (block) size smaller than a sector size is not valid
while using "disklabel -E", and really doesn't make sense. While
using "disklabel -e", not all validation checks are performed, which
makes it possible to enter invalid values.
If "disklabel -E" is used without the expert mode, fragmentation sizes
cannot be changed and will be just accepted from the parsed disklabel,
resulting in a division by zero if they are too small.
And the same happens in fsck_ffs. Instead of coming up with a guessed
value in fsck_ffs, I think it's better to simply fail and let the user
fix the disklabel. After all, it shouldn't be fsck_ffs's duty to fix
faulty values outside the filesystem.
Index: sbin/disklabel/disklabel.c
===================================================================
RCS file: /cvs/src/sbin/disklabel/disklabel.c,v
retrieving revision 1.222
diff -u -p -u -p -r1.222 disklabel.c
--- sbin/disklabel/disklabel.c 19 Jun 2016 13:42:56 -0000 1.222
+++ sbin/disklabel/disklabel.c 27 Aug 2016 13:30:38 -0000
@@ -1096,9 +1096,24 @@ getasciilabel(FILE *f, struct disklabel
case FS_BSDFFS:
NXTNUM(fsize, fsize, &errstr);
- if (fsize == 0)
+ if (fsize < lp->d_secsize ||
+ (fsize % lp->d_secsize) != 0) {
+ warnx("line %d: "
+ "bad fragmentation size: %s",
+ lineno, cp);
+ errors++;
break;
+ }
NXTNUM(v, v, &errstr);
+ if (v < fsize || (fsize != v &&
+ fsize * 2 != v && fsize * 4 != v &&
+ fsize * 8 != v)) {
+ warnx("line %d: "
+ "bad block size: %s",
+ lineno, cp);
+ errors++;
+ break;
+ }
pp->p_fragblock =
DISKLABELV1_FFS_FRAGBLOCK(fsize, v / fsize);
NXTNUM(pp->p_cpg, pp->p_cpg, &errstr);
Index: sbin/disklabel/editor.c
===================================================================
RCS file: /cvs/src/sbin/disklabel/editor.c,v
retrieving revision 1.301
diff -u -p -u -p -r1.301 editor.c
--- sbin/disklabel/editor.c 19 Aug 2016 08:06:25 -0000 1.301
+++ sbin/disklabel/editor.c 27 Aug 2016 13:30:39 -0000
@@ -2024,16 +2024,16 @@ get_bsize(struct disklabel *lp, int part
if (pp->p_fstype != FS_BSDFFS)
return (0);
+ frag = DISKLABELV1_FFS_FRAG(pp->p_fragblock);
+ fsize = DISKLABELV1_FFS_FSIZE(pp->p_fragblock);
+
/* Avoid dividing by zero... */
- if (pp->p_fragblock == 0)
- return(1);
+ if (frag * fsize < lp->d_secsize)
+ return (1);
if (!expert)
goto align;
- fsize = DISKLABELV1_FFS_FSIZE(pp->p_fragblock);
- frag = DISKLABELV1_FFS_FRAG(pp->p_fragblock);
-
for (;;) {
ui = getuint64(lp, "block size",
"Size of ffs blocks. 1, 2, 4 or 8 times ffs fragment size.",
@@ -2074,8 +2074,7 @@ align:
orig_size = DL_GETPSIZE(pp);
orig_offset = DL_GETPOFFSET(pp);
- bsize = (DISKLABELV1_FFS_FRAG(pp->p_fragblock) *
- DISKLABELV1_FFS_FSIZE(pp->p_fragblock)) / lp->d_secsize;
+ bsize = (frag * fsize) / lp->d_secsize;
if (DL_GETPOFFSET(pp) != starting_sector) {
/* Can't change offset of first partition. */
adj = bsize - (DL_GETPOFFSET(pp) % bsize);
Index: sbin/fsck_ffs/setup.c
===================================================================
RCS file: /cvs/src/sbin/fsck_ffs/setup.c,v
retrieving revision 1.61
diff -u -p -u -p -r1.61 setup.c
--- sbin/fsck_ffs/setup.c 20 Aug 2016 15:04:21 -0000 1.61
+++ sbin/fsck_ffs/setup.c 27 Aug 2016 13:30:39 -0000
@@ -625,6 +625,10 @@ calcsb(char *dev, int devfd, struct fs *
fs->fs_fsize = DISKLABELV1_FFS_FSIZE(pp->p_fragblock);
fs->fs_frag = DISKLABELV1_FFS_FRAG(pp->p_fragblock);
fs->fs_bsize = fs->fs_fsize * fs->fs_frag;
+ if (fs->bsize < lp->d_secsize) {
+ pfatal("%s: INVALID BLOCK FRAGMENTATION SIZE\n", dev);
+ return (0);
+ }
fs->fs_cpg = pp->p_cpg;
fs->fs_nspf = DL_SECTOBLK(lp, fs->fs_fsize / lp->d_secsize);
/*