]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
mount: Fix race in loop device reuse code
authorJan Kara <jack@suse.cz>
Thu, 20 Jan 2022 11:47:05 +0000 (12:47 +0100)
committerKarel Zak <kzak@redhat.com>
Tue, 25 Jan 2022 16:10:46 +0000 (17:10 +0100)
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.

Signed-off-by: Jan Kara <jack@suse.cz>
libmount/src/context_loopdev.c

index 88af20c0e5223033b2e6af9f2057cfb8fbac6c30..62e319ce613132446a83aa2f9ff8e390e8ad3a78 100644 (file)
@@ -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)