Commit b2bc2ff7 authored by Seth Forshee's avatar Seth Forshee Committed by Eric W. Biederman

fs: Add user namesapace member to struct super_block

Initially this will be used to eliminate the implicit MNT_NODEV
flag for mounts from user namespaces. In the future it will also
be used for translating ids and checking capabilities for
filesystems mounted from user namespaces.

s_user_ns is initialized in alloc_super() and is generally set to
current_user_ns(). To avoid security and corruption issues, two
additional mount checks are also added:

 - do_new_mount() gains a check that the user has CAP_SYS_ADMIN
   in current_user_ns().

 - sget() will fail with EBUSY when the filesystem it's looking
   for is already mounted from another user namespace.

proc requires some special handling. The user namespace of
current isn't appropriate when forking as a result of clone (2)
with CLONE_NEWPID|CLONE_NEWUSER, as it will set s_user_ns to the
namespace of the parent and make proc unmountable in the new user
namespace. Instead, the user namespace which owns the new pid
namespace is used. sget_userns() is allowed to allow passing in
a namespace other than that of current, and sget becomes a
wrapper around sget_userns() which passes current_user_ns().

Changes to original version of this patch
  * Documented @user_ns in sget_userns, alloc_super and fs.h
  * Kept an blank line in fs.h
  * Removed unncessary include of user_namespace.h from fs.h
  * Tweaked the location of get_user_ns and put_user_ns so
    the security modules can (if they wish) depend on it.
  -- EWB
Signed-off-by: default avatarSeth Forshee <seth.forshee@canonical.com>
Signed-off-by: default avatarEric W. Biederman <ebiederm@xmission.com>
parent 6ff33f39
...@@ -2381,6 +2381,9 @@ static int do_new_mount(struct path *path, const char *fstype, int flags, ...@@ -2381,6 +2381,9 @@ static int do_new_mount(struct path *path, const char *fstype, int flags,
struct vfsmount *mnt; struct vfsmount *mnt;
int err; int err;
if (!ns_capable(current_user_ns(), CAP_SYS_ADMIN))
return -EPERM;
if (!fstype) if (!fstype)
return -EINVAL; return -EINVAL;
......
...@@ -117,7 +117,8 @@ static struct dentry *proc_mount(struct file_system_type *fs_type, ...@@ -117,7 +117,8 @@ static struct dentry *proc_mount(struct file_system_type *fs_type,
return ERR_PTR(-EPERM); return ERR_PTR(-EPERM);
} }
sb = sget(fs_type, proc_test_super, proc_set_super, flags, ns); sb = sget_userns(fs_type, proc_test_super, proc_set_super, flags,
ns->user_ns, ns);
if (IS_ERR(sb)) if (IS_ERR(sb))
return ERR_CAST(sb); return ERR_CAST(sb);
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include <linux/cleancache.h> #include <linux/cleancache.h>
#include <linux/fsnotify.h> #include <linux/fsnotify.h>
#include <linux/lockdep.h> #include <linux/lockdep.h>
#include <linux/user_namespace.h>
#include "internal.h" #include "internal.h"
...@@ -165,6 +166,7 @@ static void destroy_super(struct super_block *s) ...@@ -165,6 +166,7 @@ static void destroy_super(struct super_block *s)
list_lru_destroy(&s->s_inode_lru); list_lru_destroy(&s->s_inode_lru);
security_sb_free(s); security_sb_free(s);
WARN_ON(!list_empty(&s->s_mounts)); WARN_ON(!list_empty(&s->s_mounts));
put_user_ns(s->s_user_ns);
kfree(s->s_subtype); kfree(s->s_subtype);
kfree(s->s_options); kfree(s->s_options);
call_rcu(&s->rcu, destroy_super_rcu); call_rcu(&s->rcu, destroy_super_rcu);
...@@ -174,11 +176,13 @@ static void destroy_super(struct super_block *s) ...@@ -174,11 +176,13 @@ static void destroy_super(struct super_block *s)
* alloc_super - create new superblock * alloc_super - create new superblock
* @type: filesystem type superblock should belong to * @type: filesystem type superblock should belong to
* @flags: the mount flags * @flags: the mount flags
* @user_ns: User namespace for the super_block
* *
* Allocates and initializes a new &struct super_block. alloc_super() * Allocates and initializes a new &struct super_block. alloc_super()
* returns a pointer new superblock or %NULL if allocation had failed. * returns a pointer new superblock or %NULL if allocation had failed.
*/ */
static struct super_block *alloc_super(struct file_system_type *type, int flags) static struct super_block *alloc_super(struct file_system_type *type, int flags,
struct user_namespace *user_ns)
{ {
struct super_block *s = kzalloc(sizeof(struct super_block), GFP_USER); struct super_block *s = kzalloc(sizeof(struct super_block), GFP_USER);
static const struct super_operations default_op; static const struct super_operations default_op;
...@@ -188,6 +192,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags) ...@@ -188,6 +192,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags)
return NULL; return NULL;
INIT_LIST_HEAD(&s->s_mounts); INIT_LIST_HEAD(&s->s_mounts);
s->s_user_ns = get_user_ns(user_ns);
if (security_sb_alloc(s)) if (security_sb_alloc(s))
goto fail; goto fail;
...@@ -442,17 +447,18 @@ void generic_shutdown_super(struct super_block *sb) ...@@ -442,17 +447,18 @@ void generic_shutdown_super(struct super_block *sb)
EXPORT_SYMBOL(generic_shutdown_super); EXPORT_SYMBOL(generic_shutdown_super);
/** /**
* sget - find or create a superblock * sget_userns - find or create a superblock
* @type: filesystem type superblock should belong to * @type: filesystem type superblock should belong to
* @test: comparison callback * @test: comparison callback
* @set: setup callback * @set: setup callback
* @flags: mount flags * @flags: mount flags
* @user_ns: User namespace for the super_block
* @data: argument to each of them * @data: argument to each of them
*/ */
struct super_block *sget(struct file_system_type *type, struct super_block *sget_userns(struct file_system_type *type,
int (*test)(struct super_block *,void *), int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *), int (*set)(struct super_block *,void *),
int flags, int flags, struct user_namespace *user_ns,
void *data) void *data)
{ {
struct super_block *s = NULL; struct super_block *s = NULL;
...@@ -465,6 +471,10 @@ struct super_block *sget(struct file_system_type *type, ...@@ -465,6 +471,10 @@ struct super_block *sget(struct file_system_type *type,
hlist_for_each_entry(old, &type->fs_supers, s_instances) { hlist_for_each_entry(old, &type->fs_supers, s_instances) {
if (!test(old, data)) if (!test(old, data))
continue; continue;
if (user_ns != old->s_user_ns) {
spin_unlock(&sb_lock);
return ERR_PTR(-EBUSY);
}
if (!grab_super(old)) if (!grab_super(old))
goto retry; goto retry;
if (s) { if (s) {
...@@ -477,7 +487,7 @@ struct super_block *sget(struct file_system_type *type, ...@@ -477,7 +487,7 @@ struct super_block *sget(struct file_system_type *type,
} }
if (!s) { if (!s) {
spin_unlock(&sb_lock); spin_unlock(&sb_lock);
s = alloc_super(type, flags); s = alloc_super(type, flags, user_ns);
if (!s) if (!s)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
goto retry; goto retry;
...@@ -500,6 +510,25 @@ struct super_block *sget(struct file_system_type *type, ...@@ -500,6 +510,25 @@ struct super_block *sget(struct file_system_type *type,
return s; return s;
} }
EXPORT_SYMBOL(sget_userns);
/**
* sget - find or create a superblock
* @type: filesystem type superblock should belong to
* @test: comparison callback
* @set: setup callback
* @flags: mount flags
* @data: argument to each of them
*/
struct super_block *sget(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
int flags,
void *data)
{
return sget_userns(type, test, set, flags, current_user_ns(), data);
}
EXPORT_SYMBOL(sget); EXPORT_SYMBOL(sget);
void drop_super(struct super_block *sb) void drop_super(struct super_block *sb)
......
...@@ -1367,6 +1367,13 @@ struct super_block { ...@@ -1367,6 +1367,13 @@ struct super_block {
struct workqueue_struct *s_dio_done_wq; struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins; struct hlist_head s_pins;
/*
* Context in which to interpret filesystem uids, gids,
* quotas, device nodes, extended attributes and security
* labels.
*/
struct user_namespace *s_user_ns;
/* /*
* Keep the lru lists last in the structure so they always sit on their * Keep the lru lists last in the structure so they always sit on their
* own individual cachelines. * own individual cachelines.
...@@ -1984,6 +1991,11 @@ void deactivate_locked_super(struct super_block *sb); ...@@ -1984,6 +1991,11 @@ void deactivate_locked_super(struct super_block *sb);
int set_anon_super(struct super_block *s, void *data); int set_anon_super(struct super_block *s, void *data);
int get_anon_bdev(dev_t *); int get_anon_bdev(dev_t *);
void free_anon_bdev(dev_t); void free_anon_bdev(dev_t);
struct super_block *sget_userns(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
int flags, struct user_namespace *user_ns,
void *data);
struct super_block *sget(struct file_system_type *type, struct super_block *sget(struct file_system_type *type,
int (*test)(struct super_block *,void *), int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *), int (*set)(struct super_block *,void *),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment