loop: remove support for swapping loop fds
LOOP_CHANGE_FD swaps the backing file of a bound read-only loop device for another file of the same size. The comment above loop_change_fd() says this is for installers that want to free the original image and for HA setups that want to switch to another copy of the image. When we discussed deprecating it in 2023 (cf. [1]) we found no users at all: zero hits on codesearch.debian.net, GitHub code search and cs.android.com. The only things that exercise it are strace, LTP, stress-ng and blktests. Everyone agreed to drop it. I just never sent the patch. Since then we found several more problems with it and all of them are structural. The ioctl changes what a block device is backed by after a filesystem has been mounted on it and nothing else in the kernel does that. (1) It creates a lock order cycle. loop_change_fd() is the only loop operation that calls disk_force_media_change() on a bound device while a filesystem can be mounted on it. loop_configure() calls it before the device is bound and __loop_clr_fd() after the last opener is gone. And it calls it with lo->lo_mutex held. Since v6.6 disk_force_media_change() doesn't just bump the disk sequence number. It calls bdev_mark_dead() which calls into the holder, i.e., fs_bdev_mark_dead(), which takes sb->s_umount of every superblock on that device. So LOOP_CHANGE_FD gives us: lo_mutex -> s_umount Mounting a filesystem takes s_umount in sget_fc() and then opens the block device in setup_bdev_super(). That takes disk->open_mutex and calls lo_open() which takes lo_mutex: s_umount -> open_mutex -> lo_mutex That's a cycle. Lockdep reports it as soon as LOOP_CHANGE_FD is used on a device that has a filesystem mounted on it. This was recently hit with erofs where LOOP_CHANGE_FD raced with umount and a concurrent open of the loop device. And it is a real deadlock: the mounter holds s_umount and waits for open_mutex, the opener holds open_mutex and waits in lo_open() for lo_mutex, and the LOOP_CHANGE_FD caller holds lo_mutex and waits in fs_bdev_mark_dead() for that superblock. Note that we went through the block layer in v6.7 to guarantee that s_umount is never taken under open_mutex. LOOP_CHANGE_FD is the reverse direction and it was simply never considered. (2) The use-case it was added for doesn't work anymore. fs_bdev_mark_dead() shuts the filesystem down. For ext4, xfs and btrfs that means ->shutdown() is called and the mounted filesystem returns -EIO from then on. So swapping the image under a mounted filesystem can't work for any filesystem that implements shutdown. It only survives on erofs because erofs deliberately doesn't implement ->shutdown() and ->remove_bdev(). Commit 69139a62a918 ("erofs: open via dedicated fs bdev helpers") explicitly cites loop_change_fd() as the reason why it shouldn't. IOW, an ioctl without users dictates what a filesystem is allowed to do on device removal. And it never worked on partitions. disk_force_media_change() only acts on disk->part0 so a filesystem mounted on loop0p1 isn't told anything when the image under it is swapped. (3) It can create superblock pin cycles. loop_validate_file() only guards against loop-on-loop recursion. It doesn't stop LOOP_CHANGE_FD from pointing a bound device at a file on the filesystem that is mounted from that very device, or two devices at files on each other's filesystems, or a device at a file on a fuse mount whose passthrough backing file lives on the loop mount. Static setups can't form these cycles because the backing file is opened before the filesystem on top of it exists. LOOP_CHANGE_FD is the only block layer path where the edge is added afterwards. The result is a cycle of superblocks: the backing file pins the mount, the mount pins the superblock, the superblock holds the device open and the device holds the backing file. umount detaches the mounts but nothing ever frees them and the mount namespace they live in leaks with them. This is not a cycle of mounts so nothing in mount teardown can break it. It's root-only, but it is a permanent leak that can't be undone. (4) It makes filesystem stacking dynamic. s_stack_depth assumes that the dependencies between filesystems are fixed when a filesystem is mounted. With LOOP_CHANGE_FD the image of a mounted filesystem can be moved onto another filesystem at any later time, so a loop-mounted filesystem is at stacking depth 0 no matter what its image ends up on. That is the hole in the stacking deadlock discussion for fanotify pre-content events (cf. [2]). The partition handling of LOOP_CHANGE_FD has been broken since v7.1 and nobody noticed until Daan ran into it in August (cf. [3]). That's about the level of use this ioctl sees. So let's remove it. LOOP_CHANGE_FD now returns -EOPNOTSUPP. [1]: https://lore.kernel.org/r/20231023-biberbau-spatzen-282ccea0825a@brauner https://lore.kernel.org/r/20231024-entfuhr-sachbezogen-aab33dec4087@brauner [2]: https://lore.kernel.org/r/CAOQ4uxi2Mx4ZB48DhQJWOvt6UBur3LnrkttgRjo72U4+Cu81iQ@mail.gmail.com https://lore.kernel.org/r/20260617-vorfahren-haften-nullnummer-b0d6fccadae3@brauner [3]: https://lore.kernel.org/r/20260806-b4-loop-nbd-stale-partitions-v1-1-67bb75a8d7be@amutable.com Signed-off-by: Christian Brauner <brauner@kernel.org> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20261012-work-loop-change_fd-v1-1-3cf4ec349bbb@kernel.org Signed-off-by: Jens Axboe <axboe@kernel.dk>