fs/romfs/super.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
A ROMFS directory stores the first entry offset in ri.spec and links its
entries through the next field in each on-disk file header. Both
romfs_lookup() and romfs_readdir() follow this image-controlled chain
without checking for cycles.
A crafted image can point an entry back to itself or to an earlier entry.
Looking up a missing name then loops forever in romfs_lookup(), consuming a
CPU in kernel context. This is independent of hard-link cycle handling:
directory next pointers and hard-link spec pointers are separate chains.
Validate each directory entry chain when the directory inode is
instantiated. Use Floyd cycle detection so validation needs constant
memory and does not impose an arbitrary directory size limit. Reject a
cycle with -ELOOP before lookup or readdir can traverse it.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Sangho Lee <kudo3228@gmail.com>
---
fs/romfs/super.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/fs/romfs/super.c b/fs/romfs/super.c
index ac55193bf..061275761 100644
--- a/fs/romfs/super.c
+++ b/fs/romfs/super.c
@@ -96,6 +96,51 @@ static const unsigned char romfs_dtype_table[] = {
static struct inode *romfs_iget(struct super_block *sb, unsigned long pos);
+static int romfs_next_dirent(struct super_block *sb, unsigned long pos,
+ unsigned long maxoff, unsigned long *next)
+{
+ struct romfs_inode ri;
+ int ret;
+
+ if (!pos || pos >= maxoff) {
+ *next = 0;
+ return 0;
+ }
+
+ ret = romfs_dev_read(sb, pos, &ri, ROMFH_SIZE);
+ if (ret < 0)
+ return ret;
+
+ *next = be32_to_cpu(ri.next) & ROMFH_MASK;
+ return 0;
+}
+
+static int romfs_validate_dir_chain(struct super_block *sb, unsigned long pos)
+{
+ unsigned long maxoff = romfs_maxsize(sb);
+ unsigned long slow = pos;
+ unsigned long fast = pos;
+ int ret;
+
+ for (;;) {
+ ret = romfs_next_dirent(sb, slow, maxoff, &slow);
+ if (ret < 0)
+ return ret;
+
+ ret = romfs_next_dirent(sb, fast, maxoff, &fast);
+ if (ret < 0)
+ return ret;
+ ret = romfs_next_dirent(sb, fast, maxoff, &fast);
+ if (ret < 0)
+ return ret;
+
+ if (!slow || !fast)
+ return 0;
+ if (slow == fast)
+ return -ELOOP;
+ }
+}
+
/*
* read a page worth of data from the image
*/
@@ -292,6 +337,13 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
pos = be32_to_cpu(ri.spec) & ROMFH_MASK;
}
+ if ((nextfh & ROMFH_TYPE) == ROMFH_DIR) {
+ ret = romfs_validate_dir_chain(sb,
+ be32_to_cpu(ri.spec) & ROMFH_MASK);
+ if (ret < 0)
+ goto error;
+ }
+
/* determine the length of the filename */
nlen = romfs_dev_strnlen(sb, pos + ROMFH_SIZE, ROMFS_MAXFN);
if (IS_ERR_VALUE(nlen))
--
2.43.0
© 2016 - 2026 Red Hat, Inc.