[PATCH] squashfs: Fix stack overflow while symlink resolving
authorRichard Weinberger <richard@nod.at>
Fri, 2 Aug 2024 16:36:47 +0000 (18:36 +0200)
committerDaniel Leidert <dleidert@debian.org>
Wed, 30 Apr 2025 23:19:02 +0000 (01:19 +0200)
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>
Reviewed-By: Daniel Leidert <dleidert@debian.org>
Origin: https://source.denx.de/u-boot/u-boot/-/commit/4f5cc096bfd0a591f8a11e86999e3d90a9484c34
Bug: https://www.openwall.com/lists/oss-security/2025/02/17/2
Bug-Debian: https://bugs.debian.org/1098254
Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2024-57257
Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2024-57257

Gbp-Pq: Name CVE-2024-57257.patch

fs/squashfs/sqfs.c

index 3cd472411b5d63ae26462f24081002dd8e2db036..239afecd3f28340cf06783a608c627d1b7eff2d7 100644 (file)
 #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)
 {
@@ -502,7 +507,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;
@@ -527,6 +532,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);
@@ -872,7 +882,7 @@ out:
        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;
@@ -967,7 +977,19 @@ out:
        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;
@@ -1310,8 +1332,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, *data_buffer = NULL;
        char *fragment = NULL, *file = NULL, *resolved, *data;
@@ -1341,11 +1363,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;
        }
@@ -1353,7 +1375,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;
@@ -1402,9 +1424,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:
@@ -1566,7 +1593,14 @@ out:
        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;
@@ -1582,10 +1616,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;
@@ -1593,7 +1627,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;
@@ -1626,6 +1660,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);
@@ -1665,10 +1704,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;
@@ -1676,7 +1716,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;
@@ -1693,6 +1733,12 @@ free_strings:
        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);