unsquashfs: prevent buffer {over|under}flow in read_block() with corrupted filesystems

Prevent buffer overflow and underflow in read_block() with corrupted
filesystems.

Overflow is easy to understand, read_block() is called to read the next
metadata block pointed to by <start>...  Often the buffer passed in is
large enough to hold the expected return bytes, which can be less than
SQUASHFS_METADATA_SIZE.  For example filesystem tables are compressed
in SQUASHFS_METADATA_SIZEd chunks, the last compressed chunk will normally
be smaller than SQUASHFS_METADATA_SIZE, so when read_block() is called,
the passed buffer is only large enough to hold the expected size.

Underflow is rather more subtle, when read_block() is called, it is
expected that the returned block will fill the expected amount of
bytes in the filesystem table (stored as an array).  If the returned
block is smaller than expected, then there will be uninitialised
data in the filesystem table which will cause unexpected behaviour later.

Fix both cases by passing in an additional parameter <expected>
which contains the expected number of bytes in the metadata block.
Refuse to read blocks which are larger than expected to avoid
buffer overflow and also return error if the block proves to be
smaller than expected, to avoid using unitialised data.

For the callers where the expected number of bytes is unknown support
<expected> containing 0, in which case the metadata block is checked to
ensure it doesn't overflow a SQUASHFS_METADATA_SIZEd buffer.  Callers of
read_block() with <expected> == 0 are expected to pass in a
SQUASHFS_METADATA_SIZEd buffer.  For instance with compressor specific
options data, the correct length is only known by the compressor specific
code, and this is later called to check the length.

Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
diff --git a/squashfs-tools/read_xattrs.c b/squashfs-tools/read_xattrs.c
index 2c66826..3f72587 100644
--- a/squashfs-tools/read_xattrs.c
+++ b/squashfs-tools/read_xattrs.c
@@ -2,7 +2,7 @@
  * Read a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2010, 2012
+ * Copyright (c) 2010, 2012, 2013
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -61,7 +61,7 @@
 		} while(0)
 
 extern int read_fs_bytes(int, long long, int, void *);
-extern int read_block(int, long long, long long *, void *);
+extern int read_block(int, long long, long long *, int, void *);
 
 static struct hash_entry {
 	long long		start;
@@ -222,7 +222,7 @@
 	}
 
 	for(i = 0; i < indexes; i++) {
-		int length = read_block(fd, index[i], NULL,
+		int length = read_block(fd, index[i], NULL, 0,
 			((unsigned char *) xattr_ids) +
 			(i * SQUASHFS_METADATA_SIZE));
 		TRACE("Read xattr id table block %d, from 0x%llx, length "
@@ -259,7 +259,7 @@
 		if(res == -1)
 			goto failed3;
 
-		length = read_block(fd, start, &start,
+		length = read_block(fd, start, &start, 0,
 			((unsigned char *) xattrs) +
 			(i * SQUASHFS_METADATA_SIZE));
 		TRACE("Read xattr block %d, length %d\n", i, length);
diff --git a/squashfs-tools/unsquash-2.c b/squashfs-tools/unsquash-2.c
index 5f19760..2c8a098 100644
--- a/squashfs-tools/unsquash-2.c
+++ b/squashfs-tools/unsquash-2.c
@@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010
+ * Copyright (c) 2009, 2010, 2013
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -83,7 +83,7 @@
 	}
 
 	for(i = 0; i < indexes; i++) {
-		int length = read_block(fd, fragment_table_index[i], NULL,
+		int length = read_block(fd, fragment_table_index[i], NULL, 0,
 			((char *) fragment_table) + (i *
 			SQUASHFS_METADATA_SIZE));
 		TRACE("Read fragment table block %d, from 0x%x, length %d\n", i,
diff --git a/squashfs-tools/unsquash-3.c b/squashfs-tools/unsquash-3.c
index 34f5ac3..726bcf2 100644
--- a/squashfs-tools/unsquash-3.c
+++ b/squashfs-tools/unsquash-3.c
@@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2011, 2012
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -70,7 +70,7 @@
 	}
 
 	for(i = 0; i < indexes; i++) {
-		int length = read_block(fd, fragment_table_index[i], NULL,
+		int length = read_block(fd, fragment_table_index[i], NULL, 0,
 			((char *) fragment_table) + (i *
 			SQUASHFS_METADATA_SIZE));
 		TRACE("Read fragment table block %d, from 0x%llx, length %d\n",
diff --git a/squashfs-tools/unsquash-4.c b/squashfs-tools/unsquash-4.c
index ca5fe60..d9b3307 100644
--- a/squashfs-tools/unsquash-4.c
+++ b/squashfs-tools/unsquash-4.c
@@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2011, 2012
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -58,7 +58,7 @@
 	SQUASHFS_INSWAP_FRAGMENT_INDEXES(fragment_table_index, indexes);
 
 	for(i = 0; i < indexes; i++) {
-		int length = read_block(fd, fragment_table_index[i], NULL,
+		int length = read_block(fd, fragment_table_index[i], NULL, 0,
 			((char *) fragment_table) + (i *
 			SQUASHFS_METADATA_SIZE));
 		TRACE("Read fragment table block %d, from 0x%llx, length %d\n",
@@ -368,7 +368,7 @@
 	SQUASHFS_INSWAP_ID_BLOCKS(id_index_table, indexes);
 
 	for(i = 0; i < indexes; i++) {
-		res = read_block(fd, id_index_table[i], NULL,
+		res = read_block(fd, id_index_table[i], NULL, 0,
 			((char *) id_table) + i * SQUASHFS_METADATA_SIZE);
 		if(res == FALSE) {
 			ERROR("read_uids_guids: failed to read id table block"
diff --git a/squashfs-tools/unsquashfs.c b/squashfs-tools/unsquashfs.c
index 76bb30c..a060814 100644
--- a/squashfs-tools/unsquashfs.c
+++ b/squashfs-tools/unsquashfs.c
@@ -3,7 +3,7 @@
  * filesystem.
  *
  * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
- * 2012
+ * 2012, 2013
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -615,10 +615,12 @@
 }
 
 
-int read_block(int fd, long long start, long long *next, void *block)
+int read_block(int fd, long long start, long long *next, int expected,
+								void *block)
 {
 	unsigned short c_byte;
-	int offset = 2;
+	int offset = 2, res, compressed;
+	int outlen = expected ? expected : SQUASHFS_METADATA_SIZE;
 	
 	if(swap) {
 		if(read_fs_bytes(fd, start, 2, &c_byte) == FALSE)
@@ -634,34 +636,53 @@
 
 	if(SQUASHFS_CHECK_DATA(sBlk.s.flags))
 		offset = 3;
-	if(SQUASHFS_COMPRESSED(c_byte)) {
-		char buffer[SQUASHFS_METADATA_SIZE];
-		int error, res;
 
-		c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte);
-		if(read_fs_bytes(fd, start + offset, c_byte, buffer) == FALSE)
+	compressed = SQUASHFS_COMPRESSED(c_byte);
+	c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte);
+
+	/*
+	 * The block size should not be larger than
+	 * the uncompressed size (or max uncompressed size if
+	 * expected is 0)
+	 */
+	if(c_byte > outlen)
+		return 0;
+
+	if(compressed) {
+		char buffer[c_byte];
+		int error;
+
+		res = read_fs_bytes(fd, start + offset, c_byte, buffer);
+		if(res == FALSE)
 			goto failed;
 
 		res = compressor_uncompress(comp, block, buffer, c_byte,
-			SQUASHFS_METADATA_SIZE, &error);
+			outlen, &error);
 
 		if(res == -1) {
 			ERROR("%s uncompress failed with error code %d\n",
 				comp->name, error);
 			goto failed;
 		}
-		if(next)
-			*next = start + offset + c_byte;
-		return res;
 	} else {
-		c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte);
-		if(read_fs_bytes(fd, start + offset, c_byte, block) == FALSE)
+		res = read_fs_bytes(fd, start + offset, c_byte, block);
+		if(res == FALSE)
 			goto failed;
-		if(next)
-			*next = start + offset + c_byte;
-		return c_byte;
+		res = c_byte;
 	}
 
+	if(next)
+		*next = start + offset + c_byte;
+
+	/*
+	 * if expected, then check the (uncompressed) return data
+	 * is of the expected size
+	 */
+	if(expected && expected != res)
+		return 0;
+	else
+		return res;
+
 failed:
 	ERROR("read_block: failed to read block @0x%llx\n", start);
 	return FALSE;
@@ -720,7 +741,7 @@
 		}
 		TRACE("uncompress_inode_table: reading block 0x%llx\n", start);
 		add_entry(inode_table_hash, start, bytes);
-		res = read_block(fd, start, &start, inode_table + bytes);
+		res = read_block(fd, start, &start, 0, inode_table + bytes);
 		if(res == 0) {
 			free(inode_table);
 			EXIT_UNSQUASH("uncompress_inode_table: failed to read "
@@ -1109,7 +1130,7 @@
 		TRACE("uncompress_directory_table: reading block 0x%llx\n",
 				start);
 		add_entry(directory_table_hash, start, bytes);
-		res = read_block(fd, start, &start, directory_table + bytes);
+		res = read_block(fd, start, &start, 0, directory_table + bytes);
 		if(res == 0)
 			EXIT_UNSQUASH("uncompress_directory_table: failed to "
 				"read block\n");
diff --git a/squashfs-tools/unsquashfs.h b/squashfs-tools/unsquashfs.h
index 287c1a1..8809241 100644
--- a/squashfs-tools/unsquashfs.h
+++ b/squashfs-tools/unsquashfs.h
@@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010
+ * Copyright (c) 2009, 2010, 2013
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -265,7 +265,7 @@
 /* unsquashfs.c */
 extern int lookup_entry(struct hash_table_entry **, long long);
 extern int read_fs_bytes(int fd, long long, int, void *);
-extern int read_block(int, long long, long long *, void *);
+extern int read_block(int, long long, long long *, int, void *);
 
 /* unsquash-1.c */
 extern void read_block_list_1(unsigned int *, char *, int);