Skip to content

Commit 84ab127

Browse files
committed
Merge tag 'v6.6-vfs.fs_context' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull mount API updates from Christian Brauner: "This introduces FSCONFIG_CMD_CREATE_EXCL which allows userspace to implement something like $ mount -t ext4 --exclusive /dev/sda /B which fails if a superblock for the requested filesystem does already exist instead of silently reusing an existing superblock. Without it, in the sequence $ move-mount -f xfs -o source=/dev/sda4 /A $ move-mount -f xfs -o noacl,source=/dev/sda4 /B the initial mounter will create a superblock. The second mounter will reuse the existing superblock, creating a bind-mount (see [1] for the source of the move-mount binary). The problem is that reusing an existing superblock means all mount options other than read-only and read-write will be silently ignored even if they are incompatible requests. For example, the second mount has requested no POSIX ACL support but since the existing superblock is reused POSIX ACL support will remain enabled. Such silent superblock reuse can easily become a security issue. After adding support for FSCONFIG_CMD_CREATE_EXCL to mount(8) in util-linux this can be fixed: $ move-mount -f xfs --exclusive -o source=/dev/sda4 /A $ move-mount -f xfs --exclusive -o noacl,source=/dev/sda4 /B Device or resource busy | move-mount.c: 300: do_fsconfig: i xfs: reusing existing filesystem not allowed This requires the new mount api. With the old mount api it would be necessary to plumb this through every legacy filesystem's file_system_type->mount() method. If they want this feature they are most welcome to switch to the new mount api" Link: https://github.com/brauner/move-mount-beneath [1] Link: https://lore.kernel.org/linux-block/20230704-fasching-wertarbeit-7c6ffb01c83d@brauner Link: https://lore.kernel.org/linux-block/20230705-pumpwerk-vielversprechend-a4b1fd947b65@brauner Link: https://lore.kernel.org/linux-fsdevel/20230725-einnahmen-warnschilder-17779aec0a97@brauner Link: https://lore.kernel.org/lkml/20230824-anzog-allheilmittel-e8c63e429a79@brauner/ * tag 'v6.6-vfs.fs_context' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: fs: add FSCONFIG_CMD_CREATE_EXCL fs: add vfs_cmd_reconfigure() fs: add vfs_cmd_create() super: remove get_tree_single_reconf()
2 parents 2dde18c + 22ed7ec commit 84ab127

File tree

5 files changed

+107
-71
lines changed

5 files changed

+107
-71
lines changed

fs/fs_context.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ void vfs_clean_context(struct fs_context *fc)
692692
security_free_mnt_opts(&fc->security);
693693
kfree(fc->source);
694694
fc->source = NULL;
695+
fc->exclusive = false;
695696

696697
fc->purpose = FS_CONTEXT_FOR_RECONFIGURE;
697698
fc->phase = FS_CONTEXT_AWAITING_RECONF;

fs/fsopen.c

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -209,63 +209,98 @@ SYSCALL_DEFINE3(fspick, int, dfd, const char __user *, path, unsigned int, flags
209209
return ret;
210210
}
211211

212+
static int vfs_cmd_create(struct fs_context *fc, bool exclusive)
213+
{
214+
struct super_block *sb;
215+
int ret;
216+
217+
if (fc->phase != FS_CONTEXT_CREATE_PARAMS)
218+
return -EBUSY;
219+
220+
if (!mount_capable(fc))
221+
return -EPERM;
222+
223+
/* require the new mount api */
224+
if (exclusive && fc->ops == &legacy_fs_context_ops)
225+
return -EOPNOTSUPP;
226+
227+
fc->phase = FS_CONTEXT_CREATING;
228+
fc->exclusive = exclusive;
229+
230+
ret = vfs_get_tree(fc);
231+
if (ret) {
232+
fc->phase = FS_CONTEXT_FAILED;
233+
return ret;
234+
}
235+
236+
sb = fc->root->d_sb;
237+
ret = security_sb_kern_mount(sb);
238+
if (unlikely(ret)) {
239+
fc_drop_locked(fc);
240+
fc->phase = FS_CONTEXT_FAILED;
241+
return ret;
242+
}
243+
244+
/* vfs_get_tree() callchains will have grabbed @s_umount */
245+
up_write(&sb->s_umount);
246+
fc->phase = FS_CONTEXT_AWAITING_MOUNT;
247+
return 0;
248+
}
249+
250+
static int vfs_cmd_reconfigure(struct fs_context *fc)
251+
{
252+
struct super_block *sb;
253+
int ret;
254+
255+
if (fc->phase != FS_CONTEXT_RECONF_PARAMS)
256+
return -EBUSY;
257+
258+
fc->phase = FS_CONTEXT_RECONFIGURING;
259+
260+
sb = fc->root->d_sb;
261+
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) {
262+
fc->phase = FS_CONTEXT_FAILED;
263+
return -EPERM;
264+
}
265+
266+
down_write(&sb->s_umount);
267+
ret = reconfigure_super(fc);
268+
up_write(&sb->s_umount);
269+
if (ret) {
270+
fc->phase = FS_CONTEXT_FAILED;
271+
return ret;
272+
}
273+
274+
vfs_clean_context(fc);
275+
return 0;
276+
}
277+
212278
/*
213279
* Check the state and apply the configuration. Note that this function is
214280
* allowed to 'steal' the value by setting param->xxx to NULL before returning.
215281
*/
216282
static int vfs_fsconfig_locked(struct fs_context *fc, int cmd,
217283
struct fs_parameter *param)
218284
{
219-
struct super_block *sb;
220285
int ret;
221286

222287
ret = finish_clean_context(fc);
223288
if (ret)
224289
return ret;
225290
switch (cmd) {
226291
case FSCONFIG_CMD_CREATE:
227-
if (fc->phase != FS_CONTEXT_CREATE_PARAMS)
228-
return -EBUSY;
229-
if (!mount_capable(fc))
230-
return -EPERM;
231-
fc->phase = FS_CONTEXT_CREATING;
232-
ret = vfs_get_tree(fc);
233-
if (ret)
234-
break;
235-
sb = fc->root->d_sb;
236-
ret = security_sb_kern_mount(sb);
237-
if (unlikely(ret)) {
238-
fc_drop_locked(fc);
239-
break;
240-
}
241-
up_write(&sb->s_umount);
242-
fc->phase = FS_CONTEXT_AWAITING_MOUNT;
243-
return 0;
292+
return vfs_cmd_create(fc, false);
293+
case FSCONFIG_CMD_CREATE_EXCL:
294+
return vfs_cmd_create(fc, true);
244295
case FSCONFIG_CMD_RECONFIGURE:
245-
if (fc->phase != FS_CONTEXT_RECONF_PARAMS)
246-
return -EBUSY;
247-
fc->phase = FS_CONTEXT_RECONFIGURING;
248-
sb = fc->root->d_sb;
249-
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) {
250-
ret = -EPERM;
251-
break;
252-
}
253-
down_write(&sb->s_umount);
254-
ret = reconfigure_super(fc);
255-
up_write(&sb->s_umount);
256-
if (ret)
257-
break;
258-
vfs_clean_context(fc);
259-
return 0;
296+
return vfs_cmd_reconfigure(fc);
260297
default:
261298
if (fc->phase != FS_CONTEXT_CREATE_PARAMS &&
262299
fc->phase != FS_CONTEXT_RECONF_PARAMS)
263300
return -EBUSY;
264301

265302
return vfs_parse_fs_param(fc, param);
266303
}
267-
fc->phase = FS_CONTEXT_FAILED;
268-
return ret;
269304
}
270305

271306
/**
@@ -353,6 +388,7 @@ SYSCALL_DEFINE5(fsconfig,
353388
return -EINVAL;
354389
break;
355390
case FSCONFIG_CMD_CREATE:
391+
case FSCONFIG_CMD_CREATE_EXCL:
356392
case FSCONFIG_CMD_RECONFIGURE:
357393
if (_key || _value || aux)
358394
return -EINVAL;

fs/super.c

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -546,17 +546,31 @@ bool mount_capable(struct fs_context *fc)
546546
* @test: Comparison callback
547547
* @set: Setup callback
548548
*
549-
* Find or create a superblock using the parameters stored in the filesystem
550-
* context and the two callback functions.
549+
* Create a new superblock or find an existing one.
551550
*
552-
* If an extant superblock is matched, then that will be returned with an
553-
* elevated reference count that the caller must transfer or discard.
551+
* The @test callback is used to find a matching existing superblock.
552+
* Whether or not the requested parameters in @fc are taken into account
553+
* is specific to the @test callback that is used. They may even be
554+
* completely ignored.
555+
*
556+
* If an extant superblock is matched, it will be returned unless:
557+
*
558+
* (1) the namespace the filesystem context @fc and the extant
559+
* superblock's namespace differ
560+
*
561+
* (2) the filesystem context @fc has requested that reusing an extant
562+
* superblock is not allowed
563+
*
564+
* In both cases EBUSY will be returned.
554565
*
555566
* If no match is made, a new superblock will be allocated and basic
556-
* initialisation will be performed (s_type, s_fs_info and s_id will be set and
557-
* the set() callback will be invoked), the superblock will be published and it
558-
* will be returned in a partially constructed state with SB_BORN and SB_ACTIVE
559-
* as yet unset.
567+
* initialisation will be performed (s_type, s_fs_info and s_id will be
568+
* set and the @set callback will be invoked), the superblock will be
569+
* published and it will be returned in a partially constructed state
570+
* with SB_BORN and SB_ACTIVE as yet unset.
571+
*
572+
* Return: On success, an extant or newly created superblock is
573+
* returned. On failure an error pointer is returned.
560574
*/
561575
struct super_block *sget_fc(struct fs_context *fc,
562576
int (*test)(struct super_block *, struct fs_context *),
@@ -603,9 +617,13 @@ struct super_block *sget_fc(struct fs_context *fc,
603617
return s;
604618

605619
share_extant_sb:
606-
if (user_ns != old->s_user_ns) {
620+
if (user_ns != old->s_user_ns || fc->exclusive) {
607621
spin_unlock(&sb_lock);
608622
destroy_unused_super(s);
623+
if (fc->exclusive)
624+
warnfc(fc, "reusing existing filesystem not allowed");
625+
else
626+
warnfc(fc, "reusing existing filesystem in another namespace not allowed");
609627
return ERR_PTR(-EBUSY);
610628
}
611629
if (!grab_super(old))
@@ -1136,7 +1154,7 @@ static int test_single_super(struct super_block *s, struct fs_context *fc)
11361154
return 1;
11371155
}
11381156

1139-
static int vfs_get_super(struct fs_context *fc, bool reconf,
1157+
static int vfs_get_super(struct fs_context *fc,
11401158
int (*test)(struct super_block *, struct fs_context *),
11411159
int (*fill_super)(struct super_block *sb,
11421160
struct fs_context *fc))
@@ -1154,19 +1172,9 @@ static int vfs_get_super(struct fs_context *fc, bool reconf,
11541172
goto error;
11551173

11561174
sb->s_flags |= SB_ACTIVE;
1157-
fc->root = dget(sb->s_root);
1158-
} else {
1159-
fc->root = dget(sb->s_root);
1160-
if (reconf) {
1161-
err = reconfigure_super(fc);
1162-
if (err < 0) {
1163-
dput(fc->root);
1164-
fc->root = NULL;
1165-
goto error;
1166-
}
1167-
}
11681175
}
11691176

1177+
fc->root = dget(sb->s_root);
11701178
return 0;
11711179

11721180
error:
@@ -1178,33 +1186,25 @@ int get_tree_nodev(struct fs_context *fc,
11781186
int (*fill_super)(struct super_block *sb,
11791187
struct fs_context *fc))
11801188
{
1181-
return vfs_get_super(fc, false, NULL, fill_super);
1189+
return vfs_get_super(fc, NULL, fill_super);
11821190
}
11831191
EXPORT_SYMBOL(get_tree_nodev);
11841192

11851193
int get_tree_single(struct fs_context *fc,
11861194
int (*fill_super)(struct super_block *sb,
11871195
struct fs_context *fc))
11881196
{
1189-
return vfs_get_super(fc, false, test_single_super, fill_super);
1197+
return vfs_get_super(fc, test_single_super, fill_super);
11901198
}
11911199
EXPORT_SYMBOL(get_tree_single);
11921200

1193-
int get_tree_single_reconf(struct fs_context *fc,
1194-
int (*fill_super)(struct super_block *sb,
1195-
struct fs_context *fc))
1196-
{
1197-
return vfs_get_super(fc, true, test_single_super, fill_super);
1198-
}
1199-
EXPORT_SYMBOL(get_tree_single_reconf);
1200-
12011201
int get_tree_keyed(struct fs_context *fc,
12021202
int (*fill_super)(struct super_block *sb,
12031203
struct fs_context *fc),
12041204
void *key)
12051205
{
12061206
fc->s_fs_info = key;
1207-
return vfs_get_super(fc, false, test_keyed_super, fill_super);
1207+
return vfs_get_super(fc, test_keyed_super, fill_super);
12081208
}
12091209
EXPORT_SYMBOL(get_tree_keyed);
12101210

include/linux/fs_context.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ struct fs_context {
109109
bool need_free:1; /* Need to call ops->free() */
110110
bool global:1; /* Goes into &init_user_ns */
111111
bool oldapi:1; /* Coming from mount(2) */
112+
bool exclusive:1; /* create new superblock, reject existing one */
112113
};
113114

114115
struct fs_context_operations {
@@ -150,9 +151,6 @@ extern int get_tree_nodev(struct fs_context *fc,
150151
extern int get_tree_single(struct fs_context *fc,
151152
int (*fill_super)(struct super_block *sb,
152153
struct fs_context *fc));
153-
extern int get_tree_single_reconf(struct fs_context *fc,
154-
int (*fill_super)(struct super_block *sb,
155-
struct fs_context *fc));
156154
extern int get_tree_keyed(struct fs_context *fc,
157155
int (*fill_super)(struct super_block *sb,
158156
struct fs_context *fc),

include/uapi/linux/mount.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ enum fsconfig_command {
100100
FSCONFIG_SET_PATH = 3, /* Set parameter, supplying an object by path */
101101
FSCONFIG_SET_PATH_EMPTY = 4, /* Set parameter, supplying an object by (empty) path */
102102
FSCONFIG_SET_FD = 5, /* Set parameter, supplying an object by fd */
103-
FSCONFIG_CMD_CREATE = 6, /* Invoke superblock creation */
103+
FSCONFIG_CMD_CREATE = 6, /* Create new or reuse existing superblock */
104104
FSCONFIG_CMD_RECONFIGURE = 7, /* Invoke superblock reconfiguration */
105+
FSCONFIG_CMD_CREATE_EXCL = 8, /* Create new superblock, fail if reusing existing superblock */
105106
};
106107

107108
/*

0 commit comments

Comments
 (0)