From ce1bca43e9a141e69ca544ff1cc913398d3bcfbb Mon Sep 17 00:00:00 2001 From: mckusick Date: Tue, 23 Oct 2018 21:10:06 +0000 Subject: [PATCH] Continuing efforts to provide hardening of FFS, this change adds a check hash to the superblock. If a check hash fails when an attempt is made to mount a filesystem, the mount fails with EINVAL (Invalid argument). This avoids a class of filesystem panics related to corrupted superblocks. The hash is done using crc32c. Check hases are added only to UFS2 and not to UFS1 as UFS1 is primarily used in embedded systems with small memories and low-powered processors which need as light-weight a filesystem as possible. Reviewed by: kib Tested by: Peter Holm Sponsored by: Netflix --- sbin/fsck_ffs/main.c | 8 +++-- sbin/newfs/mkfs.c | 4 ++- sys/sys/param.h | 1 + sys/ufs/ffs/ffs_subr.c | 63 ++++++++++++++++++++++++++++++++++++++-- sys/ufs/ffs/ffs_vfsops.c | 2 +- sys/ufs/ffs/fs.h | 3 +- 6 files changed, 73 insertions(+), 8 deletions(-) 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 */ -- 2.45.0