CVE-2021-40153 (published 2021-08-27T15:15Z)

squashfs_opendir in unsquash-1.c in Squashfs-Tools 4.5 stores the filename in the directory entry; this is then used by unsquashfs to create the new file during the unsquash. The filename is not validated for traversal outside of the destination directory, and thus allows writing to locations outside of the destination.

Severity: MEDIUM
CVSS v2 base score: 5.8 (AV:N/AC:M/Au:N/C:N/I:P/A:P)
CVSS v3 base score: 8.1, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/plougher/squashfs-tools
commit_details: repo_url: https://github.com/plougher/squashfs-tools
author: Phillip Lougher
author_date: 2021-01-16 20:08:55+00:00
msg: Unsquashfs: fix write outside destination directory exploit

An issue on Github (https://github.com/plougher/squashfs-tools/issues/72)
shows how some specially crafted Squashfs filesystems containing
invalid file names (with '/' and ..) can cause Unsquashfs to write
files outside of the destination directory.

This commit fixes this exploit by checking all names for
validity.

In doing so I have also added checks for '.' and for names that
are shorter than they should be (names in the file system should
not have '\0' terminators).

Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
repository: repo_url: https://github.com/plougher/squashfs-tools
repo_name: plougher/squashfs-tools
description: tools to create and extract Squashfs filesystems
repo_language: C
owner: plougher
file_changes: filename: Makefile
change_type: ModificationType.MODIFY
diff: @@ -156,7 +156,8 @@ MKSQUASHFS_OBJS = mksquashfs.o read_fs.o action.o swap.o pseudo.o compressor.o \
 	caches-queues-lists.o
 
 UNSQUASHFS_OBJS = unsquashfs.o unsquash-1.o unsquash-2.o unsquash-3.o \
-	unsquash-4.o unsquash-123.o unsquash-34.o swap.o compressor.o unsquashfs_info.o
+	unsquash-4.o unsquash-123.o unsquash-34.o unsquash-1234.o swap.o \
+	compressor.o unsquashfs_info.o
 
 CFLAGS ?= -O2
 CFLAGS += $(EXTRA_CFLAGS) $(INCLUDEDIR) -D_FILE_OFFSET_BITS=64 \
@@ -350,6 +351,8 @@ unsquash-123.o: unsquashfs.h unsquash-123.c squashfs_fs.h squashfs_compat.h
 
 unsquash-34.o: unsquashfs.h unsquash-34.c
 
+unsquash-1234.o: unsquash-1234.c
+
 unsquashfs_xattr.o: unsquashfs_xattr.c unsquashfs.h squashfs_fs.h xattr.h
 
 unsquashfs_info.o: unsquashfs.h squashfs_fs.h

programming_language: Shell
filename: unsquash-1.c
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2011, 2012, 2019
+ * Copyright (c) 2009, 2010, 2011, 2012, 2019, 2021
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -285,6 +285,13 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
 			memcpy(dire->name, directory_table + bytes,
 				dire->size + 1);
 			dire->name[dire->size + 1] = '\0';
+
+			/* check name for invalid characters (i.e /, ., ..) */
+			if(check_name(dire->name, dire->size + 1) == FALSE) {
+				ERROR("File system corrupted: invalid characters in name\n");
+				goto corrupted;
+			}
+
 			TRACE("squashfs_opendir: directory entry %s, inode "
 				"%d:%d, type %d\n", dire->name,
 				dirh.start_block, dire->offset, dire->type);

programming_language: C
filename: unsquash-1234.c
change_type: ModificationType.ADD
diff: @@ -0,0 +1,58 @@
+/*
+ * Unsquash a squashfs filesystem.  This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-1234.c
+ *
+ * Helper functions used by unsquash-1, unsquash-2, unsquash-3 and
+ * unsquash-4.
+ */
+
+#define TRUE 1
+#define FALSE 0
+/*
+ * Check name for validity, name should not
+ *  - be ".", "./", or
+ *  - be "..", "../" or
+ *  - have a "/" anywhere in the name, or
+ *  - be shorter than the expected size
+ */
+int check_name(char *name, int size)
+{
+	char *start = name;
+
+	if(name[0] == '.') {
+		if(name[1] == '.')
+			name++;
+		if(name[1] == '/' || name[1] == '\0')
+			return FALSE;
+	}
+
+	while(name[0] != '/' && name[0] != '\0')
+		name ++;
+
+	if(name[0] == '/')
+		return FALSE;
+
+	if((name - start) != size)
+		return FALSE;
+
+	return TRUE;
+}

programming_language: C
filename: unsquash-2.c
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2013, 2019
+ * Copyright (c) 2009, 2010, 2013, 2019, 2021
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -386,6 +386,13 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
 			memcpy(dire->name, directory_table + bytes,
 				dire->size + 1);
 			dire->name[dire->size + 1] = '\0';
+
+			/* check name for invalid characters (i.e /, ., ..) */
+			if(check_name(dire->name, dire->size + 1) == FALSE) {
+				ERROR("File system corrupted: invalid characters in name\n");
+				goto corrupted;
+			}
+
 			TRACE("squashfs_opendir: directory entry %s, inode "
 				"%d:%d, type %d\n", dire->name,
 				dirh.start_block, dire->offset, dire->type);

programming_language: C
filename: unsquash-3.c
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2019
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2019, 2021
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -413,6 +413,13 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
 			memcpy(dire->name, directory_table + bytes,
 				dire->size + 1);
 			dire->name[dire->size + 1] = '\0';
+
+			/* check name for invalid characters (i.e /, ., ..) */
+			if(check_name(dire->name, dire->size + 1) == FALSE) {
+				ERROR("File system corrupted: invalid characters in name\n");
+				goto corrupted;
+			}
+
 			TRACE("squashfs_opendir: directory entry %s, inode "
 				"%d:%d, type %d\n", dire->name,
 				dirh.start_block, dire->offset, dire->type);

programming_language: C
filename: unsquash-4.c
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2019
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2019, 2021
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -349,6 +349,13 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
 			memcpy(dire->name, directory_table + bytes,
 				dire->size + 1);
 			dire->name[dire->size + 1] = '\0';
+
+			/* check name for invalid characters (i.e /, ., ..) */
+			if(check_name(dire->name, dire->size + 1) == FALSE) {
+				ERROR("File system corrupted: invalid characters in name\n");
+				goto corrupted;
+			}
+
 			TRACE("squashfs_opendir: directory entry %s, inode "
 				"%d:%d, type %d\n", dire->name,
 				dirh.start_block, dire->offset, dire->type);

programming_language: C
filename: unsquashfs.c
change_type: ModificationType.MODIFY
diff: @@ -2670,7 +2670,7 @@ int parse_number(char *start, int *res)
 
 
 #define VERSION() \
-	printf("unsquashfs version 4.4-git (2021/01/09)\n");\
+	printf("unsquashfs version 4.4-git (2021/01/17)\n");\
 	printf("copyright (C) 2021 Phillip Lougher "\
 		"<phillip@squashfs.org.uk>\n\n");\
     	printf("This program is free software; you can redistribute it and/or"\

programming_language: C
filename: unsquashfs.h
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@
  * Unsquash a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2013, 2014, 2019
+ * Copyright (c) 2009, 2010, 2013, 2014, 2019, 2021
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -261,4 +261,7 @@ extern int read_ids(int, long long, long long, unsigned int **);
 
 /* unsquash-34.c */
 extern long long *alloc_index_table(int);
+
+/* unsquash-1234.c */
+extern int check_name(char *, int);
 #endif

programming_language: C