lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Fri, 01 Aug 2014 11:12:34 -0700
From:	"Darrick J. Wong" <darrick.wong@...cle.com>
To:	tytso@....edu, darrick.wong@...cle.com
Cc:	linux-ext4@...r.kernel.org,
	"Darrick J. Wong" <darrick.wong@...le.com>
Subject: [PATCH 08/19] e2fsck: offer to clear inode table blocks that are
 insane

From: Darrick J. Wong <darrick.wong@...cle.com>

Add a new behavior flag to the inode scan functions; when specified,
this flag will do some simple sanity checking of entire inode table
blocks.  If all the checksums are ok, we can skip checksum
verification on individual inodes later on.  If more than half of the
inodes look "insane" (bad extent tree root or checksum failure) then
ext2fs_get_next_inode_full() can return a special status code
indicating that what's in the buffer is probably garbage.

When e2fsck' inode scan encounters the 'inode is garbage' return code
it'll offer to zap the inode straightaway instead of trying to recover
anything.  This replaces the previous behavior of asking to zap
anything with a checksum error (strict_csum).

Signed-off-by: Darrick J. Wong <darrick.wong@...le.com>
---
 e2fsck/pass1.c            |   18 ++++--
 e2fsck/problem.c          |    8 +--
 e2fsck/problem.h          |    4 +
 lib/ext2fs/ext2_err.et.in |    3 +
 lib/ext2fs/ext2fs.h       |    1 
 lib/ext2fs/inode.c        |  137 ++++++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 154 insertions(+), 17 deletions(-)

diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index 283b0b1..a7792c4 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -809,7 +809,8 @@ void e2fsck_pass1(e2fsck_t ctx)
 		ctx->flags |= E2F_FLAG_ABORT;
 		goto endit;
 	}
-	ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE, 0);
+	ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE |
+				      EXT2_SF_WARN_GARBAGE_INODES, 0);
 	ctx->stashed_inode = inode;
 	scan_struct.ctx = ctx;
 	scan_struct.block_buf = block_buf;
@@ -851,7 +852,8 @@ void e2fsck_pass1(e2fsck_t ctx)
 			continue;
 		}
 		if (pctx.errcode &&
-		    pctx.errcode != EXT2_ET_INODE_CSUM_INVALID) {
+		    pctx.errcode != EXT2_ET_INODE_CSUM_INVALID &&
+		    pctx.errcode != EXT2_ET_INODE_IS_GARBAGE) {
 			fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx);
 			ctx->flags |= E2F_FLAG_ABORT;
 			goto endit;
@@ -862,12 +864,14 @@ void e2fsck_pass1(e2fsck_t ctx)
 		pctx.inode = inode;
 		ctx->stashed_ino = ino;
 
-		/* Clear corrupt inode? */
-		if (pctx.errcode == EXT2_ET_INODE_CSUM_INVALID) {
-			if (fix_problem(ctx, PR_1_INODE_CSUM_INVALID, &pctx))
-				goto clear_inode;
-			failed_csum = 1;
+		/* Clear trashed inode? */
+		if (pctx.errcode == EXT2_ET_INODE_IS_GARBAGE &&
+		    inode->i_links_count > 0 &&
+		    fix_problem(ctx, PR_1_INODE_IS_GARBAGE, &pctx)) {
+			pctx.errcode = 0;
+			e2fsck_clear_inode(ctx, ino, inode, 0, "pass1");
 		}
+		failed_csum = pctx.errcode != 0;
 
 		if (inode->i_links_count) {
 			pctx.errcode = ext2fs_icount_store(ctx->inode_link_info,
diff --git a/e2fsck/problem.c b/e2fsck/problem.c
index 80ca3a0..452137a 100644
--- a/e2fsck/problem.c
+++ b/e2fsck/problem.c
@@ -967,10 +967,10 @@ static struct e2fsck_problem problem_table[] = {
 	  N_("@i %i has zero length extent\n\t(@n logical @b %c, physical @b %b)\n"),
 	  PROMPT_CLEAR, 0 },
 
-	/* inode checksum does not match inode */
-	{ PR_1_INODE_CSUM_INVALID,
-	  N_("@i %i checksum does not match @i.  "),
-	  PROMPT_CLEAR, PR_PREEN_OK },
+	/* inode seems to contain garbage */
+	{ PR_1_INODE_IS_GARBAGE,
+	  N_("@i %i seems to contain garbage.  "),
+	  PROMPT_CLEAR, 0 },
 
 	/* inode passes checks, but checksum does not match inode */
 	{ PR_1_INODE_ONLY_CSUM_INVALID,
diff --git a/e2fsck/problem.h b/e2fsck/problem.h
index 4813fc6..72cfc4d 100644
--- a/e2fsck/problem.h
+++ b/e2fsck/problem.h
@@ -571,8 +571,8 @@ struct problem_context {
 /* Extent has zero length */
 #define PR_1_EXTENT_LENGTH_ZERO		0x010066
 
-/* inode checksum does not match inode */
-#define PR_1_INODE_CSUM_INVALID                0x010067
+/* inode seems to contain garbage */
+#define PR_1_INODE_IS_GARBAGE		0x010067
 
 /* inode passes checks, but checksum does not match inode */
 #define PR_1_INODE_ONLY_CSUM_INVALID   0x010068
diff --git a/lib/ext2fs/ext2_err.et.in b/lib/ext2fs/ext2_err.et.in
index 51c88d0..2194a18 100644
--- a/lib/ext2fs/ext2_err.et.in
+++ b/lib/ext2fs/ext2_err.et.in
@@ -515,4 +515,7 @@ ec	EXT2_ET_INLINE_DATA_NO_SPACE,
 ec	EXT2_ET_MAGIC_EA_HANDLE,
 	"Wrong magic number for extended attribute structure"
 
+ec	EXT2_ET_INODE_IS_GARBAGE,
+	"Inode seems to contain garbage"
+
 	end
diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index 7812956..b4a9f84 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -462,6 +462,7 @@ typedef struct ext2_struct_inode_scan *ext2_inode_scan;
 #define EXT2_SF_BAD_EXTRA_BYTES	0x0004
 #define EXT2_SF_SKIP_MISSING_ITABLE	0x0008
 #define EXT2_SF_DO_LAZY		0x0010
+#define EXT2_SF_WARN_GARBAGE_INODES	0x0020
 
 /*
  * ext2fs_check_if_mounted flags
diff --git a/lib/ext2fs/inode.c b/lib/ext2fs/inode.c
index 0cea9f0..08024cb 100644
--- a/lib/ext2fs/inode.c
+++ b/lib/ext2fs/inode.c
@@ -30,6 +30,10 @@
 #include "ext2fsP.h"
 #include "e2image.h"
 
+#define IBLOCK_STATUS_CSUMS_OK	1
+#define IBLOCK_STATUS_INSANE	2
+#define SCAN_BLOCK_STATUS(scan)	((scan)->temp_buffer + (scan)->inode_size)
+
 struct ext2_struct_inode_scan {
 	errcode_t		magic;
 	ext2_filsys		fs;
@@ -193,12 +197,14 @@ errcode_t ext2fs_open_inode_scan(ext2_filsys fs, int buffer_blocks,
 		ext2fs_free_mem(&scan);
 		return retval;
 	}
-	retval = ext2fs_get_mem(scan->inode_size, &scan->temp_buffer);
+	retval = ext2fs_get_mem(scan->inode_size + scan->inode_buffer_blocks,
+				&scan->temp_buffer);
 	if (retval) {
 		ext2fs_free_mem(&scan->inode_buffer);
 		ext2fs_free_mem(&scan);
 		return retval;
 	}
+	memset(SCAN_BLOCK_STATUS(scan), 0, scan->inode_buffer_blocks);
 	if (scan->fs->badblocks && scan->fs->badblocks->num)
 		scan->scan_flags |= EXT2_SF_CHK_BADBLOCKS;
 	if (ext2fs_has_group_desc_csum(fs))
@@ -348,6 +354,114 @@ static errcode_t check_for_inode_bad_blocks(ext2_inode_scan scan,
 	return 0;
 }
 
+static int block_map_looks_insane(ext2_filsys fs,
+				  struct ext2_inode_large *inode)
+{
+	unsigned int i, bad;
+
+	/* We're only interested in block mapped files, dirs, and symlinks */
+	if ((inode->i_flags & EXT4_INLINE_DATA_FL) ||
+	    (inode->i_flags & EXT4_EXTENTS_FL))
+		return 0;
+	if (!LINUX_S_ISREG(inode->i_mode) &&
+	    !LINUX_S_ISLNK(inode->i_mode) &&
+	    !LINUX_S_ISDIR(inode->i_mode))
+		return 0;
+	if (LINUX_S_ISLNK(inode->i_mode) &&
+	    EXT2_I_SIZE(inode) <= sizeof(inode->i_block))
+		return 0;
+
+	/* Unused inodes probably aren't insane */
+	if (inode->i_links_count == 0)
+		return 0;
+
+	/* See if more than half the block maps are insane */
+	for (i = 0, bad = 0; i < EXT2_N_BLOCKS; i++)
+		if (inode->i_block[i] != 0 &&
+		    (inode->i_block[i] < fs->super->s_first_data_block ||
+		     inode->i_block[i] >= ext2fs_blocks_count(fs->super)))
+			bad++;
+	return bad > EXT2_N_BLOCKS / 2;
+}
+
+static int extent_head_looks_insane(struct ext2_inode_large *inode)
+{
+	if (!(inode->i_flags & EXT4_EXTENTS_FL) ||
+	    ext2fs_extent_header_verify(inode->i_block,
+					sizeof(inode->i_block)) == 0)
+		return 0;
+	return 1;
+}
+
+/*
+ * Check all the inodes that we just read into the buffer.  Record what we
+ * find here -- currently, we can observe that all checksums are ok; more
+ * than half the inodes are insane; or no conclusions at all.
+ */
+static void check_inode_block_sanity(ext2_inode_scan scan, blk64_t num_blocks)
+{
+	ext2_ino_t	ino, inodes_to_scan;
+	unsigned int	badness, checksum_failures;
+	unsigned int	inodes_in_buf, inodes_per_block;
+	void		*p;
+	struct ext2_inode_large *inode;
+	char		*block_status;
+	unsigned int	blk;
+
+	if (!(scan->scan_flags & EXT2_SF_WARN_GARBAGE_INODES))
+		return;
+
+	inodes_to_scan = scan->inodes_left;
+	inodes_in_buf = num_blocks * scan->fs->blocksize / scan->inode_size;
+	if (inodes_to_scan > inodes_in_buf)
+		inodes_to_scan = inodes_in_buf;
+
+	p = scan->inode_buffer;
+	ino = scan->current_inode + 1;
+	checksum_failures = badness = 0;
+	block_status = SCAN_BLOCK_STATUS(scan);
+	memset(block_status, 0, scan->inode_buffer_blocks);
+	inodes_per_block = EXT2_INODES_PER_BLOCK(scan->fs->super);
+
+	while (inodes_to_scan > 0) {
+		blk = (p - (void *)scan->inode_buffer) / scan->fs->blocksize;
+		inode = p;
+
+		/* Is this inode insane? */
+		if (!ext2fs_inode_csum_verify(scan->fs, ino, inode)) {
+			checksum_failures++;
+			badness++;
+		} else if (extent_head_looks_insane(inode) ||
+			   block_map_looks_insane(scan->fs, inode))
+			badness++;
+
+		/* If more than half are insane, declare the whole block bad */
+		if (badness > inodes_per_block / 2) {
+			unsigned int ino_adj;
+
+			block_status[blk] |= IBLOCK_STATUS_INSANE;
+			ino_adj = inodes_per_block -
+						((ino - 1) % inodes_per_block);
+			if (ino_adj > inodes_to_scan)
+				ino_adj = inodes_to_scan;
+			inodes_to_scan -= ino_adj;
+			p += scan->inode_size * ino_adj;
+			ino += ino_adj;
+			checksum_failures = badness = 0;
+			continue;
+		}
+
+		if ((ino % inodes_per_block) == 0) {
+			if (checksum_failures == 0)
+				block_status[blk] |= IBLOCK_STATUS_CSUMS_OK;
+			checksum_failures = badness = 0;
+		}
+		inodes_to_scan--;
+		p += scan->inode_size;
+		ino++;
+	};
+}
+
 /*
  * This function is called by ext2fs_get_next_inode when it needs to
  * read in more blocks from the current blockgroup's inode table.
@@ -397,12 +511,15 @@ static errcode_t get_next_blocks(ext2_inode_scan scan)
 		if (retval)
 			return EXT2_ET_NEXT_INODE_READ;
 	}
+	check_inode_block_sanity(scan, num_blocks);
+
 	scan->ptr = scan->inode_buffer;
 	scan->bytes_left = num_blocks * scan->fs->blocksize;
 
 	scan->blocks_left -= num_blocks;
 	if (scan->current_block)
 		scan->current_block += num_blocks;
+
 	return 0;
 }
 
@@ -432,10 +549,14 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
 {
 	errcode_t	retval;
 	int		extra_bytes = 0;
-	const int	length = EXT2_INODE_SIZE(scan->fs->super);
+	int		length;
 	struct ext2_inode_large	*iptr = (struct ext2_inode_large *)inode;
+	char		*iblock_status;
+	unsigned int	iblk;
 
 	EXT2_CHECK_MAGIC(scan, EXT2_ET_MAGIC_INODE_SCAN);
+	length = EXT2_INODE_SIZE(scan->fs->super);
+	iblock_status = SCAN_BLOCK_STATUS(scan);
 
 	/*
 	 * Do we need to start reading a new block group?
@@ -503,6 +624,9 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
 	}
 
 	retval = 0;
+	iblk = scan->current_inode % EXT2_INODES_PER_GROUP(scan->fs->super) /
+				EXT2_INODES_PER_BLOCK(scan->fs->super) %
+				scan->inode_buffer_blocks;
 	if (extra_bytes) {
 		memcpy(scan->temp_buffer+extra_bytes, scan->ptr,
 		       scan->inode_size - extra_bytes);
@@ -510,7 +634,8 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
 		scan->bytes_left -= scan->inode_size - extra_bytes;
 
 		/* Verify the inode checksum. */
-		if (!(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
+		if (!(iblock_status[iblk] & IBLOCK_STATUS_CSUMS_OK) &&
+		    !(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
 		    !ext2fs_inode_csum_verify(scan->fs, scan->current_inode + 1,
 				(struct ext2_inode_large *)scan->temp_buffer))
 			retval = EXT2_ET_INODE_CSUM_INVALID;
@@ -529,7 +654,8 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
 		scan->scan_flags &= ~EXT2_SF_BAD_EXTRA_BYTES;
 	} else {
 		/* Verify the inode checksum. */
-		if (!(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
+		if (!(iblock_status[iblk] & IBLOCK_STATUS_CSUMS_OK) &&
+		    !(scan->fs->flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) &&
 		    !ext2fs_inode_csum_verify(scan->fs, scan->current_inode + 1,
 				(struct ext2_inode_large *)scan->ptr))
 			retval = EXT2_ET_INODE_CSUM_INVALID;
@@ -548,6 +674,9 @@ errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino,
 		if (scan->scan_flags & EXT2_SF_BAD_INODE_BLK)
 			retval = EXT2_ET_BAD_BLOCK_IN_INODE_TABLE;
 	}
+	if ((iblock_status[iblk] & IBLOCK_STATUS_INSANE) &&
+	    (retval == 0 || retval == EXT2_ET_INODE_CSUM_INVALID))
+		retval = EXT2_ET_INODE_IS_GARBAGE;
 
 	scan->inodes_left--;
 	scan->current_inode++;

--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ