[PATCH blktests] loop/014: Add test for backing file on loop device partition

Bart Van Assche <[email protected]>
Newsgroups org.kernel.vger.linux-block
Message-ID <[email protected]>
Add a new loop driver test that verifies that attempting to set the
backing file of a loop device to a file located on a filesystem on one of
the same loop device's partitions fails as expected by triggering the
kernel function loop_validate_file().

Modify src/loop_change_fd.c to accept a -r option to open the loop device
and backing file in read-only mode (O_RDONLY).

The test performs the following steps:
- Create a 1 GB image file ($TMPDIR/img).
- Instantiate a loop device for the image file with partition scanning
  enabled.
- Create two partitions of equal size on that loop device.
- Format the second partition with the ext4 filesystem and mount it.
- Create a 1 GB file on the mounted filesystem.
- Unmount the filesystem, detach the loop device, and re-instantiate it in
  read-only mode.
- Mount the partition read-only.
- Use src/loop_change_fd -r to attempt to change the backing file
  descriptor of the loop device to the file on its own partition via
  LOOP_CHANGE_FD, and verify that loop_validate_file() rejects it.
- Clean up by unmounting the filesystem and detaching the loop device.

Signed-off-by: Bart Van Assche <[email protected]>
---
 src/loop_change_fd.c |  20 ++++++--
 tests/loop/014       | 107 +++++++++++++++++++++++++++++++++++++++++++
 tests/loop/014.out   |   2 +
 3 files changed, 125 insertions(+), 4 deletions(-)
 create mode 100755 tests/loop/014
 create mode 100644 tests/loop/014.out

diff --git a/src/loop_change_fd.c b/src/loop_change_fd.c
index b124d829f380..722f14d3d91f 100644
--- a/src/loop_change_fd.c
+++ b/src/loop_change_fd.c
@@ -11,25 +11,37 @@
 
 void usage(const char *progname)
 {
-	fprintf(stderr, "usage: %s LOOPDEV PATH\n", progname);
+	fprintf(stderr, "usage: %s [-r] LOOPDEV PATH\n", progname);
 	exit(EXIT_FAILURE);
 }
 
 int main(int argc, char **argv)
 {
+	int flags = O_RDWR;
 	int ret;
 	int fd, filefd;
+	int c;
 
-	if (argc != 3)
+	while ((c = getopt(argc, argv, "r")) != -1) {
+		switch (c) {
+		case 'r':
+			flags = O_RDONLY;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind + 2 != argc)
 		usage(argv[0]);
 
-	fd = open(argv[1], O_RDWR);
+	fd = open(argv[optind], flags);
 	if (fd == -1) {
 		perror("open");
 		return EXIT_FAILURE;
 	}
 
-	filefd = open(argv[2], O_RDWR);
+	filefd = open(argv[optind + 1], flags);
 	if (filefd == -1) {
 		perror("open");
 		return EXIT_FAILURE;
diff --git a/tests/loop/014 b/tests/loop/014
new file mode 100755
index 000000000000..5814b8148a26
--- /dev/null
+++ b/tests/loop/014
@@ -0,0 +1,107 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0+
+# Copyright (C) 2026 Google LLC
+#
+# Verify that attempting to change a loop device's backing file (via
+# LOOP_CHANGE_FD) to a file on a filesystem residing on one of its own
+# partitions triggers the kernel function loop_validate_file() and fails.
+
+. tests/loop/rc
+
+DESCRIPTION="trigger loop_validate_file() when changing backing file to own partition"
+QUICK=1
+
+loop_device=
+mount_dir=
+
+requires() {
+	_have_program mkfs.ext4
+	_have_program parted
+	_have_src_program loop_change_fd
+}
+
+cleanup() {
+	if [[ -n "$mount_dir" ]]; then
+		umount "$mount_dir" &>/dev/null
+	fi
+	if [[ -n "$loop_device" ]]; then
+		losetup --detach "$loop_device" &>/dev/null
+	fi
+}
+
+test() {
+	echo "Running ${TEST_NAME}"
+	_register_test_cleanup cleanup
+
+	mount_dir="$TMPDIR/mnt"
+
+	truncate -s 1G "$TMPDIR/img"
+
+	if ! loop_device="$(losetup --find --partscan --show "$TMPDIR/img")"; then
+		echo "Failed to instantiate loop device"
+		return 1
+	fi
+
+	if ! parted --script "$loop_device" \
+		mklabel gpt \
+		mkpart primary 0% 50% \
+		mkpart primary 50% 100% >>"$FULL" 2>&1; then
+		echo "Failed to create partitions on loop device"
+		return 1
+	fi
+
+	udevadm settle
+
+	local part="${loop_device}p2"
+	if [[ ! -b "$part" ]]; then
+		echo "Partition $part does not exist"
+		return 1
+	fi
+
+	if ! mkfs.ext4 -F "$part" >>"$FULL" 2>&1; then
+		echo "Failed to format $part with ext4"
+		return 1
+	fi
+
+	mkdir -p "$mount_dir"
+	if ! mount -t ext4 "$part" "$mount_dir" >>"$FULL" 2>&1; then
+		echo "Failed to mount $part on $mount_dir"
+		return 1
+	fi
+
+	truncate -s 1G "$mount_dir/file"
+
+	umount "$mount_dir"
+	losetup --detach "$loop_device"
+	udevadm settle
+
+	# Reopen the loop device in read-only mode so that LOOP_CHANGE_FD can be used.
+	if ! loop_device="$(losetup --read-only --partscan --find --show "$TMPDIR/img")"; then
+		echo "Failed to re-instantiate loop device in read-only mode"
+		return 1
+	fi
+
+	udevadm settle
+
+	part="${loop_device}p2"
+	if ! mount -o ro -t ext4 "$part" "$mount_dir" >>"$FULL" 2>&1; then
+		echo "Failed to mount $part read-only on $mount_dir"
+		return 1
+	fi
+
+	# LOOP_CHANGE_FD triggers loop_validate_file() in the kernel, which must
+	# detect that the backing file resides on a partition of this loop device
+	# and reject the change.
+	if src/loop_change_fd -r "$loop_device" "$mount_dir/file" >>"$FULL" 2>&1; then
+		echo "Changing backing file succeeded unexpectedly"
+	fi
+
+	umount "$mount_dir"
+	mount_dir=
+
+	losetup --detach "$loop_device"
+	loop_device=
+	udevadm settle
+
+	echo "Test complete"
+}
diff --git a/tests/loop/014.out b/tests/loop/014.out
new file mode 100644
index 000000000000..78a7928c5249
--- /dev/null
+++ b/tests/loop/014.out
@@ -0,0 +1,2 @@
+Running loop/014
+Test complete
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.