diff --git a/sbin/fsck_ffs/main.c b/sbin/fsck_ffs/main.c index c160295d270..e5118619475 100644 --- a/sbin/fsck_ffs/main.c +++ b/sbin/fsck_ffs/main.c @@ -460,11 +460,13 @@ checkfilesys(char *filesys) if ((sblock.fs_metackhash & CK_CYLGRP) == 0 && reply("ADD CYLINDER GROUP CHECK-HASH PROTECTION") != 0) ckhashadd |= CK_CYLGRP; -#ifdef notyet if ((sblock.fs_metackhash & CK_SUPERBLOCK) == 0 && getosreldate() >= P_OSREL_CK_SUPERBLOCK && - reply("ADD SUPERBLOCK CHECK-HASH PROTECTION") != 0) - ckhashadd |= CK_SUPERBLOCK; + reply("ADD SUPERBLOCK CHECK-HASH PROTECTION") != 0) { + sblock.fs_metackhash |= CK_SUPERBLOCK; + sbdirty(); + } +#ifdef notyet if ((sblock.fs_metackhash & CK_INODE) == 0 && getosreldate() >= P_OSREL_CK_INODE && reply("ADD INODE CHECK-HASH PROTECTION") != 0) diff --git a/sbin/newfs/mkfs.c b/sbin/newfs/mkfs.c index 79fc627e5f7..74a0f9511a8 100644 --- a/sbin/newfs/mkfs.c +++ b/sbin/newfs/mkfs.c @@ -496,7 +496,9 @@ mkfs(struct partition *pp, char *fsys) if (Oflag > 1) { sblock.fs_flags |= FS_METACKHASH; if (getosreldate() >= P_OSREL_CK_CYLGRP) - sblock.fs_metackhash = CK_CYLGRP; + sblock.fs_metackhash |= CK_CYLGRP; + if (getosreldate() >= P_OSREL_CK_SUPERBLOCK) + sblock.fs_metackhash |= CK_SUPERBLOCK; } /* diff --git a/sys/sys/param.h b/sys/sys/param.h index 3d77aa945c1..2186dd37f42 100644 --- a/sys/sys/param.h +++ b/sys/sys/param.h @@ -88,6 +88,7 @@ #define P_OSREL_WRFSBASE 1200041 #define P_OSREL_CK_CYLGRP 1200046 #define P_OSREL_VMTOTAL64 1200054 +#define P_OSREL_CK_SUPERBLOCK 1300000 #define P_OSREL_MAJOR(x) ((x) / 100000) #endif diff --git a/sys/ufs/ffs/ffs_subr.c b/sys/ufs/ffs/ffs_subr.c index c7490568a76..de70a3f8428 100644 --- a/sys/ufs/ffs/ffs_subr.c +++ b/sys/ufs/ffs/ffs_subr.c @@ -45,6 +45,7 @@ __FBSDID("$FreeBSD$"); #include #include +uint32_t calculate_crc32c(uint32_t, const void *, size_t); struct malloc_type; #define UFS_MALLOC(size, type, flags) malloc(size) #define UFS_FREE(ptr, type) free(ptr) @@ -142,6 +143,7 @@ ffs_load_inode(struct buf *bp, struct inode *ip, struct fs *fs, ino_t ino) static off_t sblock_try[] = SBLOCKSEARCH; static int readsuper(void *, struct fs **, off_t, int, int (*)(void *, off_t, void **, int)); +static uint32_t calc_sbhash(struct fs *); /* * Read a superblock from the devfd device. @@ -252,7 +254,8 @@ readsuper(void *devfd, struct fs **fsp, off_t sblockloc, int isaltsblk, int (*readfunc)(void *devfd, off_t loc, void **bufp, int size)) { struct fs *fs; - int error; + int error, res; + uint32_t ckhash; error = (*readfunc)(devfd, sblockloc, (void **)fsp, SBLOCKSIZE); if (error != 0) @@ -267,7 +270,26 @@ readsuper(void *devfd, struct fs **fsp, off_t sblockloc, int isaltsblk, fs->fs_ncg >= 1 && fs->fs_bsize >= MINBSIZE && fs->fs_bsize <= MAXBSIZE && - fs->fs_bsize >= roundup(sizeof(struct fs), DEV_BSIZE)) { + fs->fs_bsize >= roundup(sizeof(struct fs), DEV_BSIZE) && + fs->fs_sbsize <= SBLOCKSIZE) { + if (fs->fs_ckhash != (ckhash = calc_sbhash(fs))) { +#ifdef _KERNEL + res = uprintf("Superblock check-hash failed: recorded " + "check-hash 0x%x != computed check-hash 0x%x\n", + fs->fs_ckhash, ckhash); +#else + res = 0; +#endif + /* + * Print check-hash failure if no controlling terminal + * in kernel or always if in user-mode (libufs). + */ + if (res == 0) + printf("Superblock check-hash failed: recorded " + "check-hash 0x%x != computed check-hash " + "0x%x\n", fs->fs_ckhash, ckhash); + return (EINVAL); + } /* Have to set for old filesystems that predate this field */ fs->fs_sblockactualloc = sblockloc; /* Not yet any summary information */ @@ -313,11 +335,48 @@ ffs_sbput(void *devfd, struct fs *fs, off_t loc, } fs->fs_fmod = 0; fs->fs_time = UFS_TIME; + fs->fs_ckhash = calc_sbhash(fs); if ((error = (*writefunc)(devfd, loc, fs, fs->fs_sbsize)) != 0) return (error); return (0); } +/* + * Calculate the check-hash for a superblock. + */ +static uint32_t +calc_sbhash(struct fs *fs) +{ + uint32_t ckhash, save_ckhash; + + /* + * A filesystem that was using a superblock ckhash may be moved + * to an older kernel that does not support ckhashes. The + * older kernel will clear the FS_METACKHASH flag indicating + * that it does not update hashes. When the disk is moved back + * to a kernel capable of ckhashes it disables them on mount: + * + * if ((fs->fs_flags & FS_METACKHASH) == 0) + * fs->fs_metackhash = 0; + * + * This leaves (fs->fs_metackhash & CK_SUPERBLOCK) == 0) with an + * old stale value in the fs->fs_ckhash field. Thus the need to + * just accept what is there. + */ + if ((fs->fs_metackhash & CK_SUPERBLOCK) == 0) + return (fs->fs_ckhash); + + save_ckhash = fs->fs_ckhash; + fs->fs_ckhash = 0; + /* + * If newly read from disk, the caller is responsible for + * verifying that fs->fs_sbsize <= SBLOCKSIZE. + */ + ckhash = calculate_crc32c(~0L, (void *)fs, fs->fs_sbsize); + fs->fs_ckhash = save_ckhash; + return (ckhash); +} + /* * Update the frsum fields to reflect addition or deletion * of some frags. diff --git a/sys/ufs/ffs/ffs_vfsops.c b/sys/ufs/ffs/ffs_vfsops.c index e3927327c79..b69dd20da11 100644 --- a/sys/ufs/ffs/ffs_vfsops.c +++ b/sys/ufs/ffs/ffs_vfsops.c @@ -814,7 +814,7 @@ ffs_mountfs(devvp, mp, td) if ((fs->fs_flags & FS_METACKHASH) == 0) fs->fs_metackhash = 0; /* none of these types of check-hashes are maintained by this kernel */ - fs->fs_metackhash &= ~(CK_SUPERBLOCK | CK_INODE | CK_INDIR | CK_DIR); + fs->fs_metackhash &= ~(CK_INODE | CK_INDIR | CK_DIR); /* no support for any undefined flags */ fs->fs_flags &= FS_SUPPORTED; fs->fs_flags &= ~FS_UNCLEAN; diff --git a/sys/ufs/ffs/fs.h b/sys/ufs/ffs/fs.h index 30925d72165..0560bc693af 100644 --- a/sys/ufs/ffs/fs.h +++ b/sys/ufs/ffs/fs.h @@ -364,7 +364,8 @@ struct fs { int32_t fs_save_cgsize; /* save real cg size to use fs_bsize */ ufs_time_t fs_mtime; /* Last mount or fsck time. */ int32_t fs_sujfree; /* SUJ free list */ - int32_t fs_sparecon32[22]; /* reserved for future constants */ + int32_t fs_sparecon32[21]; /* reserved for future constants */ + u_int32_t fs_ckhash; /* if CK_SUPERBLOCK, its check-hash */ u_int32_t fs_metackhash; /* metadata check-hash, see CK_ below */ int32_t fs_flags; /* see FS_ flags below */ int32_t fs_contigsumsize; /* size of cluster summary array */