221 lines
7.1 KiB
Diff
221 lines
7.1 KiB
Diff
From 4f5cc096bfd0a591f8a11e86999e3d90a9484c34 Mon Sep 17 00:00:00 2001
|
|
From: Richard Weinberger <richard@nod.at>
|
|
Date: Fri, 2 Aug 2024 18:36:47 +0200
|
|
Subject: [PATCH] squashfs: Fix stack overflow while symlink resolving
|
|
|
|
The squashfs driver blindly follows symlinks, and calls sqfs_size()
|
|
recursively. So an attacker can create a crafted filesystem and with
|
|
a deep enough nesting level a stack overflow can be achieved.
|
|
|
|
Fix by limiting the nesting level to 8.
|
|
|
|
Signed-off-by: Richard Weinberger <richard@nod.at>
|
|
Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
|
---
|
|
fs/squashfs/sqfs.c | 76 +++++++++++++++++++++++++++++++++++++---------
|
|
1 file changed, 61 insertions(+), 15 deletions(-)
|
|
|
|
diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c
|
|
index fa99d514f20f..af7ff80a7bdf 100644
|
|
--- a/fs/squashfs/sqfs.c
|
|
+++ b/fs/squashfs/sqfs.c
|
|
@@ -24,7 +24,12 @@
|
|
#include "sqfs_filesystem.h"
|
|
#include "sqfs_utils.h"
|
|
|
|
+#define MAX_SYMLINK_NEST 8
|
|
+
|
|
static struct squashfs_ctxt ctxt;
|
|
+static int symlinknest;
|
|
+
|
|
+static int sqfs_readdir_nest(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp);
|
|
|
|
static int sqfs_disk_read(__u32 block, __u32 nr_blocks, void *buf)
|
|
{
|
|
@@ -510,7 +515,7 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
|
|
goto out;
|
|
}
|
|
|
|
- while (!sqfs_readdir(dirsp, &dent)) {
|
|
+ while (!sqfs_readdir_nest(dirsp, &dent)) {
|
|
ret = strcmp(dent->name, token_list[j]);
|
|
if (!ret)
|
|
break;
|
|
@@ -537,6 +542,11 @@ static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
|
|
|
|
/* Check for symbolic link and inode type sanity */
|
|
if (get_unaligned_le16(&dir->inode_type) == SQFS_SYMLINK_TYPE) {
|
|
+ if (++symlinknest == MAX_SYMLINK_NEST) {
|
|
+ ret = -ELOOP;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
sym = (struct squashfs_symlink_inode *)table;
|
|
/* Get first j + 1 tokens */
|
|
path = sqfs_concat_tokens(token_list, j + 1);
|
|
@@ -884,7 +894,7 @@ static int sqfs_read_directory_table(unsigned char **dir_table, u32 **pos_list)
|
|
return metablks_count;
|
|
}
|
|
|
|
-int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp)
|
|
+static int sqfs_opendir_nest(const char *filename, struct fs_dir_stream **dirsp)
|
|
{
|
|
unsigned char *inode_table = NULL, *dir_table = NULL;
|
|
int j, token_count = 0, ret = 0, metablks_count;
|
|
@@ -979,7 +989,19 @@ int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp)
|
|
return ret;
|
|
}
|
|
|
|
+int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp)
|
|
+{
|
|
+ symlinknest = 0;
|
|
+ return sqfs_opendir_nest(filename, dirsp);
|
|
+}
|
|
+
|
|
int sqfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
|
|
+{
|
|
+ symlinknest = 0;
|
|
+ return sqfs_readdir_nest(fs_dirs, dentp);
|
|
+}
|
|
+
|
|
+static int sqfs_readdir_nest(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
|
|
{
|
|
struct squashfs_super_block *sblk = ctxt.sblk;
|
|
struct squashfs_dir_stream *dirs;
|
|
@@ -1325,8 +1347,8 @@ static int sqfs_get_lregfile_info(struct squashfs_lreg_inode *lreg,
|
|
return datablk_count;
|
|
}
|
|
|
|
-int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len,
|
|
- loff_t *actread)
|
|
+static int sqfs_read_nest(const char *filename, void *buf, loff_t offset,
|
|
+ loff_t len, loff_t *actread)
|
|
{
|
|
char *dir = NULL, *fragment_block, *datablock = NULL;
|
|
char *fragment = NULL, *file = NULL, *resolved, *data;
|
|
@@ -1356,11 +1378,11 @@ int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len,
|
|
}
|
|
|
|
/*
|
|
- * sqfs_opendir will uncompress inode and directory tables, and will
|
|
+ * sqfs_opendir_nest will uncompress inode and directory tables, and will
|
|
* return a pointer to the directory that contains the requested file.
|
|
*/
|
|
sqfs_split_path(&file, &dir, filename);
|
|
- ret = sqfs_opendir(dir, &dirsp);
|
|
+ ret = sqfs_opendir_nest(dir, &dirsp);
|
|
if (ret) {
|
|
goto out;
|
|
}
|
|
@@ -1368,7 +1390,7 @@ int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len,
|
|
dirs = (struct squashfs_dir_stream *)dirsp;
|
|
|
|
/* For now, only regular files are able to be loaded */
|
|
- while (!sqfs_readdir(dirsp, &dent)) {
|
|
+ while (!sqfs_readdir_nest(dirsp, &dent)) {
|
|
ret = strcmp(dent->name, file);
|
|
if (!ret)
|
|
break;
|
|
@@ -1421,9 +1443,14 @@ int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len,
|
|
break;
|
|
case SQFS_SYMLINK_TYPE:
|
|
case SQFS_LSYMLINK_TYPE:
|
|
+ if (++symlinknest == MAX_SYMLINK_NEST) {
|
|
+ ret = -ELOOP;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
symlink = (struct squashfs_symlink_inode *)ipos;
|
|
resolved = sqfs_resolve_symlink(symlink, filename);
|
|
- ret = sqfs_read(resolved, buf, offset, len, actread);
|
|
+ ret = sqfs_read_nest(resolved, buf, offset, len, actread);
|
|
free(resolved);
|
|
goto out;
|
|
case SQFS_BLKDEV_TYPE:
|
|
@@ -1594,7 +1621,14 @@ int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len,
|
|
return ret;
|
|
}
|
|
|
|
-int sqfs_size(const char *filename, loff_t *size)
|
|
+int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len,
|
|
+ loff_t *actread)
|
|
+{
|
|
+ symlinknest = 0;
|
|
+ return sqfs_read_nest(filename, buf, offset, len, actread);
|
|
+}
|
|
+
|
|
+static int sqfs_size_nest(const char *filename, loff_t *size)
|
|
{
|
|
struct squashfs_super_block *sblk = ctxt.sblk;
|
|
struct squashfs_symlink_inode *symlink;
|
|
@@ -1610,10 +1644,10 @@ int sqfs_size(const char *filename, loff_t *size)
|
|
|
|
sqfs_split_path(&file, &dir, filename);
|
|
/*
|
|
- * sqfs_opendir will uncompress inode and directory tables, and will
|
|
+ * sqfs_opendir_nest will uncompress inode and directory tables, and will
|
|
* return a pointer to the directory that contains the requested file.
|
|
*/
|
|
- ret = sqfs_opendir(dir, &dirsp);
|
|
+ ret = sqfs_opendir_nest(dir, &dirsp);
|
|
if (ret) {
|
|
ret = -EINVAL;
|
|
goto free_strings;
|
|
@@ -1621,7 +1655,7 @@ int sqfs_size(const char *filename, loff_t *size)
|
|
|
|
dirs = (struct squashfs_dir_stream *)dirsp;
|
|
|
|
- while (!sqfs_readdir(dirsp, &dent)) {
|
|
+ while (!sqfs_readdir_nest(dirsp, &dent)) {
|
|
ret = strcmp(dent->name, file);
|
|
if (!ret)
|
|
break;
|
|
@@ -1661,6 +1695,11 @@ int sqfs_size(const char *filename, loff_t *size)
|
|
break;
|
|
case SQFS_SYMLINK_TYPE:
|
|
case SQFS_LSYMLINK_TYPE:
|
|
+ if (++symlinknest == MAX_SYMLINK_NEST) {
|
|
+ *size = 0;
|
|
+ return -ELOOP;
|
|
+ }
|
|
+
|
|
symlink = (struct squashfs_symlink_inode *)ipos;
|
|
resolved = sqfs_resolve_symlink(symlink, filename);
|
|
ret = sqfs_size(resolved, size);
|
|
@@ -1700,10 +1739,11 @@ int sqfs_exists(const char *filename)
|
|
|
|
sqfs_split_path(&file, &dir, filename);
|
|
/*
|
|
- * sqfs_opendir will uncompress inode and directory tables, and will
|
|
+ * sqfs_opendir_nest will uncompress inode and directory tables, and will
|
|
* return a pointer to the directory that contains the requested file.
|
|
*/
|
|
- ret = sqfs_opendir(dir, &dirsp);
|
|
+ symlinknest = 0;
|
|
+ ret = sqfs_opendir_nest(dir, &dirsp);
|
|
if (ret) {
|
|
ret = -EINVAL;
|
|
goto free_strings;
|
|
@@ -1711,7 +1751,7 @@ int sqfs_exists(const char *filename)
|
|
|
|
dirs = (struct squashfs_dir_stream *)dirsp;
|
|
|
|
- while (!sqfs_readdir(dirsp, &dent)) {
|
|
+ while (!sqfs_readdir_nest(dirsp, &dent)) {
|
|
ret = strcmp(dent->name, file);
|
|
if (!ret)
|
|
break;
|
|
@@ -1728,6 +1768,12 @@ int sqfs_exists(const char *filename)
|
|
return ret == 0;
|
|
}
|
|
|
|
+int sqfs_size(const char *filename, loff_t *size)
|
|
+{
|
|
+ symlinknest = 0;
|
|
+ return sqfs_size_nest(filename, size);
|
|
+}
|
|
+
|
|
void sqfs_close(void)
|
|
{
|
|
sqfs_decompressor_cleanup(&ctxt);
|