diff options
Diffstat (limited to 'fs')
| -rw-r--r-- | fs/btrfs/btrfs.c | 77 | ||||
| -rw-r--r-- | fs/btrfs/ctree.c | 16 | ||||
| -rw-r--r-- | fs/btrfs/ctree.h | 3 | ||||
| -rw-r--r-- | fs/btrfs/dir-item.c | 7 | ||||
| -rw-r--r-- | fs/fs.c | 42 | ||||
| -rw-r--r-- | fs/squashfs/sqfs.c | 138 | ||||
| -rw-r--r-- | fs/squashfs/sqfs_dir.c | 42 | ||||
| -rw-r--r-- | fs/squashfs/sqfs_filesystem.h | 6 | ||||
| -rw-r--r-- | fs/squashfs/sqfs_inode.c | 106 |
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: @@ -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(®->fragment); - u32 file_size = get_unaligned_le32(®->file_size); + u32 fragment, file_size; unsigned int blk_list_size; + int size; + + if (max < sizeof(*reg)) + return -EINVAL; + + fragment = get_unaligned_le32(®->fragment); + file_size = get_unaligned_le32(®->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; |
