summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/btrfs/btrfs.c77
-rw-r--r--fs/btrfs/ctree.c16
-rw-r--r--fs/btrfs/ctree.h3
-rw-r--r--fs/btrfs/dir-item.c7
-rw-r--r--fs/fs.c42
-rw-r--r--fs/squashfs/sqfs.c138
-rw-r--r--fs/squashfs/sqfs_dir.c42
-rw-r--r--fs/squashfs/sqfs_filesystem.h6
-rw-r--r--fs/squashfs/sqfs_inode.c106
9 files changed, 366 insertions, 71 deletions
diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c
index e663dda12e8..f5f6d638ffd 100644
--- a/fs/btrfs/btrfs.c
+++ b/fs/btrfs/btrfs.c
@@ -88,12 +88,43 @@ static unsigned int btrfs_dirent_type_to_fs_type(u8 dirent_type)
}
}
+/*
+ * Read the size stored in an inode item. A missing item is -ENOENT and
+ * leaves *size untouched.
+ */
+static int btrfs_get_inode_size(struct btrfs_root *root, u64 ino, u64 *size)
+{
+ struct btrfs_inode_item *ii;
+ struct btrfs_path path;
+ struct btrfs_key key;
+ int ret;
+
+ key.objectid = ino;
+ key.type = BTRFS_INODE_ITEM_KEY;
+ key.offset = 0;
+
+ btrfs_init_path(&path);
+ ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ ret = -ENOENT;
+ if (!ret) {
+ ii = btrfs_item_ptr(path.nodes[0], path.slots[0],
+ struct btrfs_inode_item);
+ *size = btrfs_inode_size(path.nodes[0], ii);
+ }
+ btrfs_release_path(&path);
+ return ret;
+}
+
int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
{
struct btrfs_dir_stream *dirs = container_of(fs_dirs, struct btrfs_dir_stream, parent);
struct btrfs_fs_info *fs_info = current_fs_info;
struct fs_dirent *dent = &dirs->dirent;
struct btrfs_root *root;
+ struct btrfs_key location;
struct btrfs_key key;
u8 type;
int ret;
@@ -110,13 +141,29 @@ int btrfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
memset(dent, 0, sizeof(*dent));
ret = btrfs_next_dir_entry(root, dirs->ino, &dirs->offset, dent->name,
- sizeof(dent->name), &type);
+ sizeof(dent->name), &type, &location);
if (ret < 0)
return ret;
if (ret > 0)
return -ENOENT;
dent->type = btrfs_dirent_type_to_fs_type(type);
+
+ /*
+ * A subvolume entry points at a root item rather than an inode, and
+ * has no size of its own. Everything else carries one, and the fs
+ * layer prints it, so look it up.
+ */
+ if (location.type == BTRFS_INODE_ITEM_KEY) {
+ u64 size;
+
+ ret = btrfs_get_inode_size(root, location.objectid, &size);
+ if (ret < 0 && ret != -ENOENT)
+ return ret;
+ if (!ret)
+ dent->size = size;
+ }
+
*dentp = dent;
return 0;
}
@@ -151,10 +198,8 @@ int btrfs_exists(const char *file)
int btrfs_size(const char *file, loff_t *size)
{
struct btrfs_fs_info *fs_info = current_fs_info;
- struct btrfs_inode_item *ii;
struct btrfs_root *root;
- struct btrfs_path path;
- struct btrfs_key key;
+ u64 isize;
u64 ino;
u8 type;
int ret;
@@ -169,27 +214,13 @@ int btrfs_size(const char *file, loff_t *size)
printf("Not a regular file: %s\n", file);
return -ENOENT;
}
- btrfs_init_path(&path);
- key.objectid = ino;
- key.type = BTRFS_INODE_ITEM_KEY;
- key.offset = 0;
-
- ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
- if (ret < 0) {
- printf("Cannot lookup ino %llu\n", ino);
+ ret = btrfs_get_inode_size(root, ino, &isize);
+ if (ret) {
+ printf("Cannot read size of ino %llu\n", ino);
return ret;
}
- if (ret > 0) {
- printf("Ino %llu does not exist\n", ino);
- ret = -ENOENT;
- goto out;
- }
- ii = btrfs_item_ptr(path.nodes[0], path.slots[0],
- struct btrfs_inode_item);
- *size = btrfs_inode_size(path.nodes[0], ii);
-out:
- btrfs_release_path(&path);
- return ret;
+ *size = isize;
+ return 0;
}
int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len,
diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
index 8e932adc425..48c50e556b1 100644
--- a/fs/btrfs/ctree.c
+++ b/fs/btrfs/ctree.c
@@ -425,8 +425,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans,
level = btrfs_header_level(b);
p->nodes[level] = b;
ret = check_block(fs_info, p, level);
- if (ret)
- return -1;
+ if (ret) {
+ ret = -1;
+ goto err;
+ }
ret = btrfs_bin_search(b, key, &slot);
if (level != 0) {
if (ret && slot > 0)
@@ -461,8 +463,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans,
break;
b = read_node_slot(fs_info, b, slot);
- if (!extent_buffer_uptodate(b))
- return -EIO;
+ if (!extent_buffer_uptodate(b)) {
+ ret = -EIO;
+ goto err;
+ }
} else {
p->slots[level] = slot;
/*
@@ -479,6 +483,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans,
}
}
return 1;
+
+err:
+ btrfs_release_path(p);
+ return ret;
}
/*
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 3fa9a8c9c02..cd3fd669f9a 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -1221,7 +1221,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
const char *name, int name_len,
int mod);
int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset,
- char *namebuf, int namebuf_len, u8 *ftype);
+ char *namebuf, int namebuf_len, u8 *ftype,
+ struct btrfs_key *location);
/* inode.c */
int btrfs_lookup_path(struct btrfs_root *root, u64 ino, const char *filename,
struct btrfs_root **root_ret, u64 *ino_ret,
diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index c7b87d60d98..6edda34818b 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -126,12 +126,16 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
* @namebuf: caller buffer that receives the NUL-terminated name
* @namebuf_len: size of @namebuf in bytes
* @ftype: receives the BTRFS_FT_* type of the entry
+ * @location: receives the key the entry points at, so the caller can
+ * reach the inode item without searching for the name
+ * again
*
* Return: 0 if an entry was returned, 1 when the directory is exhausted,
* -ve on error.
*/
int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset,
- char *namebuf, int namebuf_len, u8 *ftype)
+ char *namebuf, int namebuf_len, u8 *ftype,
+ struct btrfs_key *location)
{
struct btrfs_path path;
struct btrfs_key key;
@@ -180,6 +184,7 @@ int btrfs_next_dir_entry(struct btrfs_root *root, u64 ino, u64 *offset,
(unsigned long)(di + 1), name_len);
namebuf[name_len] = '\0';
*ftype = btrfs_dir_type(path.nodes[0], di);
+ btrfs_dir_item_key_to_cpu(path.nodes[0], di, location);
ret = 0;
out:
diff --git a/fs/fs.c b/fs/fs.c
index 1ebda6f4ee2..2824c7defa2 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -461,11 +461,53 @@ const char *fs_get_type_name(void)
return fs_get_info(fs_type)->name;
}
+/*
+ * Some fstypes (semihosting, ubifs) have no underlying block device
+ * and ignore the block_desc argument of their probe hook. The legacy
+ * commands (ubifsload, semihosting via env macros) just pass NULL;
+ * for "load <iface> ..." to behave the same, the dispatcher opts
+ * those fstypes in by name here, before any block-device lookup is
+ * attempted.
+ *
+ * Returns the matching fstype_info if @ifname names a fstype that
+ * opts into null_dev_desc_ok dispatch and the caller's @fstype filter
+ * permits it. Returns NULL otherwise.
+ */
+static struct fstype_info *fs_lookup_null_dev_info(const char *ifname,
+ int fstype)
+{
+ struct fstype_info *info;
+ int i;
+
+ for (i = 0, info = fstypes; i < ARRAY_SIZE(fstypes); i++, info++) {
+ if (fstype != FS_TYPE_ANY && info->fstype != FS_TYPE_ANY &&
+ fstype != info->fstype)
+ continue;
+ if (!info->null_dev_desc_ok || !info->name)
+ continue;
+ if (!strcmp(info->name, ifname))
+ return info;
+ }
+
+ return NULL;
+}
+
int fs_set_blk_dev(const char *ifname, const char *dev_part_str, int fstype)
{
struct fstype_info *info;
int part, i;
+ info = fs_lookup_null_dev_info(ifname, fstype);
+ if (info) {
+ fs_dev_desc = NULL;
+ memset(&fs_partition, 0, sizeof(fs_partition));
+ if (!info->probe(NULL, &fs_partition)) {
+ fs_type = info->fstype;
+ fs_dev_part = 0;
+ return 0;
+ }
+ }
+
part = part_get_info_by_dev_and_name_or_num(ifname, dev_part_str, &fs_dev_desc,
&fs_partition, 1);
if (part < 0)
diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c
index 3aadcdd36ec..0ea6dcaba1a 100644
--- a/fs/squashfs/sqfs.c
+++ b/fs/squashfs/sqfs.c
@@ -109,6 +109,7 @@ static int sqfs_frag_lookup(u32 inode_fragment_index,
unsigned char *metadata_buffer, *metadata, *table;
struct squashfs_fragment_block_entry *entries;
struct squashfs_super_block *sblk = ctxt.sblk;
+ size_t table_size, metadata_size, valid_len;
unsigned long dest_len;
int block, offset, ret;
u16 header;
@@ -133,7 +134,12 @@ static int sqfs_frag_lookup(u32 inode_fragment_index,
start /= ctxt.cur_dev->blksz;
/* Allocate a proper sized buffer to store the fragment index table */
- table = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
+ if (__builtin_mul_overflow(n_blks, ctxt.cur_dev->blksz, &table_size)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ table = malloc_cache_aligned(table_size);
if (!table) {
ret = -ENOMEM;
goto out;
@@ -148,6 +154,16 @@ static int sqfs_frag_lookup(u32 inode_fragment_index,
offset = SQFS_FRAGMENT_INDEX_OFFSET(inode_fragment_index);
/*
+ * 'inode_fragment_index' is only checked against sblk->fragments, which
+ * is itself read from the image, so the resulting index may point past
+ * the fragment index table that was actually read from the device.
+ */
+ if (table_offset + ((u64)block + 1) * sizeof(u64) > table_size) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
* Get the start offset of the metadata block that contains the right
* fragment block entry
*/
@@ -158,7 +174,13 @@ static int sqfs_frag_lookup(u32 inode_fragment_index,
n_blks = sqfs_calc_n_blks(cpu_to_le64(start_block),
sblk->fragment_table_start, &table_offset);
- metadata_buffer = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
+ if (__builtin_mul_overflow(n_blks, ctxt.cur_dev->blksz,
+ &metadata_size)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ metadata_buffer = malloc_cache_aligned(metadata_size);
if (!metadata_buffer) {
ret = -ENOMEM;
goto out;
@@ -170,6 +192,11 @@ static int sqfs_frag_lookup(u32 inode_fragment_index,
}
/* Every metadata block starts with a 16-bit header */
+ if (table_offset + SQFS_HEADER_SIZE > metadata_size) {
+ ret = -EINVAL;
+ goto out;
+ }
+
header = get_unaligned_le16(metadata_buffer + table_offset);
metadata = metadata_buffer + table_offset + SQFS_HEADER_SIZE;
@@ -183,6 +210,16 @@ static int sqfs_frag_lookup(u32 inode_fragment_index,
goto out;
}
+ /*
+ * The metadata block's payload is read straight out of
+ * 'metadata_buffer', so it has to fit in what was read from the device.
+ */
+ if (table_offset + SQFS_HEADER_SIZE + SQFS_METADATA_SIZE(header) >
+ metadata_size) {
+ ret = -EINVAL;
+ goto out;
+ }
+
entries = malloc(SQFS_METADATA_BLOCK_SIZE);
if (!entries) {
ret = -ENOMEM;
@@ -198,8 +235,17 @@ static int sqfs_frag_lookup(u32 inode_fragment_index,
ret = -EINVAL;
goto out;
}
+
+ valid_len = dest_len;
} else {
memcpy(entries, metadata, SQFS_METADATA_SIZE(header));
+ valid_len = SQFS_METADATA_SIZE(header);
+ }
+
+ /* Only the part of 'entries' that was actually filled in is usable */
+ if (((u64)offset + 1) * sizeof(*entries) > valid_len) {
+ ret = -EINVAL;
+ goto out;
}
*e = entries[offset];
@@ -486,7 +532,8 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
dirsp = (struct fs_dir_stream *)dirs;
/* Start by root inode */
- table = sqfs_find_inode(dirs->inode_table, le32_to_cpu(sblk->inodes),
+ table = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+ le32_to_cpu(sblk->inodes),
sblk->inodes, sblk->block_size);
if (!table)
return -EINVAL;
@@ -496,6 +543,8 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
/* get directory offset in directory table */
offset = sqfs_dir_offset(table, m_list, m_count);
+ if (offset < 0)
+ return offset;
dirs->table = &dirs->dir_table[offset];
/* Setup directory header */
@@ -543,10 +592,13 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
dirs->dir_header->inode_number;
/* Get reference to inode in the inode table */
- table = sqfs_find_inode(dirs->inode_table, new_inode_number,
+ table = sqfs_find_inode(dirs->inode_table,
+ dirs->inode_table_size, new_inode_number,
sblk->inodes, sblk->block_size);
- if (!table)
- return -EINVAL;
+ if (!table) {
+ ret = -EINVAL;
+ goto out;
+ }
dir = (struct squashfs_dir_inode *)table;
/* Check for symbolic link and inode type sanity */
@@ -615,8 +667,6 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
goto out;
} else if (!sqfs_is_dir(get_unaligned_le16(&dir->inode_type))) {
printf("** Cannot find directory. **\n");
- free(dirs->entry);
- dirs->entry = NULL;
ret = -EINVAL;
goto out;
}
@@ -627,6 +677,10 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
/* Get dir. offset into the directory table */
offset = sqfs_dir_offset(table, m_list, m_count);
+ if (offset < 0) {
+ ret = offset;
+ goto out;
+ }
dirs->table = &dirs->dir_table[offset];
/* Copy directory header */
@@ -636,8 +690,6 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
/* Check for empty directory */
if (sqfs_is_empty_dir(table)) {
printf("Empty directory.\n");
- free(dirs->entry);
- dirs->entry = NULL;
ret = SQFS_EMPTY_DIR;
goto out;
}
@@ -651,6 +703,10 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
}
offset = sqfs_dir_offset(table, m_list, m_count);
+ if (offset < 0) {
+ ret = offset;
+ goto out;
+ }
dirs->table = &dirs->dir_table[offset];
if (get_unaligned_le16(&dir->inode_type) == SQFS_DIR_TYPE)
@@ -659,6 +715,10 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
memcpy(&dirs->i_ldir, ldir, sizeof(*ldir));
out:
+ if (ret < 0) {
+ free(dirs->entry);
+ dirs->entry = NULL;
+ }
free(res);
free(rem);
free(path);
@@ -719,7 +779,7 @@ static int sqfs_get_metablk_pos(u32 *pos_list, void *table, u32 offset,
return ret;
}
-static int sqfs_read_inode_table(unsigned char **inode_table)
+static int sqfs_read_inode_table(unsigned char **inode_table, size_t *out_size)
{
struct squashfs_super_block *sblk = ctxt.sblk;
u64 start, n_blks, table_offset, table_size;
@@ -773,6 +833,8 @@ static int sqfs_read_inode_table(unsigned char **inode_table)
goto free_itb;
}
+ *out_size = (size_t)metablks_count * SQFS_METADATA_BLOCK_SIZE;
+
src_table = itb + table_offset + SQFS_HEADER_SIZE;
/* Extract compressed Inode table */
@@ -935,6 +997,7 @@ static int sqfs_opendir_nest(const char *filename, struct fs_dir_stream **dirsp)
int j, token_count = 0, ret = 0, metablks_count;
struct squashfs_dir_stream *dirs;
char **token_list = NULL, *path = NULL;
+ size_t inode_table_size = 0;
u32 *pos_list = NULL;
dirs = calloc(1, sizeof(*dirs));
@@ -948,7 +1011,7 @@ static int sqfs_opendir_nest(const char *filename, struct fs_dir_stream **dirsp)
dirs->inode_table = NULL;
dirs->dir_table = NULL;
- ret = sqfs_read_inode_table(&inode_table);
+ ret = sqfs_read_inode_table(&inode_table, &inode_table_size);
if (ret) {
ret = -EINVAL;
goto out;
@@ -988,6 +1051,7 @@ static int sqfs_opendir_nest(const char *filename, struct fs_dir_stream **dirsp)
* a general solution for the malloc size, since 'i' is a union.
*/
dirs->inode_table = inode_table;
+ dirs->inode_table_size = inode_table_size;
dirs->dir_table = dir_table;
ret = sqfs_search_dir(dirs, token_list, token_count, pos_list,
metablks_count);
@@ -1086,8 +1150,8 @@ static int sqfs_readdir_nest(struct fs_dir_stream *fs_dirs, struct fs_dirent **d
}
i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
- ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
- sblk->block_size);
+ ipos = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+ i_number, sblk->inodes, sblk->block_size);
if (!ipos)
return -SQFS_STOP_READDIR;
@@ -1445,8 +1509,8 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
}
i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
- ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
- sblk->block_size);
+ ipos = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+ i_number, sblk->inodes, sblk->block_size);
if (!ipos) {
ret = -EINVAL;
goto out;
@@ -1488,6 +1552,15 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
symlink = (struct squashfs_symlink_inode *)ipos;
resolved = sqfs_resolve_symlink(symlink, filename);
+ /*
+ * Free the parent directory resources before recursing so that
+ * the recursive call can allocate its own inode and directory
+ * tables without exhausting the heap.
+ */
+ free(dirs->entry);
+ dirs->entry = NULL;
+ sqfs_closedir(dirsp);
+ dirsp = NULL;
ret = sqfs_read_nest(resolved, buf, offset, len, actread);
free(resolved);
goto out;
@@ -1639,6 +1712,19 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
goto out;
}
+ /*
+ * finfo.offset and finfo.size come from the on-disk inode and
+ * must not let the copy read past the decompressed fragment
+ * block (dest_len bytes).
+ */
+ if (finfo.size < (size_t)*actread ||
+ finfo.offset > dest_len ||
+ finfo.size - *actread > dest_len - finfo.offset) {
+ free(fragment_block);
+ ret = -EINVAL;
+ goto out;
+ }
+
memcpy(buf + *actread, &fragment_block[finfo.offset], finfo.size - *actread);
*actread = finfo.size;
@@ -1647,6 +1733,17 @@ static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
} else if (finfo.frag && !finfo.comp) {
fragment_block = (void *)fragment + table_offset;
+ /*
+ * Same check for the uncompressed fragment: the readable data
+ * is table_size bytes starting at table_offset within fragment.
+ */
+ if (finfo.size < (size_t)*actread ||
+ finfo.offset > table_size ||
+ finfo.size - *actread > table_size - finfo.offset) {
+ ret = -EINVAL;
+ goto out;
+ }
+
memcpy(buf + *actread, &fragment_block[finfo.offset], finfo.size - *actread);
*actread = finfo.size;
}
@@ -1714,8 +1811,8 @@ static int sqfs_size_nest(const char *filename, loff_t *size)
}
i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
- ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
- sblk->block_size);
+ ipos = sqfs_find_inode(dirs->inode_table, dirs->inode_table_size,
+ i_number, sblk->inodes, sblk->block_size);
if (!ipos) {
*size = 0;
@@ -1746,6 +1843,11 @@ static int sqfs_size_nest(const char *filename, loff_t *size)
symlink = (struct squashfs_symlink_inode *)ipos;
resolved = sqfs_resolve_symlink(symlink, filename);
+ /*
+ * Free the parent directory resources before recursing.
+ */
+ sqfs_closedir(dirsp);
+ dirsp = NULL;
ret = sqfs_size(resolved, size);
free(resolved);
break;
diff --git a/fs/squashfs/sqfs_dir.c b/fs/squashfs/sqfs_dir.c
index ed83c90682f..3908d1380b3 100644
--- a/fs/squashfs/sqfs_dir.c
+++ b/fs/squashfs/sqfs_dir.c
@@ -32,6 +32,7 @@ int sqfs_dir_offset(void *dir_i, u32 *m_list, int m_count)
struct squashfs_base_inode *base = dir_i;
struct squashfs_ldir_inode *ldir;
struct squashfs_dir_inode *dir;
+ u64 table_size, res;
u32 start_block;
int j, offset;
@@ -51,20 +52,49 @@ int sqfs_dir_offset(void *dir_i, u32 *m_list, int m_count)
return -EINVAL;
}
- if (offset < 0)
+ /*
+ * 'offset' is an offset into a decompressed metadata block, so it can
+ * never address past the end of one.
+ */
+ if (offset >= SQFS_METADATA_BLOCK_SIZE)
return -EINVAL;
+ if (m_count < 1)
+ return -EINVAL;
+
+ /* The caller's directory table holds m_count decompressed blocks. */
+ table_size = (u64)m_count * SQFS_METADATA_BLOCK_SIZE;
+
for (j = 0; j < m_count; j++) {
if (m_list[j] == start_block)
- return (++j * SQFS_METADATA_BLOCK_SIZE) + offset;
+ break;
}
- if (start_block == 0)
- return offset;
+ if (j < m_count) {
+ /*
+ * m_list[j] is the position of the metadata block following
+ * block j, so a match means the directory starts in block
+ * j + 1.
+ */
+ res = (u64)(j + 1) * SQFS_METADATA_BLOCK_SIZE + offset;
+ } else if (start_block == 0) {
+ res = offset;
+ } else {
+ printf("Error: invalid inode reference to directory table.\n");
+ return -EINVAL;
+ }
- printf("Error: invalid inode reference to directory table.\n");
+ /*
+ * Callers use the return value to index the directory table and read a
+ * directory header from it, so the whole header must lie inside the
+ * table.
+ */
+ if (res + SQFS_DIR_HEADER_SIZE > table_size) {
+ printf("Error: inode points past the end of the directory table.\n");
+ return -EINVAL;
+ }
- return -EINVAL;
+ return res;
}
bool sqfs_is_empty_dir(void *dir_i)
diff --git a/fs/squashfs/sqfs_filesystem.h b/fs/squashfs/sqfs_filesystem.h
index be56498a5e3..6c97b2c3a9c 100644
--- a/fs/squashfs/sqfs_filesystem.h
+++ b/fs/squashfs/sqfs_filesystem.h
@@ -275,6 +275,8 @@ struct squashfs_dir_stream {
* sqfs_opendir() and freed in sqfs_closedir().
*/
unsigned char *inode_table;
+ /* Size in bytes of the decompressed inode_table buffer */
+ size_t inode_table_size;
unsigned char *dir_table;
};
@@ -293,8 +295,8 @@ struct squashfs_file_info {
bool comp;
};
-void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count,
- __le32 block_size);
+void *sqfs_find_inode(void *inode_table, size_t table_size, int inode_number,
+ __le32 inode_count, __le32 block_size);
int sqfs_dir_offset(void *dir_i, u32 *m_list, int m_count);
diff --git a/fs/squashfs/sqfs_inode.c b/fs/squashfs/sqfs_inode.c
index ce9a8ff8e2a..20085f8912b 100644
--- a/fs/squashfs/sqfs_inode.c
+++ b/fs/squashfs/sqfs_inode.c
@@ -17,65 +17,122 @@
#include "sqfs_filesystem.h"
#include "sqfs_utils.h"
-int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
+int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size, size_t max)
{
- u16 inode_type = get_unaligned_le16(&inode->inode_type);
+ u16 inode_type;
+
+ /* The smallest possible inode must fit in the remaining bytes */
+ if (max < sizeof(struct squashfs_base_inode))
+ return -EINVAL;
+
+ inode_type = get_unaligned_le16(&inode->inode_type);
switch (inode_type) {
case SQFS_DIR_TYPE:
+ if (max < sizeof(struct squashfs_dir_inode))
+ return -EINVAL;
return sizeof(struct squashfs_dir_inode);
case SQFS_REG_TYPE: {
struct squashfs_reg_inode *reg =
(struct squashfs_reg_inode *)inode;
- u32 fragment = get_unaligned_le32(&reg->fragment);
- u32 file_size = get_unaligned_le32(&reg->file_size);
+ u32 fragment, file_size;
unsigned int blk_list_size;
+ int size;
+
+ if (max < sizeof(*reg))
+ return -EINVAL;
+
+ fragment = get_unaligned_le32(&reg->fragment);
+ file_size = get_unaligned_le32(&reg->file_size);
+
+ if (!blk_size)
+ return -EINVAL;
if (SQFS_IS_FRAGMENTED(fragment))
blk_list_size = file_size / blk_size;
else
blk_list_size = DIV_ROUND_UP(file_size, blk_size);
- return sizeof(*reg) + blk_list_size * sizeof(u32);
+ if (__builtin_mul_overflow(blk_list_size, (unsigned int)sizeof(u32),
+ &blk_list_size) ||
+ __builtin_add_overflow((int)sizeof(*reg), (int)blk_list_size,
+ &size))
+ return -EINVAL;
+
+ return size;
}
case SQFS_LDIR_TYPE: {
struct squashfs_ldir_inode *ldir =
(struct squashfs_ldir_inode *)inode;
- u16 i_count = get_unaligned_le16(&ldir->i_count);
+ u16 i_count;
unsigned int index_list_size = 0, l = 0;
struct squashfs_directory_index *di;
+ size_t consumed;
u32 sz;
+ int size;
+ if (max < sizeof(*ldir))
+ return -EINVAL;
+
+ i_count = get_unaligned_le16(&ldir->i_count);
if (i_count == 0)
return sizeof(*ldir);
di = ldir->index;
+ consumed = sizeof(*ldir);
while (l < i_count) {
+ /* The directory index header must stay in bounds */
+ if (consumed + sizeof(*di) > max)
+ return -EINVAL;
sz = get_unaligned_le32(&di->size) + 1;
+ if (__builtin_add_overflow(consumed, sizeof(*di) + sz,
+ &consumed) ||
+ consumed > max)
+ return -EINVAL;
index_list_size += sz;
di = (void *)di + sizeof(*di) + sz;
l++;
}
- return sizeof(*ldir) + index_list_size +
- i_count * SQFS_DIR_INDEX_BASE_LENGTH;
+ if (__builtin_add_overflow((int)(sizeof(*ldir) + index_list_size),
+ (int)(i_count * SQFS_DIR_INDEX_BASE_LENGTH),
+ &size))
+ return -EINVAL;
+
+ return size;
}
case SQFS_LREG_TYPE: {
struct squashfs_lreg_inode *lreg =
(struct squashfs_lreg_inode *)inode;
- u32 fragment = get_unaligned_le32(&lreg->fragment);
- u64 file_size = get_unaligned_le64(&lreg->file_size);
+ u32 fragment;
+ u64 file_size;
unsigned int blk_list_size;
+ int size;
+
+ if (max < sizeof(*lreg))
+ return -EINVAL;
+
+ fragment = get_unaligned_le32(&lreg->fragment);
+ file_size = get_unaligned_le64(&lreg->file_size);
+
+ if (!blk_size)
+ return -EINVAL;
if (fragment == 0xFFFFFFFF)
blk_list_size = DIV_ROUND_UP(file_size, blk_size);
else
blk_list_size = file_size / blk_size;
- return sizeof(*lreg) + blk_list_size * sizeof(u32);
+ if (__builtin_mul_overflow(blk_list_size, (unsigned int)sizeof(u32),
+ &blk_list_size) ||
+ __builtin_add_overflow((int)sizeof(*lreg), (int)blk_list_size,
+ &size))
+ return -EINVAL;
+
+ return size;
}
case SQFS_SYMLINK_TYPE:
@@ -85,6 +142,9 @@ int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
struct squashfs_symlink_inode *symlink =
(struct squashfs_symlink_inode *)inode;
+ if (max < sizeof(*symlink))
+ return -EINVAL;
+
if (__builtin_add_overflow(sizeof(*symlink),
get_unaligned_le32(&symlink->symlink_size), &size))
return -EINVAL;
@@ -94,15 +154,23 @@ int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
case SQFS_BLKDEV_TYPE:
case SQFS_CHRDEV_TYPE:
+ if (max < sizeof(struct squashfs_dev_inode))
+ return -EINVAL;
return sizeof(struct squashfs_dev_inode);
case SQFS_LBLKDEV_TYPE:
case SQFS_LCHRDEV_TYPE:
+ if (max < sizeof(struct squashfs_ldev_inode))
+ return -EINVAL;
return sizeof(struct squashfs_ldev_inode);
case SQFS_FIFO_TYPE:
case SQFS_SOCKET_TYPE:
+ if (max < sizeof(struct squashfs_ipc_inode))
+ return -EINVAL;
return sizeof(struct squashfs_ipc_inode);
case SQFS_LFIFO_TYPE:
case SQFS_LSOCKET_TYPE:
+ if (max < sizeof(struct squashfs_lipc_inode))
+ return -EINVAL;
return sizeof(struct squashfs_lipc_inode);
default:
printf("Error while searching inode: unknown type.\n");
@@ -114,11 +182,12 @@ int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
* Given the uncompressed inode table, the inode to be found and the number of
* inodes in the table, return inode position in case of success.
*/
-void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count,
- __le32 block_size)
+void *sqfs_find_inode(void *inode_table, size_t table_size, int inode_number,
+ __le32 inode_count, __le32 block_size)
{
struct squashfs_base_inode *base;
- unsigned int offset = 0, k;
+ size_t offset = 0;
+ unsigned int k;
int sz;
if (!inode_table) {
@@ -127,12 +196,17 @@ void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count,
}
for (k = 0; k < le32_to_cpu(inode_count); k++) {
+ /* The base inode header must lie within the inode table */
+ if (offset + sizeof(struct squashfs_base_inode) > table_size)
+ return NULL;
+
base = inode_table + offset;
if (get_unaligned_le32(&base->inode_number) == inode_number)
return inode_table + offset;
- sz = sqfs_inode_size(base, le32_to_cpu(block_size));
- if (sz < 0)
+ sz = sqfs_inode_size(base, le32_to_cpu(block_size),
+ table_size - offset);
+ if (sz <= 0 || (size_t)sz > table_size - offset)
return NULL;
offset += sz;