CVE: CVE-2021-41072
|
Upstream-Status: Backport [https://github.com/plougher/squashfs-tools/commit/e048580]
|
|
Update on 20211109:
|
Squash a follow-up fix for CVE-2021-41072 from upstream:
|
https://github.com/plougher/squashfs-tools/commit/19fcc93
|
|
Signed-off-by: Kai Kang <kai.kang@windriver.com>
|
|
From e0485802ec72996c20026da320650d8362f555bd Mon Sep 17 00:00:00 2001
|
From: Phillip Lougher <phillip@squashfs.org.uk>
|
Date: Sun, 12 Sep 2021 23:50:06 +0100
|
Subject: [PATCH] Unsquashfs: additional write outside destination directory
|
exploit fix
|
|
An issue on github (https://github.com/plougher/squashfs-tools/issues/72)
|
showed how some specially crafted Squashfs filesystems containing
|
invalid file names (with '/' and '..') can cause Unsquashfs to write
|
files outside of the destination directory.
|
|
Since then it has been shown that specially crafted Squashfs filesystems
|
that contain a symbolic link pointing outside of the destination directory,
|
coupled with an identically named file within the same directory, can
|
cause Unsquashfs to write files outside of the destination directory.
|
|
Specifically the symbolic link produces a pathname pointing outside
|
of the destination directory, which is then followed when writing the
|
duplicate identically named file within the directory.
|
|
This commit fixes this exploit by explictly checking for duplicate
|
filenames within a directory. As directories in v2.1, v3.x, and v4.0
|
filesystems are sorted, this is achieved by checking for consecutively
|
identical filenames. Additionally directories are checked to
|
ensure they are sorted, to avoid attempts to evade the duplicate
|
check.
|
|
Version 1.x and 2.0 filesystems (where the directories were unsorted)
|
are sorted and then the above duplicate filename check is applied.
|
|
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
|
---
|
squashfs-tools/Makefile | 6 +-
|
squashfs-tools/unsquash-1.c | 6 ++
|
squashfs-tools/unsquash-12.c | 110 +++++++++++++++++++++++++++++++++
|
squashfs-tools/unsquash-1234.c | 21 +++++++
|
squashfs-tools/unsquash-2.c | 16 +++++
|
squashfs-tools/unsquash-3.c | 6 ++
|
squashfs-tools/unsquash-4.c | 6 ++
|
squashfs-tools/unsquashfs.h | 4 ++
|
8 files changed, 173 insertions(+), 2 deletions(-)
|
create mode 100644 squashfs-tools/unsquash-12.c
|
|
diff --git a/squashfs-tools/Makefile b/squashfs-tools/Makefile
|
index 7262a2e..1b544ed 100755
|
--- a/squashfs-tools/Makefile
|
+++ b/squashfs-tools/Makefile
|
@@ -160,8 +160,8 @@ MKSQUASHFS_OBJS = mksquashfs.o read_fs.o action.o swap.o pseudo.o compressor.o \
|
caches-queues-lists.o reader.o tar.o
|
|
UNSQUASHFS_OBJS = unsquashfs.o unsquash-1.o unsquash-2.o unsquash-3.o \
|
- unsquash-4.o unsquash-123.o unsquash-34.o unsquash-1234.o swap.o \
|
- compressor.o unsquashfs_info.o
|
+ unsquash-4.o unsquash-123.o unsquash-34.o unsquash-1234.o unsquash-12.o \
|
+ swap.o compressor.o unsquashfs_info.o
|
|
CFLAGS ?= -O2
|
CFLAGS += $(EXTRA_CFLAGS) $(INCLUDEDIR) -D_FILE_OFFSET_BITS=64 \
|
@@ -393,6 +393,8 @@ unsquash-34.o: unsquashfs.h unsquash-34.c unsquashfs_error.h
|
|
unsquash-1234.o: unsquash-1234.c unsquashfs_error.h
|
|
+unsquash-12.o: unsquash-12.c unsquashfs.h
|
+
|
unsquashfs_xattr.o: unsquashfs_xattr.c unsquashfs.h squashfs_fs.h xattr.h unsquashfs_error.h
|
|
unsquashfs_info.o: unsquashfs.h squashfs_fs.h unsquashfs_error.h
|
diff --git a/squashfs-tools/unsquash-1.c b/squashfs-tools/unsquash-1.c
|
index b604434..88866fc 100644
|
--- a/squashfs-tools/unsquash-1.c
|
+++ b/squashfs-tools/unsquash-1.c
|
@@ -370,6 +370,12 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
|
}
|
}
|
|
+ /* check directory for duplicate names. Need to sort directory first */
|
+ sort_directory(dir);
|
+ if(check_directory(dir) == FALSE) {
|
+ ERROR("File system corrupted: directory has duplicate names\n");
|
+ goto corrupted;
|
+ }
|
return dir;
|
|
corrupted:
|
diff --git a/squashfs-tools/unsquash-12.c b/squashfs-tools/unsquash-12.c
|
new file mode 100644
|
index 0000000..61bf128
|
--- /dev/null
|
+++ b/squashfs-tools/unsquash-12.c
|
@@ -0,0 +1,110 @@
|
+/*
|
+ * 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-12.c
|
+ *
|
+ * Helper functions used by unsquash-1 and unsquash-2.
|
+ */
|
+
|
+#include "unsquashfs.h"
|
+
|
+/*
|
+ * Bottom up linked list merge sort.
|
+ *
|
+ */
|
+void sort_directory(struct dir *dir)
|
+{
|
+ struct dir_ent *cur, *l1, *l2, *next;
|
+ int len1, len2, stride = 1;
|
+
|
+ if(dir->dir_count < 2)
|
+ return;
|
+
|
+ /*
|
+ * We can consider our linked-list to be made up of stride length
|
+ * sublists. Eacn iteration around this loop merges adjacent
|
+ * stride length sublists into larger 2*stride sublists. We stop
|
+ * when stride becomes equal to the entire list.
|
+ *
|
+ * Initially stride = 1 (by definition a sublist of 1 is sorted), and
|
+ * these 1 element sublists are merged into 2 element sublists, which
|
+ * are then merged into 4 element sublists and so on.
|
+ */
|
+ do {
|
+ l2 = dir->dirs; /* head of current linked list */
|
+ cur = NULL; /* empty output list */
|
+
|
+ /*
|
+ * Iterate through the linked list, merging adjacent sublists.
|
+ * On each interation l2 points to the next sublist pair to be
|
+ * merged (if there's only one sublist left this is simply added
|
+ * to the output list)
|
+ */
|
+ while(l2) {
|
+ l1 = l2;
|
+ for(len1 = 0; l2 && len1 < stride; len1 ++, l2 = l2->next);
|
+ len2 = stride;
|
+
|
+ /*
|
+ * l1 points to first sublist.
|
+ * l2 points to second sublist.
|
+ * Merge them onto the output list
|
+ */
|
+ while(len1 && l2 && len2) {
|
+ if(strcmp(l1->name, l2->name) <= 0) {
|
+ next = l1;
|
+ l1 = l1->next;
|
+ len1 --;
|
+ } else {
|
+ next = l2;
|
+ l2 = l2->next;
|
+ len2 --;
|
+ }
|
+
|
+ if(cur) {
|
+ cur->next = next;
|
+ cur = next;
|
+ } else
|
+ dir->dirs = cur = next;
|
+ }
|
+ /*
|
+ * One sublist is now empty, copy the other one onto the
|
+ * output list
|
+ */
|
+ for(; len1; len1 --, l1 = l1->next) {
|
+ if(cur) {
|
+ cur->next = l1;
|
+ cur = l1;
|
+ } else
|
+ dir->dirs = cur = l1;
|
+ }
|
+ for(; l2 && len2; len2 --, l2 = l2->next) {
|
+ if(cur) {
|
+ cur->next = l2;
|
+ cur = l2;
|
+ } else
|
+ dir->dirs = cur = l2;
|
+ }
|
+ }
|
+ cur->next = NULL;
|
+ stride = stride << 1;
|
+ } while(stride < dir->dir_count);
|
+}
|
diff --git a/squashfs-tools/unsquash-1234.c b/squashfs-tools/unsquash-1234.c
|
index e389f8d..98a81ed 100644
|
--- a/squashfs-tools/unsquash-1234.c
|
+++ b/squashfs-tools/unsquash-1234.c
|
@@ -72,3 +72,24 @@ void squashfs_closedir(struct dir *dir)
|
|
free(dir);
|
}
|
+
|
+
|
+/*
|
+ * Check directory for duplicate names. As the directory should be sorted,
|
+ * duplicates will be consecutive. Obviously we also need to check if the
|
+ * directory has been deliberately unsorted, to evade this check.
|
+ */
|
+int check_directory(struct dir *dir)
|
+{
|
+ int i;
|
+ struct dir_ent *ent;
|
+
|
+ if(dir->dir_count < 2)
|
+ return TRUE;
|
+
|
+ for(ent = dir->dirs, i = 0; i < dir->dir_count - 1; ent = ent->next, i++)
|
+ if(strcmp(ent->name, ent->next->name) >= 0)
|
+ return FALSE;
|
+
|
+ return TRUE;
|
+}
|
diff --git a/squashfs-tools/unsquash-2.c b/squashfs-tools/unsquash-2.c
|
index 956f96f..0e36f7d 100644
|
--- a/squashfs-tools/unsquash-2.c
|
+++ b/squashfs-tools/unsquash-2.c
|
@@ -29,6 +29,7 @@
|
static squashfs_fragment_entry_2 *fragment_table;
|
static unsigned int *uid_table, *guid_table;
|
static squashfs_operations ops;
|
+static int needs_sorting = FALSE;
|
|
|
static void read_block_list(unsigned int *block_list, long long start,
|
@@ -463,6 +464,17 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
|
}
|
}
|
|
+ if(needs_sorting)
|
+ sort_directory(dir);
|
+
|
+ /* check directory for duplicate names and sorting */
|
+ if(check_directory(dir) == FALSE) {
|
+ if(needs_sorting)
|
+ ERROR("File system corrupted: directory has duplicate names\n");
|
+ else
|
+ ERROR("File system corrupted: directory has duplicate names or is unsorted\n");
|
+ goto corrupted;
|
+ }
|
return dir;
|
|
corrupted:
|
@@ -596,6 +608,10 @@ int read_super_2(squashfs_operations **s_ops, void *s)
|
* 2.x filesystems use gzip compression.
|
*/
|
comp = lookup_compressor("gzip");
|
+
|
+ if(sBlk_3->s_minor == 0)
|
+ needs_sorting = TRUE;
|
+
|
return TRUE;
|
}
|
|
diff --git a/squashfs-tools/unsquash-3.c b/squashfs-tools/unsquash-3.c
|
index 835a574..0123562 100644
|
--- a/squashfs-tools/unsquash-3.c
|
+++ b/squashfs-tools/unsquash-3.c
|
@@ -497,6 +497,12 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
|
}
|
}
|
|
+ /* check directory for duplicate names and sorting */
|
+ if(check_directory(dir) == FALSE) {
|
+ ERROR("File system corrupted: directory has duplicate names or is unsorted\n");
|
+ goto corrupted;
|
+ }
|
+
|
return dir;
|
|
corrupted:
|
diff --git a/squashfs-tools/unsquash-4.c b/squashfs-tools/unsquash-4.c
|
index 694783d..c615bb8 100644
|
--- a/squashfs-tools/unsquash-4.c
|
+++ b/squashfs-tools/unsquash-4.c
|
@@ -434,6 +434,12 @@ static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offse
|
}
|
}
|
|
+ /* check directory for duplicate names and sorting */
|
+ if(check_directory(dir) == FALSE) {
|
+ ERROR("File system corrupted: directory has duplicate names or is unsorted\n");
|
+ goto corrupted;
|
+ }
|
+
|
return dir;
|
|
corrupted:
|
diff --git a/squashfs-tools/unsquashfs.h b/squashfs-tools/unsquashfs.h
|
index f8cf78c..bf2a80d 100644
|
--- a/squashfs-tools/unsquashfs.h
|
+++ b/squashfs-tools/unsquashfs.h
|
@@ -293,4 +293,8 @@ extern long long *alloc_index_table(int);
|
/* unsquash-1234.c */
|
extern int check_name(char *, int);
|
extern void squashfs_closedir(struct dir *);
|
+extern int check_directory(struct dir *);
|
+
|
+/* unsquash-12.c */
|
+extern void sort_directory(struct dir *);
|
#endif
|
--
|
2.17.1
|