tux3: Make tux_readdir() more robust against invalid f_pos

If dir f_pos is invalid position, and current tux_readdir() calls
BUG() as wrong entry.

This patch checks f_pos and more detail of dir entry (from ext4
checker). And additionally, checks f_pos alignment is valid at first.

If f_pos is invalid clearly, this returns -ENOENT as SuSv4's readdir()
says.

If directory entry is invalid, converts from tux_zero_len_error() to
just error message, then skip that block and continue.  [FIXME: If
directory entry is invalid, skips that block, and continue at next
block. What is good behavior?]

Reported-by: "Darrick J. Wong" <darrick.wong@oracle.com>
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
diff --git a/fs/tux3/dir.c b/fs/tux3/dir.c
index 0000bbd..007f6ae 100644
--- a/fs/tux3/dir.c
+++ b/fs/tux3/dir.c
@@ -306,6 +306,39 @@
 	[TUX_LNK] = DT_LNK,
 };
 
+/*
+ * Return 0 if the directory entry is OK, and 1 if there is a problem
+ */
+static int __check_dir_entry(const char *func, int line, struct inode *dir,
+			     struct buffer_head *buffer, tux_dirent *entry)
+{
+	struct sb *sb = tux_sb(dir->i_sb);
+	const char *error_msg = NULL;
+	const void *base = bufdata(buffer);
+	const int off = (void *)entry - base;
+	const int rlen = tux_rec_len_from_disk(entry->rec_len);
+
+	if (unlikely(rlen < TUX_REC_LEN(1)))
+		error_msg = "rec_len is smaller than minimal";
+	else if (unlikely(rlen & (TUX_DIR_ALIGN - 1)))
+		error_msg = "rec_len alignment error";
+	else if (unlikely(rlen < TUX_REC_LEN(entry->name_len)))
+		error_msg = "rec_len is too small for name_len";
+	else if (unlikely(off + rlen > sb->blocksize))
+		error_msg = "directory entry across range";
+	else
+		return 0;
+
+	__tux3_err(sb, func, line,
+		   "bad entry: %s: inum %Lu, block %Lu, off %d, rec_len %d",
+		   error_msg, tux_inode(dir)->inum, bufindex(buffer),
+		   off, rlen);
+
+	return 1;
+}
+#define check_dir_entry(d, b, e)		\
+	__check_dir_entry(__func__, __LINE__, d, b, e)
+
 int tux_readdir(struct file *file, struct dir_context *ctx)
 {
 	struct inode *dir = file_inode(file);
@@ -317,6 +350,10 @@
 
 	assert(!(dir->i_size & sb->blockmask));
 
+	/* Clearly invalid offset */
+	if (unlikely(offset & (TUX_DIR_ALIGN - 1)))
+		return -ENOENT;
+
 	for (block = ctx->pos >> blockbits; block < blocks; block++) {
 		struct buffer_head *buffer = blockread(mapping(dir), block);
 		if (!buffer)
@@ -336,10 +373,10 @@
 		}
 		tux_dirent *limit = base + sb->blocksize - TUX_REC_LEN(1);
 		for (tux_dirent *entry = base + offset; entry <= limit; entry = next_entry(entry)) {
-			if (entry->rec_len == 0) {
-				blockput(buffer);
-				tux_zero_len_error(dir, block);
-				return -EIO;
+			if (check_dir_entry(dir, buffer, entry)) {
+				/* On error, skip to next block */
+				ctx->pos = (ctx->pos | (sb->blocksize - 1)) + 1;
+				break;
 			}
 			if (!is_deleted(entry)) {
 				unsigned type = (entry->type < TUX_TYPES) ? filetype[entry->type] : DT_UNKNOWN;
diff --git a/fs/tux3/tux3.h b/fs/tux3/tux3.h
index 0415298..c2e9363 100644
--- a/fs/tux3/tux3.h
+++ b/fs/tux3/tux3.h
@@ -703,9 +703,11 @@
 
 #define tux3_msg(sb, fmt, ...)						\
 	__tux3_msg(sb, KERN_INFO, "", fmt, ##__VA_ARGS__)
-#define tux3_err(sb, fmt, ...)						\
+#define __tux3_err(sb, func, line, fmt, ...)				\
 	__tux3_msg(sb, KERN_ERR, " error",				\
-		   "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+		   "%s:%d: " fmt, func, line, ##__VA_ARGS__)
+#define tux3_err(sb, fmt, ...)						\
+	__tux3_err(sb, __func__, __LINE__, fmt, ##__VA_ARGS__)
 #define tux3_warn(sb, fmt, ...)					\
 	__tux3_msg(sb, KERN_WARNING, " warning", fmt, ##__VA_ARGS__)