2022-05-30 07:45:12

by Daniil Lunev

[permalink] [raw]
Subject: [PATCH v3 0/2] Prevent re-use of FUSE superblock after force unmount

Force unmount of fuse severes the connection between FUSE driver and its
userspace counterpart. However, open file handles will prevent the
superblock from being reclaimed. An attempt to remount the filesystem at
the same endpoint will try re-using the superblock, if still present.
Since the superblock re-use path doesn't go through the fs-specific
superblock setup code, its state in FUSE case is already disfunctional,
and that will prevent the mount from succeeding.

Changes in v3:
- Back to state tracking from v1
- Use s_iflag to mark superblocked ignored
- Only unregister private bdi in retire, without freeing

Changes in v2:
- Remove super from list of superblocks instead of using a flag

Daniil Lunev (2):
fs/super: function to prevent super re-use
FUSE: Retire superblock on force unmount

fs/fuse/inode.c | 7 +++++--
fs/super.c | 32 ++++++++++++++++++++++++++++----
include/linux/fs.h | 2 ++
3 files changed, 35 insertions(+), 6 deletions(-)

--
2.31.0



2022-05-30 09:55:40

by Daniil Lunev

[permalink] [raw]
Subject: [PATCH v3 1/2] fs/super: function to prevent super re-use

The function is to be called from filesystem-specific code to mark a
superblock to be ignored by superblock test and thus never re-used. The
function also unregisters bdi if the bdi is per-superblock to avoid
collision if a new superblock is created to represent the filesystem.
generic_shutdown_super() skips unregistering bdi for a retired
superlock as it assumes retire function has already done it.

Signed-off-by: Daniil Lunev <[email protected]>
---

Changes in v3:
- Back to state tracking from v1
- Use s_iflag to mark superblocked ignored
- Only unregister private bdi in retire, without freeing

Changes in v2:
- Remove super from list of superblocks instead of using a flag

fs/super.c | 32 ++++++++++++++++++++++++++++----
include/linux/fs.h | 2 ++
2 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/fs/super.c b/fs/super.c
index f1d4a193602d6..cb092fc6d6d34 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -422,6 +422,30 @@ bool trylock_super(struct super_block *sb)
return false;
}

+/**
+ * retire_super - prevernts superblock from being reused
+ * @sb: superblock to retire
+ *
+ * The function marks superblock to be ignored in superblock test, which
+ * prevents it from being reused for any new mounts. If the superblock has
+ * a private bdi, it also unregisters it, but doesn't reduce the refcount
+ * of the superblock to prevent potential races. The refcount is reduced
+ * by generic_shutdown_super(). The function can not be called concurrently
+ * with generic_shutdown_super(). It is safe to call the function multiple
+ * times, subsequent calls have no effect.
+ */
+void retire_super(struct super_block *sb)
+{
+ down_write(&sb->s_umount);
+ if (sb->s_bdi != &noop_backing_dev_info) {
+ if (sb->s_iflags & SB_I_PERSB_BDI && !(sb->s_iflags & SB_I_RETIRED))
+ bdi_unregister(sb->s_bdi);
+ }
+ sb->s_iflags |= SB_I_RETIRED;
+ up_write(&sb->s_umount);
+}
+EXPORT_SYMBOL(retire_super);
+
/**
* generic_shutdown_super - common helper for ->kill_sb()
* @sb: superblock to kill
@@ -468,12 +492,12 @@ void generic_shutdown_super(struct super_block *sb)
}
}
spin_lock(&sb_lock);
- /* should be initialized for __put_super_and_need_restart() */
hlist_del_init(&sb->s_instances);
spin_unlock(&sb_lock);
up_write(&sb->s_umount);
if (sb->s_bdi != &noop_backing_dev_info) {
- if (sb->s_iflags & SB_I_PERSB_BDI)
+ /* retire should have already unregistered bdi */
+ if (sb->s_iflags & SB_I_PERSB_BDI && !(sb->s_iflags & SB_I_RETIRED))
bdi_unregister(sb->s_bdi);
bdi_put(sb->s_bdi);
sb->s_bdi = &noop_backing_dev_info;
@@ -1216,7 +1240,7 @@ static int set_bdev_super_fc(struct super_block *s, struct fs_context *fc)

static int test_bdev_super_fc(struct super_block *s, struct fs_context *fc)
{
- return s->s_bdev == fc->sget_key;
+ return !(s->s_iflags & SB_I_RETIRED) && s->s_bdev == fc->sget_key;
}

/**
@@ -1307,7 +1331,7 @@ EXPORT_SYMBOL(get_tree_bdev);

static int test_bdev_super(struct super_block *s, void *data)
{
- return (void *)s->s_bdev == data;
+ return !(s->s_iflags & SB_I_RETIRED) && (void *)s->s_bdev == data;
}

struct dentry *mount_bdev(struct file_system_type *fs_type,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index bbde95387a23a..ef392fd2361bd 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1411,6 +1411,7 @@ extern int send_sigurg(struct fown_struct *fown);
#define SB_I_SKIP_SYNC 0x00000100 /* Skip superblock at global sync */
#define SB_I_PERSB_BDI 0x00000200 /* has a per-sb bdi */
#define SB_I_TS_EXPIRY_WARNED 0x00000400 /* warned about timestamp range expiry */
+#define SB_I_RETIRED 0x00000800 /* superblock shouldn't be reused */

/* Possible states of 'frozen' field */
enum {
@@ -2424,6 +2425,7 @@ extern struct dentry *mount_nodev(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int));
extern struct dentry *mount_subtree(struct vfsmount *mnt, const char *path);
+void retire_super(struct super_block *sb);
void generic_shutdown_super(struct super_block *sb);
void kill_block_super(struct super_block *sb);
void kill_anon_super(struct super_block *sb);
--
2.31.0


2022-05-30 13:53:13

by Daniil Lunev

[permalink] [raw]
Subject: [PATCH v3 2/2] FUSE: Retire superblock on force unmount

Force unmount of FUSE severes the connection with the user space, even
if there are still open files. Subsequent remount tries to re-use the
superblock held by the open files, which is meaningless in the FUSE case
after disconnect - reused super block doesn't have userspace counterpart
attached to it and is incapable of doing any IO.

Signed-off-by: Daniil Lunev <[email protected]>

---

Changes in v3:
- No changes

Changes in v2:
- Use an exported function instead of directly modifying superblock

fs/fuse/inode.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 8c0665c5dff88..8875361544b2a 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -476,8 +476,11 @@ static void fuse_umount_begin(struct super_block *sb)
{
struct fuse_conn *fc = get_fuse_conn_super(sb);

- if (!fc->no_force_umount)
- fuse_abort_conn(fc);
+ if (fc->no_force_umount)
+ return;
+
+ fuse_abort_conn(fc);
+ retire_super(sb);
}

static void fuse_send_destroy(struct fuse_mount *fm)
--
2.31.0


2022-05-31 20:57:47

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] fs/super: function to prevent super re-use

On Mon, May 30, 2022 at 11:39:57AM +1000, Daniil Lunev wrote:
> +void retire_super(struct super_block *sb)
> +{
> + down_write(&sb->s_umount);
> + if (sb->s_bdi != &noop_backing_dev_info) {
> + if (sb->s_iflags & SB_I_PERSB_BDI && !(sb->s_iflags & SB_I_RETIRED))

SB_I_PERSB_BDI can't be set for noop_backing_dev_info, so that check
should not be needed. Which also conveniently fixes the overly long
line.

Also this should clear SB_I_PERSB_BDI as the only place that checks
it is the unregistration.

> spin_lock(&sb_lock);
> - /* should be initialized for __put_super_and_need_restart() */

This is a completely unrelated change. While the function is gone
it might be worth to check what it got renamed to or folded in, or
if the initialization is still needed. But all that is for a separate
patch.

> up_write(&sb->s_umount);
> if (sb->s_bdi != &noop_backing_dev_info) {
> - if (sb->s_iflags & SB_I_PERSB_BDI)
> + /* retire should have already unregistered bdi */
> + if (sb->s_iflags & SB_I_PERSB_BDI && !(sb->s_iflags & SB_I_RETIRED))
> bdi_unregister(sb->s_bdi);
> bdi_put(sb->s_bdi);

And once SB_I_PERSB_BDI is dropped when retiring we don't need this
change.

2022-06-01 20:00:23

by Daniil Lunev

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] fs/super: function to prevent super re-use

Thank you for your comments. Uploaded v4 with requested changes.
--Daniil

On Tue, May 31, 2022 at 5:24 PM Christoph Hellwig <[email protected]> wrote:
>
> On Mon, May 30, 2022 at 11:39:57AM +1000, Daniil Lunev wrote:
> > +void retire_super(struct super_block *sb)
> > +{
> > + down_write(&sb->s_umount);
> > + if (sb->s_bdi != &noop_backing_dev_info) {
> > + if (sb->s_iflags & SB_I_PERSB_BDI && !(sb->s_iflags & SB_I_RETIRED))
>
> SB_I_PERSB_BDI can't be set for noop_backing_dev_info, so that check
> should not be needed. Which also conveniently fixes the overly long
> line.
>
> Also this should clear SB_I_PERSB_BDI as the only place that checks
> it is the unregistration.
>
> > spin_lock(&sb_lock);
> > - /* should be initialized for __put_super_and_need_restart() */
>
> This is a completely unrelated change. While the function is gone
> it might be worth to check what it got renamed to or folded in, or
> if the initialization is still needed. But all that is for a separate
> patch.
>
> > up_write(&sb->s_umount);
> > if (sb->s_bdi != &noop_backing_dev_info) {
> > - if (sb->s_iflags & SB_I_PERSB_BDI)
> > + /* retire should have already unregistered bdi */
> > + if (sb->s_iflags & SB_I_PERSB_BDI && !(sb->s_iflags & SB_I_RETIRED))
> > bdi_unregister(sb->s_bdi);
> > bdi_put(sb->s_bdi);
>
> And once SB_I_PERSB_BDI is dropped when retiring we don't need this
> change.