[PATCH v2 2/2] test: squashfs: add directory table overflow regression test

Shahriyar Jalayeri <[email protected]>
Newsgroups gmane.comp.boot-loaders.u-boot
Message-ID <[email protected]>
Add a Python test that lists a crafted SquashFS image whose directory
table declares an oversized metadata-block count. Such an image must be
rejected without corrupting the heap, which the test checks by confirming
U-Boot is still responsive afterwards.

Signed-off-by: Shahriyar Jalayeri <[email protected]>
---
 .../test_fs/test_squashfs/test_sqfs_overflow.py    | 75 ++++++++++++++++++++++
 1 file changed, 75 insertions(+)

diff --git a/test/py/tests/test_fs/test_squashfs/test_sqfs_overflow.py b/test/py/tests/test_fs/test_squashfs/test_sqfs_overflow.py
new file mode 100644
index 00000000000..df7f875a58f
--- /dev/null
+++ b/test/py/tests/test_fs/test_squashfs/test_sqfs_overflow.py
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0
+# Regression test for the SquashFS directory-table integer overflow.
+
+import os
+import struct
+import pytest
+
+# metadata block size (SQFS_METADATA_BLOCK_SIZE) and metadata header size
+SQFS_METADATA_BLOCK_SIZE = 8192
+# metablks_count that makes metablks_count * SQFS_METADATA_BLOCK_SIZE wrap a
+# 32-bit int back down to a tiny value: (2^19 + 1) * 8192 == 2^32 + 8192.
+NR_METABLKS = (1 << 19) + 1
+
+def make_overflow_image(path):
+    """Build a SquashFS image whose directory table inflates metablks_count so
+    that metablks_count * SQFS_METADATA_BLOCK_SIZE wraps a 32-bit int, then
+    writes one block of data one metadata block past the resulting buffer."""
+    def metahdr(size):
+        # uncompressed metadata block header (bit 15 set)
+        return struct.pack('<H', 0x8000 | (size & 0x7fff))
+
+    # inode table: one small valid uncompressed metadata block
+    inode_region = metahdr(32) + b'\x00' * 32
+    inode_start = 96
+    dir_start = inode_start + len(inode_region)
+
+    # directory table: NR_METABLKS metadata blocks, only block 1 carries data
+    dir_region = bytearray()
+    dir_region += metahdr(0)                          # block 0: empty
+    dir_region += metahdr(200) + b'A' * 200           # block 1: data
+    dir_region += metahdr(0) * (NR_METABLKS - 2)      # blocks 2..N-1: empty
+    frag_start = dir_start + len(dir_region)
+
+    sb = bytearray(96)
+    struct.pack_into('<I', sb, 0, 0x73717368)         # s_magic
+    struct.pack_into('<I', sb, 4, 1)                  # inodes
+    struct.pack_into('<I', sb, 12, 131072)            # block_size
+    struct.pack_into('<H', sb, 20, 1)                 # compression = gzip/zlib
+    struct.pack_into('<H', sb, 22, 17)                # block_log
+    struct.pack_into('<H', sb, 26, 1)                 # no_ids
+    struct.pack_into('<H', sb, 28, 4)                 # s_major
+    struct.pack_into('<Q', sb, 48, frag_start)        # id_table_start
+    struct.pack_into('<Q', sb, 56, 0xffffffffffffffff)  # xattr_id_table_start
+    struct.pack_into('<Q', sb, 64, inode_start)       # inode_table_start
+    struct.pack_into('<Q', sb, 72, dir_start)         # directory_table_start
+    struct.pack_into('<Q', sb, 80, frag_start)        # fragment_table_start
+    struct.pack_into('<Q', sb, 88, 0xffffffffffffffff)  # export_table_start
+
+    img = bytearray(sb) + inode_region + dir_region
+    # pad so the directory-table block read stays within the file
+    need = ((len(img) + 511) // 512 + 1) * 512
+    img += b'\x00' * (need - len(img))
+    struct.pack_into('<Q', img, 40, len(img))         # bytes_used
+
+    with open(path, 'wb') as f:
+        f.write(img)
+
[email protected]('sandbox')
[email protected]('cmd_squashfs')
[email protected]('fs_squashfs')
[email protected]
+def test_sqfs_ls_dir_table_overflow(ubman):
+    """Listing a crafted image whose directory table declares an oversized
+    metadata-block count must be rejected without corrupting the heap.
+    """
+    ubman.restart_uboot()
+    image_path = os.path.join(ubman.config.build_dir, 'sqfs_dir_table_overflow')
+    make_overflow_image(image_path)
+    try:
+        ubman.run_command('host bind 0 {}'.format(image_path))
+        ubman.run_command('sqfsls host 0')
+        # The crafted image must not take the board down.
+        assert 'alive' in ubman.run_command('echo alive')
+    finally:
+        os.remove(image_path)

-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.