From 99e5203da4bb8c4470f0c865add67b8151405bbc Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 20 Jan 2022 12:47:05 +0100 Subject: mount: Fix race in loop device reuse code Small timing changes in the kernel loop device handling broke the following loop: while :; do mount -o loop,ro isofs.iso isofs/; umount isofs/; done which quickly reports: mount: /mnt: can't read superblock on /dev/loop0. umount: /mnt: not mounted. And this loop is broken because of a subtle interaction with systemd-udevd that also opens the loop device. The race seems to be in mount(8) handling itself and the altered kernel timing makes it happen. It look like: bash systemd-udevd mount -o loop,ro isofs.iso isofs/ /dev/loop0 is created and bound to isofs.iso, autoclear is set for loop0 opens /dev/loop0 umount isofs/ loop0 still lives because systemd-udev still has device open mount -o loop,ro isofs.iso isofs/ gets to mnt_context_setup_loopdev() loopcxt_find_overlap() sees loop0 is still valid and with proper parameters reuse = true; close /dev/loop0 last fd closed => loop0 is cleaned up loopcxt_get_fd() opens loop0 but it is no longer the device we wanted! calls mount(2) which fails because we cannot read from the loop device Fix the problem by rechecking that loop device is still attached after opening the device. This makes sure the kernel will not autoclear the device anymore. Addresses: https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=2117203 Signed-off-by: Jan Kara --- libmount/src/context_loopdev.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libmount/src/context_loopdev.c b/libmount/src/context_loopdev.c index 6462bfb62..73bcc01c1 100644 --- a/libmount/src/context_loopdev.c +++ b/libmount/src/context_loopdev.c @@ -255,6 +255,25 @@ int mnt_context_setup_loopdev(struct libmnt_context *cxt) DBG(LOOP, ul_debugobj(cxt, "re-using existing loop device %s", loopcxt_get_device(&lc))); + /* Open loop device to block device autoclear... */ + if (loopcxt_get_fd(&lc) < 0) { + DBG(LOOP, ul_debugobj(cxt, "failed to get loopdev FD")); + rc = -errno; + goto done; + } + + /* + * Now that we certainly have the loop device open, + * verify the loop device was not autocleared in the + * mean time. + */ + if (!loopcxt_get_info(&lc)) { + DBG(LOOP, ul_debugobj(cxt, "lost race with %s teardown", + loopcxt_get_device(&lc))); + loopcxt_deinit(&lc); + break; + } + /* Once a loop is initialized RO, there is no * way to change its parameters. */ if (loopcxt_is_readonly(&lc) -- 2.37.1