unsquashfs-4: Add more sanity checks + fix CVE-2015-4645/6

Add more filesystem table sanity checks to Unsquashfs-4 and
also properly fix CVE-2015-4645 and CVE-2015-4646.

The CVEs were raised due to Unsquashfs having variable
oveflow and stack overflow in a number of vulnerable
functions.

The suggested patch only "fixed" one such function and fixed
it badly, and so it was buggy and introduced extra bugs!

The suggested patch was not only buggy, but, it used the
essentially wrong approach too.  It was "fixing" the
symptom but not the cause.  The symptom is wrong values
causing overflow, the cause is filesystem corruption.
This corruption should be detected and the filesystem
rejected *before* trying to allocate memory.

This patch applies the following fixes:

1. The filesystem super-block tables are checked, and the values
   must match across the filesystem.

   This will trap corrupted filesystems created by Mksquashfs.

2. The maximum (theorectical) size the filesystem tables could grow
   to, were analysed, and some variables were increased from int to
   long long.

   This analysis has been added as comments.

3. Stack allocation was removed, and a shared buffer (which is
   checked and increased as necessary) is used to read the
   table indexes.

Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
void dir_scan(char *parent_name, unsigned int start_block, unsigned int offset,
	struct pathnames *paths)
{
	unsigned int type;
	char *name;
	struct pathnames *new;
	struct inode *i;
	struct dir *dir = s_ops.squashfs_opendir(start_block, offset, &i);

	if(dir == NULL) {
		ERROR("dir_scan: failed to read directory %s, skipping\n",
			parent_name);
		return;
	}

	if(lsonly || info)
		print_filename(parent_name, i);

	if(!lsonly) {
		/*
		 * Make directory with default User rwx permissions rather than
		 * the permissions from the filesystem, as these may not have
		 * write/execute permission.  These are fixed up later in
		 * set_attributes().
		 */
		int res = mkdir(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
		if(res == -1) {
			/*
			 * Skip directory if mkdir fails, unless we're
			 * forcing and the error is -EEXIST
			 */
			if(!force || errno != EEXIST) {
				ERROR("dir_scan: failed to make directory %s, "
					"because %s\n", parent_name,
					strerror(errno));
				squashfs_closedir(dir);
				FAILED = TRUE;
				return;
			} 

			/*
			 * Try to change permissions of existing directory so
			 * that we can write to it
			 */
			res = chmod(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
			if (res == -1)
				ERROR("dir_scan: failed to change permissions "
					"for directory %s, because %s\n",
					parent_name, strerror(errno));
		}
	}

	while(squashfs_readdir(dir, &name, &start_block, &offset, &type)) {
		char *pathname;
		int res;

		TRACE("dir_scan: name %s, start_block %d, offset %d, type %d\n",
			name, start_block, offset, type);


		if(!matches(paths, name, &new))
			continue;

		res = asprintf(&pathname, "%s/%s", parent_name, name);
		if(res == -1)
			EXIT_UNSQUASH("asprintf failed in dir_scan\n");

		if(type == SQUASHFS_DIR_TYPE) {
			dir_scan(pathname, start_block, offset, new);
			free(pathname);
		} else if(new == NULL) {
			update_info(pathname);

			i = s_ops.read_inode(start_block, offset);

			if(lsonly || info)
				print_filename(pathname, i);

			if(!lsonly)
				create_inode(pathname, i);

			if(i->type == SQUASHFS_SYMLINK_TYPE ||
					i->type == SQUASHFS_LSYMLINK_TYPE)
				free(i->symlink);
		} else
			free(pathname);

		free_subdir(new);
	}

	if(!lsonly)
		queue_dir(parent_name, dir);

	squashfs_closedir(dir);
	dir_count ++;
}

void dir_scan(char *parent_name, unsigned int start_block, unsigned int offset,
	struct pathnames *paths)
{
	unsigned int type;
	char *name;
	struct pathnames *new;
	struct inode *i;
	struct dir *dir = s_ops.squashfs_opendir(start_block, offset, &i);

	if(dir == NULL) {
		ERROR("dir_scan: failed to read directory %s, skipping\n",
			parent_name);
		return;
	}

	if(lsonly || info)
		print_filename(parent_name, i);

	if(!lsonly) {
		/*
		 * Make directory with default User rwx permissions rather than
		 * the permissions from the filesystem, as these may not have
		 * write/execute permission.  These are fixed up later in
		 * set_attributes().
		 */
		int res = mkdir(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
		if(res == -1) {
			/*
			 * Skip directory if mkdir fails, unless we're
			 * forcing and the error is -EEXIST
			 */
			if(!force || errno != EEXIST) {
				ERROR("dir_scan: failed to make directory %s, "
					"because %s\n", parent_name,
					strerror(errno));
				squashfs_closedir(dir);
				FAILED = TRUE;
				return;
			} 

			/*
			 * Try to change permissions of existing directory so
			 * that we can write to it
			 */
			res = chmod(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
			if (res == -1)
				ERROR("dir_scan: failed to change permissions "
					"for directory %s, because %s\n",
					parent_name, strerror(errno));
		}
	}

	while(squashfs_readdir(dir, &name, &start_block, &offset, &type)) {
		char *pathname;
		int res;

		TRACE("dir_scan: name %s, start_block %d, offset %d, type %d\n",
			name, start_block, offset, type);


		if(!matches(paths, name, &new))
			continue;

		res = asprintf(&pathname, "%s/%s", parent_name, name);
		if(res == -1)
			EXIT_UNSQUASH("asprintf failed in dir_scan\n");

		if(type == SQUASHFS_DIR_TYPE) {
			dir_scan(pathname, start_block, offset, new);
			free(pathname);
		} else if(new == NULL) {
			update_info(pathname);

			i = s_ops.read_inode(start_block, offset);

			if(lsonly || info)
				print_filename(pathname, i);

			if(!lsonly)
				create_inode(pathname, i);

			if(i->type == SQUASHFS_SYMLINK_TYPE ||
					i->type == SQUASHFS_LSYMLINK_TYPE)
				free(i->symlink);
		} else
			free(pathname);

		free_subdir(new);
	}

	if(!lsonly)
		queue_dir(parent_name, dir);

	squashfs_closedir(dir);
	dir_count ++;
}
