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 --- 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