From 6831ab9f5ae7c8d224a2b8075bd04815ed7d5e17 Mon Sep 17 00:00:00 2001 From: renxudong Date: Sun, 11 Aug 2019 03:17:20 -0400 Subject: [PATCH] CVE-2015-464 --- squashfs-tools/read_xattrs.c | 57 ++++++++--- squashfs-tools/unsquash-4.c | 232 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 236 insertions(+), 53 deletions(-) diff --git a/squashfs-tools/read_xattrs.c b/squashfs-tools/read_xattrs.c index 9c66387..189a8a1 100644 --- a/squashfs-tools/read_xattrs.c +++ b/squashfs-tools/read_xattrs.c @@ -150,7 +150,16 @@ static int read_xattr_entry(struct xattr_list *xattr, */ int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk, int flag, long long *table_start) { - int res, bytes, i, indexes, index_bytes, ids; + /* + * Note on overflow limits: + * Size of ids (id_table.xattr_ids) is 2^32 (unsigned int) + * Max size of bytes is 2^32*16 or 2^36 + * Max indexes is (2^32*16)/8K or 2^23 + * Max index_bytes is ((2^32*16)/8K)*8 or 2^26 or 64M + */ + int res, i, indexes, index_bytes; + unsigned int ids; + long long bytes; long long *index, start, end; struct squashfs_xattr_table id_table; @@ -170,24 +179,44 @@ int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk, int flag, l SQUASHFS_INSWAP_XATTR_TABLE(&id_table); - if(flag) { - /* - * id_table.xattr_table_start stores the start of the compressed xattr - * * metadata blocks. This by definition is also the end of the previous - * filesystem table - the id lookup table. - */ + /* + * Compute index table values + */ + ids = id_table.xattr_ids; + xattr_table_start = id_table.xattr_table_start; + index_bytes = SQUASHFS_XATTR_BLOCK_BYTES((long long) ids); + indexes = SQUASHFS_XATTR_BLOCKS((long long) ids); + + /* + * The size of the index table (index_bytes) should match the + * table start and end points + */ + if(index_bytes != (sBlk->bytes_used - (sBlk->xattr_id_table_start + sizeof(id_table)))) { + ERROR("read_xattrs_from_disk: Bad xattr_ids count in super block\n"); + return 0; + } + + /* + * id_table.xattr_table_start stores the start of the compressed xattr + * metadata blocks. This by definition is also the end of the previous + * filesystem table - the id lookup table. + */ + if(table_start != NULL) *table_start = id_table.xattr_table_start; + + /* + * If flag is set then return once we've read the above + * table_start. That value is necessary for sanity checking, + * but we don't actually want to extract the xattrs, and so + * stop here. + */ + if(flag) return id_table.xattr_ids; - } /* * Allocate and read the index to the xattr id table metadata * blocks */ - ids = id_table.xattr_ids; - xattr_table_start = id_table.xattr_table_start; - index_bytes = SQUASHFS_XATTR_BLOCK_BYTES(ids); - indexes = SQUASHFS_XATTR_BLOCKS(ids); index = malloc(index_bytes); if(index == NULL) MEM_ERROR(); @@ -203,7 +232,7 @@ int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk, int flag, l * Allocate enough space for the uncompressed xattr id table, and * read and decompress it */ - bytes = SQUASHFS_XATTR_BYTES(ids); + bytes = SQUASHFS_XATTR_BYTES((long long) ids); xattr_ids = malloc(bytes); if(xattr_ids == NULL) MEM_ERROR(); @@ -213,7 +242,7 @@ int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk, int flag, l bytes & (SQUASHFS_METADATA_SIZE - 1); int length = read_block(fd, index[i], NULL, expected, ((unsigned char *) xattr_ids) + - (i * SQUASHFS_METADATA_SIZE)); + ((long long) i * SQUASHFS_METADATA_SIZE)); TRACE("Read xattr id table block %d, from 0x%llx, length " "%d\n", i, index[i], length); if(length == 0) { diff --git a/squashfs-tools/unsquash-4.c b/squashfs-tools/unsquash-4.c index 02b5cfc..a1da4c7 100644 --- a/squashfs-tools/unsquash-4.c +++ b/squashfs-tools/unsquash-4.c @@ -29,23 +29,55 @@ static struct squashfs_fragment_entry *fragment_table; static unsigned int *id_table; -static int read_fragment_table(long long *directory_table_end) +long long *alloc_index_table(int indexes) { + static long long *alloc_table = NULL; + static int alloc_size = 0; + int length = indexes * sizeof(long long); + + if(alloc_size < length) { + long long *table = realloc(alloc_table, length); + + if(table == NULL) + EXIT_UNSQUASH("alloc_index_table: failed to allocate " + "index table\n"); + + alloc_table = table; + alloc_size = length; + } + + return alloc_table; +} + +static int read_fragment_table(long long *table_start) +{ + /* + * Note on overflow limits: + * Size of SBlk.s.fragments is 2^32 (unsigned int) + * Max size of bytes is 2^32*16 or 2^36 + * Max indexes is (2^32*16)/8K or 2^23 + * Max length is ((2^32*16)/8K)*8 or 2^26 or 64M + */ int res, i; - size_t bytes = SQUASHFS_FRAGMENT_BYTES(sBlk.s.fragments); - size_t indexes = SQUASHFS_FRAGMENT_INDEXES(sBlk.s.fragments); + long long bytes = SQUASHFS_FRAGMENT_BYTES((long long) sBlk.s.fragments); + int indexes = SQUASHFS_FRAGMENT_INDEXES((long long) sBlk.s.fragments); + int length = SQUASHFS_FRAGMENT_INDEX_BYTES((long long) sBlk.s.fragments); long long *fragment_table_index; + /* + * The size of the index table (length bytes) should match the + * table start and end points + */ + if(length != (*table_start - sBlk.s.fragment_table_start)) { + ERROR("read_fragment_table: Bad fragment count in super block\n"); + return FALSE; + } + TRACE("read_fragment_table: %u fragments, reading %zu fragment indexes " "from 0x%llx\n", sBlk.s.fragments, indexes, sBlk.s.fragment_table_start); - if(sBlk.s.fragments == 0) { - *directory_table_end = sBlk.s.fragment_table_start; - return TRUE; - } - - fragment_table_index = malloc(indexes*sizeof(long long)); + fragment_table_index = alloc_index_table(indexes); if(fragment_table_index == NULL) EXIT_UNSQUASH("read_fragment_table: failed to allocate " "fragment table index\n"); @@ -55,9 +87,8 @@ static int read_fragment_table(long long *directory_table_end) EXIT_UNSQUASH("read_fragment_table: failed to allocate " "fragment table\n"); - res = read_fs_bytes(fd, sBlk.s.fragment_table_start, - SQUASHFS_FRAGMENT_INDEX_BYTES(sBlk.s.fragments), - fragment_table_index); + res = read_fs_bytes(fd, sBlk.s.fragment_table_start, length, + fragment_table_index); if(res == FALSE) { ERROR("read_fragment_table: failed to read fragment table " "index\n"); @@ -83,7 +114,7 @@ static int read_fragment_table(long long *directory_table_end) for(i = 0; i < sBlk.s.fragments; i++) SQUASHFS_INSWAP_FRAGMENT_ENTRY(&fragment_table[i]); - *directory_table_end = fragment_table_index[0]; + *table_start = fragment_table_index[0]; return TRUE; } @@ -361,25 +392,42 @@ corrupted: } -static int read_uids_guids(long long *table_start) +static int read_id_table(long long *table_start) { + /* + * Note on overflow limits: + * Size of SBlk.s.no_ids is 2^16 (unsigned short) + * Max size of bytes is 2^16*4 or 256K + * Max indexes is (2^16*4)/8K or 32 + * Max length is ((2^16*4)/8K)*8 or 256 + */ int res, i; int bytes = SQUASHFS_ID_BYTES(sBlk.s.no_ids); int indexes = SQUASHFS_ID_BLOCKS(sBlk.s.no_ids); - long long id_index_table[indexes]; + int length = SQUASHFS_ID_BLOCK_BYTES(sBlk.s.no_ids); + long long *id_index_table; - TRACE("read_uids_guids: no_ids %d\n", sBlk.s.no_ids); + /* + * The size of the index table (length bytes) should match the + * table start and end points + */ + if(length != (*table_start - sBlk.s.id_table_start)) { + ERROR("read_id_table: Bad id count in super block\n"); + return FALSE; + } + + TRACE("read_id_table: no_ids %d\n", sBlk.s.no_ids); + id_index_table = alloc_index_table(indexes); id_table = malloc(bytes); if(id_table == NULL) { - ERROR("read_uids_guids: failed to allocate id table\n"); + ERROR("read_id_table: failed to allocate id table\n"); return FALSE; } - res = read_fs_bytes(fd, sBlk.s.id_table_start, - SQUASHFS_ID_BLOCK_BYTES(sBlk.s.no_ids), id_index_table); + res = read_fs_bytes(fd, sBlk.s.id_table_start, length, id_index_table); if(res == FALSE) { - ERROR("read_uids_guids: failed to read id index table\n"); + ERROR("read_id_table: failed to read id index table\n"); return FALSE; } SQUASHFS_INSWAP_ID_BLOCKS(id_index_table, indexes); @@ -398,7 +446,7 @@ static int read_uids_guids(long long *table_start) res = read_block(fd, id_index_table[i], NULL, expected, ((char *) id_table) + i * SQUASHFS_METADATA_SIZE); if(res == FALSE) { - ERROR("read_uids_guids: failed to read id table block" + ERROR("read_id_table: failed to read id table block" "\n"); return FALSE; } @@ -412,12 +460,30 @@ static int read_uids_guids(long long *table_start) static int parse_exports_table(long long *table_start) { + /* + * Note on overflow limits: + * Size of SBlk.s.inodes is 2^32 (unsigned int) + * Max indexes is (2^32*8)/8K or 2^22 + * Max length is ((2^32*8)/8K)*8 or 2^25 + */ int res; - int indexes = SQUASHFS_LOOKUP_BLOCKS(sBlk.s.inodes); - long long export_index_table[indexes]; + int indexes = SQUASHFS_LOOKUP_BLOCKS((long long) sBlk.s.inodes); + int length = SQUASHFS_LOOKUP_BLOCK_BYTES((long long) sBlk.s.inodes); + long long *export_index_table; + + /* + * The size of the index table (length bytes) should match the + * table start and end points + */ + if(length != (*table_start - sBlk.s.lookup_table_start)) { + ERROR("parse_exports_table: Bad inode count in super block\n"); + return FALSE; + } - res = read_fs_bytes(fd, sBlk.s.lookup_table_start, - SQUASHFS_LOOKUP_BLOCK_BYTES(sBlk.s.inodes), export_index_table); + export_index_table = alloc_index_table(indexes); + + res = read_fs_bytes(fd, sBlk.s.lookup_table_start, length, + export_index_table); if(res == FALSE) { ERROR("parse_exports_table: failed to read export index table\n"); return FALSE; @@ -437,30 +503,118 @@ static int parse_exports_table(long long *table_start) int read_filesystem_tables_4() { - long long directory_table_end, table_start; + long long table_start; - if(read_xattrs_from_disk(fd, &sBlk.s, no_xattrs, &table_start) == 0) - return FALSE; + /* Read xattrs */ + if(sBlk.s.xattr_id_table_start != SQUASHFS_INVALID_BLK) { + /* sanity check super block contents */ + if(sBlk.s.xattr_id_table_start >= sBlk.s.bytes_used) { + ERROR("read_filesystem_tables: xattr id table start too large in super block\n"); + goto corrupted; + } - if(read_uids_guids(&table_start) == FALSE) - return FALSE; + if(read_xattrs_from_disk(fd, &sBlk.s, no_xattrs, &table_start) == 0) + goto corrupted; + } else + table_start = sBlk.s.bytes_used; - if(parse_exports_table(&table_start) == FALSE) - return FALSE; + /* Read id lookup table */ - if(read_fragment_table(&directory_table_end) == FALSE) - return FALSE; + /* Sanity check super block contents */ + if(sBlk.s.id_table_start >= table_start) { + ERROR("read_filesystem_tables: id table start too large in super block\n"); + goto corrupted; + } - if(read_inode_table(sBlk.s.inode_table_start, - sBlk.s.directory_table_start) == FALSE) - return FALSE; + /* there should always be at least one id */ + if(sBlk.s.no_ids == 0) { + ERROR("read_filesystem_tables: Bad id count in super block\n"); + goto corrupted; + } + + /* + * the number of ids can never be more than double the number of inodes + * (the maximum is a unique uid and gid for each inode). + */ + if(sBlk.s.no_ids > (sBlk.s.inodes * 2L)) { + ERROR("read_filesystem_tables: Bad id count in super block\n"); + goto corrupted; + } + + if(read_id_table(&table_start) == FALSE) + goto corrupted; + + /* Read exports table */ + if(sBlk.s.lookup_table_start != SQUASHFS_INVALID_BLK) { + + /* sanity check super block contents */ + if(sBlk.s.lookup_table_start >= table_start) { + ERROR("read_filesystem_tables: lookup table start too large in super block\n"); + goto corrupted; + } + + if(parse_exports_table(&table_start) == FALSE) + goto corrupted; + } + + /* Read fragment table */ + if(sBlk.s.fragments != 0) { + + /* Sanity check super block contents */ + if(sBlk.s.fragment_table_start >= table_start) { + ERROR("read_filesystem_tables: fragment table start too large in super block\n"); + goto corrupted; + } + + /* The number of fragments should not exceed the number of inodes */ + if(sBlk.s.fragments > sBlk.s.inodes) { + ERROR("read_filesystem_tables: Bad fragment count in super block\n"); + goto corrupted; + } + + if(read_fragment_table(&table_start) == FALSE) + goto corrupted; + } else { + /* + * Sanity check super block contents - with 0 fragments, + * the fragment table should be empty + */ + if(sBlk.s.fragment_table_start != table_start) { + ERROR("read_filesystem_tables: fragment table start invalid in super block\n"); + goto corrupted; + } + } + + /* Read directory table */ + + /* Sanity check super block contents */ + if(sBlk.s.directory_table_start >= table_start) { + ERROR("read_filesystem_tables: directory table start too large in super block\n"); + goto corrupted; + } if(read_directory_table(sBlk.s.directory_table_start, - directory_table_end) == FALSE) - return FALSE; + table_start) == FALSE) + goto corrupted; + + /* Read inode table */ + + /* Sanity check super block contents */ + if(sBlk.s.inode_table_start >= sBlk.s.directory_table_start) { + ERROR("read_filesystem_tables: inode table start too large in super block\n"); + goto corrupted; + } + + if(read_inode_table(sBlk.s.inode_table_start, + sBlk.s.directory_table_start) == FALSE) + goto corrupted; if(no_xattrs) sBlk.s.xattr_id_table_start = SQUASHFS_INVALID_BLK; return TRUE; + +corrupted: + ERROR("File system corruption detected\n"); + return FALSE; } -- 1.8.3.1