diff options
author | Eric W. Biederman <ebiederm@xmission.com> | 2017-01-31 12:06:16 -0500 |
---|---|---|
committer | Eric W. Biederman <ebiederm@xmission.com> | 2017-02-01 10:36:12 -0500 |
commit | 93faccbbfa958a9668d3ab4e30f38dd205cee8d8 (patch) | |
tree | f4a102b92d86d19a52a88a57bdde19ad25250ca1 | |
parent | c6c70f4455d1eda91065e93cc4f7eddf4499b105 (diff) |
fs: Better permission checking for submounts
To support unprivileged users mounting filesystems two permission
checks have to be performed: a test to see if the user allowed to
create a mount in the mount namespace, and a test to see if
the user is allowed to access the specified filesystem.
The automount case is special in that mounting the original filesystem
grants permission to mount the sub-filesystems, to any user who
happens to stumble across the their mountpoint and satisfies the
ordinary filesystem permission checks.
Attempting to handle the automount case by using override_creds
almost works. It preserves the idea that permission to mount
the original filesystem is permission to mount the sub-filesystem.
Unfortunately using override_creds messes up the filesystems
ordinary permission checks.
Solve this by being explicit that a mount is a submount by introducing
vfs_submount, and using it where appropriate.
vfs_submount uses a new mount internal mount flags MS_SUBMOUNT, to let
sget and friends know that a mount is a submount so they can take appropriate
action.
sget and sget_userns are modified to not perform any permission checks
on submounts.
follow_automount is modified to stop using override_creds as that
has proven problemantic.
do_mount is modified to always remove the new MS_SUBMOUNT flag so
that we know userspace will never by able to specify it.
autofs4 is modified to stop using current_real_cred that was put in
there to handle the previous version of submount permission checking.
cifs is modified to pass the mountpoint all of the way down to vfs_submount.
debugfs is modified to pass the mountpoint all of the way down to
trace_automount by adding a new parameter. To make this change easier
a new typedef debugfs_automount_t is introduced to capture the type of
the debugfs automount function.
Cc: stable@vger.kernel.org
Fixes: 069d5ac9ae0d ("autofs: Fix automounts by using current_real_cred()->uid")
Fixes: aeaa4a79ff6a ("fs: Call d_automount with the filesystems creds")
Reviewed-by: Trond Myklebust <trond.myklebust@primarydata.com>
Reviewed-by: Seth Forshee <seth.forshee@canonical.com>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
-rw-r--r-- | fs/afs/mntpt.c | 2 | ||||
-rw-r--r-- | fs/autofs4/waitq.c | 4 | ||||
-rw-r--r-- | fs/cifs/cifs_dfs_ref.c | 7 | ||||
-rw-r--r-- | fs/debugfs/inode.c | 8 | ||||
-rw-r--r-- | fs/namei.c | 3 | ||||
-rw-r--r-- | fs/namespace.c | 17 | ||||
-rw-r--r-- | fs/nfs/namespace.c | 2 | ||||
-rw-r--r-- | fs/nfs/nfs4namespace.c | 2 | ||||
-rw-r--r-- | fs/super.c | 13 | ||||
-rw-r--r-- | include/linux/debugfs.h | 3 | ||||
-rw-r--r-- | include/linux/mount.h | 3 | ||||
-rw-r--r-- | include/uapi/linux/fs.h | 1 | ||||
-rw-r--r-- | kernel/trace/trace.c | 4 |
13 files changed, 47 insertions, 22 deletions
diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c index 81dd075356b9..d4fb0afc0097 100644 --- a/fs/afs/mntpt.c +++ b/fs/afs/mntpt.c | |||
@@ -202,7 +202,7 @@ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt) | |||
202 | 202 | ||
203 | /* try and do the mount */ | 203 | /* try and do the mount */ |
204 | _debug("--- attempting mount %s -o %s ---", devname, options); | 204 | _debug("--- attempting mount %s -o %s ---", devname, options); |
205 | mnt = vfs_kern_mount(&afs_fs_type, 0, devname, options); | 205 | mnt = vfs_submount(mntpt, &afs_fs_type, devname, options); |
206 | _debug("--- mount result %p ---", mnt); | 206 | _debug("--- mount result %p ---", mnt); |
207 | 207 | ||
208 | free_page((unsigned long) devname); | 208 | free_page((unsigned long) devname); |
diff --git a/fs/autofs4/waitq.c b/fs/autofs4/waitq.c index 1278335ce366..79fbd85db4ba 100644 --- a/fs/autofs4/waitq.c +++ b/fs/autofs4/waitq.c | |||
@@ -436,8 +436,8 @@ int autofs4_wait(struct autofs_sb_info *sbi, | |||
436 | memcpy(&wq->name, &qstr, sizeof(struct qstr)); | 436 | memcpy(&wq->name, &qstr, sizeof(struct qstr)); |
437 | wq->dev = autofs4_get_dev(sbi); | 437 | wq->dev = autofs4_get_dev(sbi); |
438 | wq->ino = autofs4_get_ino(sbi); | 438 | wq->ino = autofs4_get_ino(sbi); |
439 | wq->uid = current_real_cred()->uid; | 439 | wq->uid = current_cred()->uid; |
440 | wq->gid = current_real_cred()->gid; | 440 | wq->gid = current_cred()->gid; |
441 | wq->pid = pid; | 441 | wq->pid = pid; |
442 | wq->tgid = tgid; | 442 | wq->tgid = tgid; |
443 | wq->status = -EINTR; /* Status return if interrupted */ | 443 | wq->status = -EINTR; /* Status return if interrupted */ |
diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c index ec9dbbcca3b9..9156be545b0f 100644 --- a/fs/cifs/cifs_dfs_ref.c +++ b/fs/cifs/cifs_dfs_ref.c | |||
@@ -245,7 +245,8 @@ compose_mount_options_err: | |||
245 | * @fullpath: full path in UNC format | 245 | * @fullpath: full path in UNC format |
246 | * @ref: server's referral | 246 | * @ref: server's referral |
247 | */ | 247 | */ |
248 | static struct vfsmount *cifs_dfs_do_refmount(struct cifs_sb_info *cifs_sb, | 248 | static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt, |
249 | struct cifs_sb_info *cifs_sb, | ||
249 | const char *fullpath, const struct dfs_info3_param *ref) | 250 | const char *fullpath, const struct dfs_info3_param *ref) |
250 | { | 251 | { |
251 | struct vfsmount *mnt; | 252 | struct vfsmount *mnt; |
@@ -259,7 +260,7 @@ static struct vfsmount *cifs_dfs_do_refmount(struct cifs_sb_info *cifs_sb, | |||
259 | if (IS_ERR(mountdata)) | 260 | if (IS_ERR(mountdata)) |
260 | return (struct vfsmount *)mountdata; | 261 | return (struct vfsmount *)mountdata; |
261 | 262 | ||
262 | mnt = vfs_kern_mount(&cifs_fs_type, 0, devname, mountdata); | 263 | mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata); |
263 | kfree(mountdata); | 264 | kfree(mountdata); |
264 | kfree(devname); | 265 | kfree(devname); |
265 | return mnt; | 266 | return mnt; |
@@ -334,7 +335,7 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) | |||
334 | mnt = ERR_PTR(-EINVAL); | 335 | mnt = ERR_PTR(-EINVAL); |
335 | break; | 336 | break; |
336 | } | 337 | } |
337 | mnt = cifs_dfs_do_refmount(cifs_sb, | 338 | mnt = cifs_dfs_do_refmount(mntpt, cifs_sb, |
338 | full_path, referrals + i); | 339 | full_path, referrals + i); |
339 | cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", | 340 | cifs_dbg(FYI, "%s: cifs_dfs_do_refmount:%s , mnt:%p\n", |
340 | __func__, referrals[i].node_name, mnt); | 341 | __func__, referrals[i].node_name, mnt); |
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index f17fcf89e18e..1e30f74a9527 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c | |||
@@ -187,9 +187,9 @@ static const struct super_operations debugfs_super_operations = { | |||
187 | 187 | ||
188 | static struct vfsmount *debugfs_automount(struct path *path) | 188 | static struct vfsmount *debugfs_automount(struct path *path) |
189 | { | 189 | { |
190 | struct vfsmount *(*f)(void *); | 190 | debugfs_automount_t f; |
191 | f = (struct vfsmount *(*)(void *))path->dentry->d_fsdata; | 191 | f = (debugfs_automount_t)path->dentry->d_fsdata; |
192 | return f(d_inode(path->dentry)->i_private); | 192 | return f(path->dentry, d_inode(path->dentry)->i_private); |
193 | } | 193 | } |
194 | 194 | ||
195 | static const struct dentry_operations debugfs_dops = { | 195 | static const struct dentry_operations debugfs_dops = { |
@@ -504,7 +504,7 @@ EXPORT_SYMBOL_GPL(debugfs_create_dir); | |||
504 | */ | 504 | */ |
505 | struct dentry *debugfs_create_automount(const char *name, | 505 | struct dentry *debugfs_create_automount(const char *name, |
506 | struct dentry *parent, | 506 | struct dentry *parent, |
507 | struct vfsmount *(*f)(void *), | 507 | debugfs_automount_t f, |
508 | void *data) | 508 | void *data) |
509 | { | 509 | { |
510 | struct dentry *dentry = start_creating(name, parent); | 510 | struct dentry *dentry = start_creating(name, parent); |
diff --git a/fs/namei.c b/fs/namei.c index 6fa3e9138fe4..da689c9c005e 100644 --- a/fs/namei.c +++ b/fs/namei.c | |||
@@ -1100,7 +1100,6 @@ static int follow_automount(struct path *path, struct nameidata *nd, | |||
1100 | bool *need_mntput) | 1100 | bool *need_mntput) |
1101 | { | 1101 | { |
1102 | struct vfsmount *mnt; | 1102 | struct vfsmount *mnt; |
1103 | const struct cred *old_cred; | ||
1104 | int err; | 1103 | int err; |
1105 | 1104 | ||
1106 | if (!path->dentry->d_op || !path->dentry->d_op->d_automount) | 1105 | if (!path->dentry->d_op || !path->dentry->d_op->d_automount) |
@@ -1129,9 +1128,7 @@ static int follow_automount(struct path *path, struct nameidata *nd, | |||
1129 | if (nd->total_link_count >= 40) | 1128 | if (nd->total_link_count >= 40) |
1130 | return -ELOOP; | 1129 | return -ELOOP; |
1131 | 1130 | ||
1132 | old_cred = override_creds(&init_cred); | ||
1133 | mnt = path->dentry->d_op->d_automount(path); | 1131 | mnt = path->dentry->d_op->d_automount(path); |
1134 | revert_creds(old_cred); | ||
1135 | if (IS_ERR(mnt)) { | 1132 | if (IS_ERR(mnt)) { |
1136 | /* | 1133 | /* |
1137 | * The filesystem is allowed to return -EISDIR here to indicate | 1134 | * The filesystem is allowed to return -EISDIR here to indicate |
diff --git a/fs/namespace.c b/fs/namespace.c index 487ba30bb5c6..089a6b23135a 100644 --- a/fs/namespace.c +++ b/fs/namespace.c | |||
@@ -989,6 +989,21 @@ vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void | |||
989 | } | 989 | } |
990 | EXPORT_SYMBOL_GPL(vfs_kern_mount); | 990 | EXPORT_SYMBOL_GPL(vfs_kern_mount); |
991 | 991 | ||
992 | struct vfsmount * | ||
993 | vfs_submount(const struct dentry *mountpoint, struct file_system_type *type, | ||
994 | const char *name, void *data) | ||
995 | { | ||
996 | /* Until it is worked out how to pass the user namespace | ||
997 | * through from the parent mount to the submount don't support | ||
998 | * unprivileged mounts with submounts. | ||
999 | */ | ||
1000 | if (mountpoint->d_sb->s_user_ns != &init_user_ns) | ||
1001 | return ERR_PTR(-EPERM); | ||
1002 | |||
1003 | return vfs_kern_mount(type, MS_SUBMOUNT, name, data); | ||
1004 | } | ||
1005 | EXPORT_SYMBOL_GPL(vfs_submount); | ||
1006 | |||
992 | static struct mount *clone_mnt(struct mount *old, struct dentry *root, | 1007 | static struct mount *clone_mnt(struct mount *old, struct dentry *root, |
993 | int flag) | 1008 | int flag) |
994 | { | 1009 | { |
@@ -2794,7 +2809,7 @@ long do_mount(const char *dev_name, const char __user *dir_name, | |||
2794 | 2809 | ||
2795 | flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN | | 2810 | flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN | |
2796 | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | | 2811 | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | |
2797 | MS_STRICTATIME | MS_NOREMOTELOCK); | 2812 | MS_STRICTATIME | MS_NOREMOTELOCK | MS_SUBMOUNT); |
2798 | 2813 | ||
2799 | if (flags & MS_REMOUNT) | 2814 | if (flags & MS_REMOUNT) |
2800 | retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags, | 2815 | retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags, |
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c index 5551e8ef67fd..e49d831c4e85 100644 --- a/fs/nfs/namespace.c +++ b/fs/nfs/namespace.c | |||
@@ -226,7 +226,7 @@ static struct vfsmount *nfs_do_clone_mount(struct nfs_server *server, | |||
226 | const char *devname, | 226 | const char *devname, |
227 | struct nfs_clone_mount *mountdata) | 227 | struct nfs_clone_mount *mountdata) |
228 | { | 228 | { |
229 | return vfs_kern_mount(&nfs_xdev_fs_type, 0, devname, mountdata); | 229 | return vfs_submount(mountdata->dentry, &nfs_xdev_fs_type, devname, mountdata); |
230 | } | 230 | } |
231 | 231 | ||
232 | /** | 232 | /** |
diff --git a/fs/nfs/nfs4namespace.c b/fs/nfs/nfs4namespace.c index d21104912676..d8b040bd9814 100644 --- a/fs/nfs/nfs4namespace.c +++ b/fs/nfs/nfs4namespace.c | |||
@@ -279,7 +279,7 @@ static struct vfsmount *try_location(struct nfs_clone_mount *mountdata, | |||
279 | mountdata->hostname, | 279 | mountdata->hostname, |
280 | mountdata->mnt_path); | 280 | mountdata->mnt_path); |
281 | 281 | ||
282 | mnt = vfs_kern_mount(&nfs4_referral_fs_type, 0, page, mountdata); | 282 | mnt = vfs_submount(mountdata->dentry, &nfs4_referral_fs_type, page, mountdata); |
283 | if (!IS_ERR(mnt)) | 283 | if (!IS_ERR(mnt)) |
284 | break; | 284 | break; |
285 | } | 285 | } |
diff --git a/fs/super.c b/fs/super.c index 1709ed029a2c..4185844f7a12 100644 --- a/fs/super.c +++ b/fs/super.c | |||
@@ -469,7 +469,7 @@ struct super_block *sget_userns(struct file_system_type *type, | |||
469 | struct super_block *old; | 469 | struct super_block *old; |
470 | int err; | 470 | int err; |
471 | 471 | ||
472 | if (!(flags & MS_KERNMOUNT) && | 472 | if (!(flags & (MS_KERNMOUNT|MS_SUBMOUNT)) && |
473 | !(type->fs_flags & FS_USERNS_MOUNT) && | 473 | !(type->fs_flags & FS_USERNS_MOUNT) && |
474 | !capable(CAP_SYS_ADMIN)) | 474 | !capable(CAP_SYS_ADMIN)) |
475 | return ERR_PTR(-EPERM); | 475 | return ERR_PTR(-EPERM); |
@@ -499,7 +499,7 @@ retry: | |||
499 | } | 499 | } |
500 | if (!s) { | 500 | if (!s) { |
501 | spin_unlock(&sb_lock); | 501 | spin_unlock(&sb_lock); |
502 | s = alloc_super(type, flags, user_ns); | 502 | s = alloc_super(type, (flags & ~MS_SUBMOUNT), user_ns); |
503 | if (!s) | 503 | if (!s) |
504 | return ERR_PTR(-ENOMEM); | 504 | return ERR_PTR(-ENOMEM); |
505 | goto retry; | 505 | goto retry; |
@@ -540,8 +540,15 @@ struct super_block *sget(struct file_system_type *type, | |||
540 | { | 540 | { |
541 | struct user_namespace *user_ns = current_user_ns(); | 541 | struct user_namespace *user_ns = current_user_ns(); |
542 | 542 | ||
543 | /* We don't yet pass the user namespace of the parent | ||
544 | * mount through to here so always use &init_user_ns | ||
545 | * until that changes. | ||
546 | */ | ||
547 | if (flags & MS_SUBMOUNT) | ||
548 | user_ns = &init_user_ns; | ||
549 | |||
543 | /* Ensure the requestor has permissions over the target filesystem */ | 550 | /* Ensure the requestor has permissions over the target filesystem */ |
544 | if (!(flags & MS_KERNMOUNT) && !ns_capable(user_ns, CAP_SYS_ADMIN)) | 551 | if (!(flags & (MS_KERNMOUNT|MS_SUBMOUNT)) && !ns_capable(user_ns, CAP_SYS_ADMIN)) |
545 | return ERR_PTR(-EPERM); | 552 | return ERR_PTR(-EPERM); |
546 | 553 | ||
547 | return sget_userns(type, test, set, flags, user_ns, data); | 554 | return sget_userns(type, test, set, flags, user_ns, data); |
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index 014cc564d1c4..233006be30aa 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h | |||
@@ -97,9 +97,10 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent); | |||
97 | struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent, | 97 | struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent, |
98 | const char *dest); | 98 | const char *dest); |
99 | 99 | ||
100 | typedef struct vfsmount *(*debugfs_automount_t)(struct dentry *, void *); | ||
100 | struct dentry *debugfs_create_automount(const char *name, | 101 | struct dentry *debugfs_create_automount(const char *name, |
101 | struct dentry *parent, | 102 | struct dentry *parent, |
102 | struct vfsmount *(*f)(void *), | 103 | debugfs_automount_t f, |
103 | void *data); | 104 | void *data); |
104 | 105 | ||
105 | void debugfs_remove(struct dentry *dentry); | 106 | void debugfs_remove(struct dentry *dentry); |
diff --git a/include/linux/mount.h b/include/linux/mount.h index c6f55158d5e5..8e0352af06b7 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h | |||
@@ -90,6 +90,9 @@ struct file_system_type; | |||
90 | extern struct vfsmount *vfs_kern_mount(struct file_system_type *type, | 90 | extern struct vfsmount *vfs_kern_mount(struct file_system_type *type, |
91 | int flags, const char *name, | 91 | int flags, const char *name, |
92 | void *data); | 92 | void *data); |
93 | extern struct vfsmount *vfs_submount(const struct dentry *mountpoint, | ||
94 | struct file_system_type *type, | ||
95 | const char *name, void *data); | ||
93 | 96 | ||
94 | extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list); | 97 | extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list); |
95 | extern void mark_mounts_for_expiry(struct list_head *mounts); | 98 | extern void mark_mounts_for_expiry(struct list_head *mounts); |
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 36da93fbf188..048a85e9f017 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h | |||
@@ -132,6 +132,7 @@ struct inodes_stat_t { | |||
132 | #define MS_LAZYTIME (1<<25) /* Update the on-disk [acm]times lazily */ | 132 | #define MS_LAZYTIME (1<<25) /* Update the on-disk [acm]times lazily */ |
133 | 133 | ||
134 | /* These sb flags are internal to the kernel */ | 134 | /* These sb flags are internal to the kernel */ |
135 | #define MS_SUBMOUNT (1<<26) | ||
135 | #define MS_NOREMOTELOCK (1<<27) | 136 | #define MS_NOREMOTELOCK (1<<27) |
136 | #define MS_NOSEC (1<<28) | 137 | #define MS_NOSEC (1<<28) |
137 | #define MS_BORN (1<<29) | 138 | #define MS_BORN (1<<29) |
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index d7449783987a..310f0ea0d1a2 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c | |||
@@ -7503,7 +7503,7 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer) | |||
7503 | ftrace_init_tracefs(tr, d_tracer); | 7503 | ftrace_init_tracefs(tr, d_tracer); |
7504 | } | 7504 | } |
7505 | 7505 | ||
7506 | static struct vfsmount *trace_automount(void *ingore) | 7506 | static struct vfsmount *trace_automount(struct dentry *mntpt, void *ingore) |
7507 | { | 7507 | { |
7508 | struct vfsmount *mnt; | 7508 | struct vfsmount *mnt; |
7509 | struct file_system_type *type; | 7509 | struct file_system_type *type; |
@@ -7516,7 +7516,7 @@ static struct vfsmount *trace_automount(void *ingore) | |||
7516 | type = get_fs_type("tracefs"); | 7516 | type = get_fs_type("tracefs"); |
7517 | if (!type) | 7517 | if (!type) |
7518 | return NULL; | 7518 | return NULL; |
7519 | mnt = vfs_kern_mount(type, 0, "tracefs", NULL); | 7519 | mnt = vfs_submount(mntpt, type, "tracefs", NULL); |
7520 | put_filesystem(type); | 7520 | put_filesystem(type); |
7521 | if (IS_ERR(mnt)) | 7521 | if (IS_ERR(mnt)) |
7522 | return NULL; | 7522 | return NULL; |