diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 4307cb8acdc..3db6ba6c219 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -381,6 +381,7 @@ CONFIG_FS_CBFS=y
 CONFIG_FS_EXFAT=y
 CONFIG_EXT4_WRITE=y
 CONFIG_FS_EXT4L=y
+CONFIG_FS_ISOFS=y
 CONFIG_FS_CRAMFS=y
 CONFIG_ADDR_MAP=y
 CONFIG_BACKTRACE=y
diff --git a/doc/build/gcc.rst b/doc/build/gcc.rst
index 847b39b0d7b..0b6db0b2317 100644
--- a/doc/build/gcc.rst
+++ b/doc/build/gcc.rst
@@ -24,7 +24,8 @@ Depending on the build targets further packages maybe needed
 .. code-block:: bash
 
     sudo apt-get install acpica-tools bc bison build-essential coccinelle \
-      device-tree-compiler dfu-util efitools flex gdisk graphviz imagemagick \
+      device-tree-compiler dfu-util efitools flex gdisk genisoimage \
+      graphviz imagemagick \
       libgnutls28-dev libguestfs-tools libncurses-dev \
       libpython3-dev libsdl2-dev libssl-dev lz4 lzma lzma-alone openssl \
       pkg-config python3 python3-asteval python3-coverage python3-filelock \
diff --git a/test/fs/Makefile b/test/fs/Makefile
index 0e7d167a345..e07b9dc1367 100644
--- a/test/fs/Makefile
+++ b/test/fs/Makefile
@@ -3,3 +3,4 @@
 obj-y += fs_basic.o
 obj-$(CONFIG_FS_EXT4L) += ext4l.o
 obj-$(CONFIG_$(PHASE_)VFS) += vfs.o
+obj-$(CONFIG_FS_ISOFS) += vfs_iso.o
diff --git a/test/fs/vfs_iso.c b/test/fs/vfs_iso.c
new file mode 100644
index 00000000000..79fab28956f
--- /dev/null
+++ b/test/fs/vfs_iso.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * VFS tests for ISO 9660 filesystems
+ *
+ * These tests are marked UTF_MANUAL and are called from test_vfs.py
+ * which creates the ISO filesystem image using genisoimage.
+ *
+ * Copyright 2026 Simon Glass <sjg@chromium.org>
+ */
+
+#include <blk.h>
+#include <command.h>
+#include <dm.h>
+#include <env.h>
+#include <fs.h>
+#include <mapmem.h>
+#include <sandbox_host.h>
+#include <vfs.h>
+#include <dm/device-internal.h>
+#include <test/fs.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+#define VFS_ARG_IMAGE	0	/* fs_image: path to filesystem image */
+#define BUF_ADDR	0x10000
+
+/**
+ * iso_mount_image() - Bind a host image and mount it via VFS
+ *
+ * @uts: Unit test state
+ * @image: Path to filesystem image
+ * @devp: Returns the host device
+ * Return: 0 on success
+ */
+static int iso_mount_image(struct unit_test_state *uts, const char *image,
+			   struct udevice **devp)
+{
+	struct udevice *blk;
+	struct blk_desc *desc;
+
+	ut_assertok(vfs_init());
+	ut_assertok(host_create_device("isotest", true, DEFAULT_BLKSZ, devp));
+	ut_assertok(host_attach_file(*devp, image));
+	ut_assertok(blk_get_from_parent(*devp, &blk));
+	ut_assertok(device_probe(blk));
+	desc = dev_get_uclass_plat(blk);
+
+	ut_assertok(run_commandf("mount host %x:0 /mnt", desc->devnum));
+	ut_assert_console_end();
+
+	return 0;
+}
+
+/**
+ * iso_umount_image() - Unmount and clean up a host image
+ *
+ * @uts: Unit test state
+ * @dev: The host device to detach
+ * Return: 0 on success
+ */
+static int iso_umount_image(struct unit_test_state *uts, struct udevice *dev)
+{
+	ut_assertok(run_command("umount /mnt", 0));
+	ut_assert_console_end();
+	ut_assertok(host_detach_file(dev));
+	ut_assertok(device_unbind(dev));
+
+	return 0;
+}
+
+/* Test ls on VFS-mounted ISO filesystem */
+static int fs_test_vfs_iso_ls_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(VFS_ARG_IMAGE);
+	struct udevice *dev;
+
+	ut_assertok(iso_mount_image(uts, fs_image, &dev));
+
+	ut_assertok(run_command("ls /mnt", 0));
+	ut_assert_nextline("DIR          0 .");
+	ut_assert_nextline("DIR          0 ..");
+	ut_assert_nextline("DIR          0 subdir");
+	ut_assert_nextline("            12 testfile.txt");
+	ut_assert_console_end();
+
+	ut_assertok(iso_umount_image(uts, dev));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_vfs_iso_ls_norun,
+	     UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
+
+/* Test cat on VFS-mounted ISO filesystem */
+static int fs_test_vfs_iso_cat_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(VFS_ARG_IMAGE);
+	struct udevice *dev;
+
+	ut_assertok(iso_mount_image(uts, fs_image, &dev));
+
+	ut_assertok(run_command("cat /mnt/testfile.txt", 0));
+	ut_assert_nextline("hello world");
+	ut_assert_console_end();
+
+	ut_assertok(iso_umount_image(uts, dev));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_vfs_iso_cat_norun,
+	     UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
+
+/* Test load on VFS-mounted ISO filesystem */
+static int fs_test_vfs_iso_load_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(VFS_ARG_IMAGE);
+	struct udevice *dev;
+	char *buf;
+
+	ut_assertok(iso_mount_image(uts, fs_image, &dev));
+
+	buf = map_sysmem(BUF_ADDR, 0x100);
+	memset(buf, '\0', 0x100);
+	ut_assertok(run_commandf("load %x /mnt/testfile.txt", BUF_ADDR));
+	ut_assert_nextline("12 bytes read");
+	ut_assert_console_end();
+	ut_asserteq_str("hello world\n", buf);
+	unmap_sysmem(buf);
+
+	ut_assertok(iso_umount_image(uts, dev));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_vfs_iso_load_norun,
+	     UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
+
+/* Test size on VFS-mounted ISO filesystem */
+static int fs_test_vfs_iso_size_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(VFS_ARG_IMAGE);
+	struct udevice *dev;
+
+	ut_assertok(iso_mount_image(uts, fs_image, &dev));
+
+	env_set("filesize", NULL);
+	ut_assertok(run_command("size /mnt/testfile.txt", 0));
+	ut_assert_console_end();
+	ut_asserteq(12, env_get_hex("filesize", 0));
+
+	ut_assertok(iso_umount_image(uts, dev));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_vfs_iso_size_norun,
+	     UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
+
+/* Test nested path access on VFS-mounted ISO filesystem */
+static int fs_test_vfs_iso_nested_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(VFS_ARG_IMAGE);
+	struct udevice *dev;
+	char *buf;
+
+	ut_assertok(iso_mount_image(uts, fs_image, &dev));
+
+	/* Load file from subdirectory */
+	buf = map_sysmem(BUF_ADDR, 0x100);
+	memset(buf, '\0', 0x100);
+	ut_assertok(run_commandf("load %x /mnt/subdir/nested.txt", BUF_ADDR));
+	ut_assert_nextline("12 bytes read");
+	ut_assert_console_end();
+	ut_asserteq_str("hello world\n", buf);
+	unmap_sysmem(buf);
+
+	ut_assertok(iso_umount_image(uts, dev));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_vfs_iso_nested_norun,
+	     UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
+
+/* Test cd and pwd on VFS-mounted ISO filesystem */
+static int fs_test_vfs_iso_cd_norun(struct unit_test_state *uts)
+{
+	const char *fs_image = ut_str(VFS_ARG_IMAGE);
+	struct udevice *dev;
+
+	ut_assertok(iso_mount_image(uts, fs_image, &dev));
+
+	ut_assertok(run_command("cd /mnt", 0));
+	ut_assert_console_end();
+
+	ut_assertok(run_command("pwd", 0));
+	ut_assert_nextline("/mnt");
+	ut_assert_console_end();
+
+	/* ls without path should list cwd */
+	ut_assertok(run_command("ls", 0));
+	ut_assert_nextline("DIR          0 .");
+	ut_assert_nextline("DIR          0 ..");
+	ut_assert_nextline("DIR          0 subdir");
+	ut_assert_nextline("            12 testfile.txt");
+	ut_assert_console_end();
+
+	/* cd back to root */
+	ut_assertok(run_command("cd /", 0));
+	ut_assert_console_end();
+
+	ut_assertok(iso_umount_image(uts, dev));
+
+	return 0;
+}
+FS_TEST_ARGS(fs_test_vfs_iso_cd_norun,
+	     UTF_SCAN_FDT | UTF_CONSOLE | UTF_MANUAL,
+	     { "fs_image", UT_ARG_STR });
diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py
index 269b1c2d10f..d762f975582 100644
--- a/test/py/tests/fs_helper.py
+++ b/test/py/tests/fs_helper.py
@@ -101,7 +101,7 @@ class FsHelper:
                 master key (via --master-key-file), enabling pre_derived unlock.
         """
         if ('fat' not in fs_type and 'ext' not in fs_type and
-             fs_type not in ['exfat', 'fs_generic']):
+             fs_type not in ['exfat', 'fs_generic', 'iso']):
             raise ValueError(f"Unsupported filesystem type '{fs_type}'")
 
         self.config = config
@@ -164,6 +164,11 @@ class FsHelper:
     def mk_fs(self):
         """Make a new filesystem and copy in the files"""
         self.setup()
+
+        if self.fs_type == 'iso':
+            self._mk_iso()
+            return
+
         mkfs_opt, fs_lnxtype = self._get_fs_args()
         fs_img = self.fs_img
         with open(fs_img, 'wb') as fsi:
@@ -195,6 +200,15 @@ class FsHelper:
         if self.passphrase or self.encrypt_keyfile:
             self.encrypt_luks()
 
+    def _mk_iso(self):
+        """Make an ISO 9660 filesystem image with Rock Ridge extensions"""
+        fs_img = self.fs_img
+        self._do_cleanup = True
+        quiet = '-quiet' if self.quiet else ''
+        check_call(f'genisoimage -R {quiet} -o {fs_img} {self.srcdir}',
+                   shell=True, stdout=DEVNULL if self.quiet else None,
+                   stderr=DEVNULL if self.quiet else None)
+
     def setup(self):
         """Set up the srcdir ready to receive files"""
         if not self.srcdir:
diff --git a/test/py/tests/test_fs/test_vfs.py b/test/py/tests/test_fs/test_vfs.py
index 7f0fbdc802e..f4c6108efd0 100644
--- a/test/py/tests/test_fs/test_vfs.py
+++ b/test/py/tests/test_fs/test_vfs.py
@@ -4,9 +4,9 @@
 # Test VFS operations on real filesystem images
 
 """
-Test VFS mount, ls, stat, load, save, cat, mkdir, rm, mv on ext4 and
-FAT images. Python creates the filesystem image; C tests mount it via
-VFS and exercise the operations.
+Test VFS mount, ls, stat, load, save, cat, mkdir, rm, mv on ext4,
+FAT and ISO 9660 images. Python creates the filesystem image; C tests
+mount it via VFS and exercise the operations.
 """
 
 import os
@@ -96,3 +96,31 @@ class TestVfsFat:
 # Attach shared test methods to TestVfsFat
 for _name in VFS_TESTS:
     setattr(TestVfsFat, f'test_{_name}', _define_test(_name))
+
+
+# Read-only tests applicable to ISO 9660
+ISO_TESTS = ['cat', 'cd', 'load', 'ls', 'nested', 'size']
+
+
+@pytest.mark.buildconfigspec('cmd_vfs')
+@pytest.mark.buildconfigspec('fs_isofs')
+class TestVfsIso:
+    """Test VFS operations on an ISO 9660 filesystem."""
+
+    @pytest.fixture(scope='class')
+    def fs_image(self, u_boot_config):
+        """Create an ISO 9660 filesystem image with test files."""
+        fsh = FsHelper(u_boot_config, 'iso', 0, 'vfs')
+        fsh.setup()
+        _populate_srcdir(fsh.srcdir)
+        fsh.mk_fs()
+
+        yield fsh.fs_img
+
+        fsh.cleanup()
+
+
+# Attach read-only test methods to TestVfsIso (using vfs_iso prefix)
+for _name in ISO_TESTS:
+    setattr(TestVfsIso, f'test_{_name}',
+            _define_test(_name, 'vfs_iso'))
