summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-07-03 15:20:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2015-07-03 15:20:57 -0700
commit0cbee992696236227a7ea411e4b0fbf73b918b6a (patch)
treee84f0a8e64d64ff910e5b373537e4bd21e03f607
parent2fee94b74b45681a09b1dac54cb615e02b7b30d0 (diff)
parent93e3bce6287e1fb3e60d3324ed08555b5bbafa89 (diff)
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace
Pull user namespace updates from Eric Biederman: "Long ago and far away when user namespaces where young it was realized that allowing fresh mounts of proc and sysfs with only user namespace permissions could violate the basic rule that only root gets to decide if proc or sysfs should be mounted at all. Some hacks were put in place to reduce the worst of the damage could be done, and the common sense rule was adopted that fresh mounts of proc and sysfs should allow no more than bind mounts of proc and sysfs. Unfortunately that rule has not been fully enforced. There are two kinds of gaps in that enforcement. Only filesystems mounted on empty directories of proc and sysfs should be ignored but the test for empty directories was insufficient. So in my tree directories on proc, sysctl and sysfs that will always be empty are created specially. Every other technique is imperfect as an ordinary directory can have entries added even after a readdir returns and shows that the directory is empty. Special creation of directories for mount points makes the code in the kernel a smidge clearer about it's purpose. I asked container developers from the various container projects to help test this and no holes were found in the set of mount points on proc and sysfs that are created specially. This set of changes also starts enforcing the mount flags of fresh mounts of proc and sysfs are consistent with the existing mount of proc and sysfs. I expected this to be the boring part of the work but unfortunately unprivileged userspace winds up mounting fresh copies of proc and sysfs with noexec and nosuid clear when root set those flags on the previous mount of proc and sysfs. So for now only the atime, read-only and nodev attributes which userspace happens to keep consistent are enforced. Dealing with the noexec and nosuid attributes remains for another time. This set of changes also addresses an issue with how open file descriptors from /proc/<pid>/ns/* are displayed. Recently readlink of /proc/<pid>/fd has been triggering a WARN_ON that has not been meaningful since it was added (as all of the code in the kernel was converted) and is not now actively wrong. There is also a short list of issues that have not been fixed yet that I will mention briefly. It is possible to rename a directory from below to above a bind mount. At which point any directory pointers below the renamed directory can be walked up to the root directory of the filesystem. With user namespaces enabled a bind mount of the bind mount can be created allowing the user to pick a directory whose children they can rename to outside of the bind mount. This is challenging to fix and doubly so because all obvious solutions must touch code that is in the performance part of pathname resolution. As mentioned above there is also a question of how to ensure that developers by accident or with purpose do not introduce exectuable files on sysfs and proc and in doing so introduce security regressions in the current userspace that will not be immediately obvious and as such are likely to require breaking userspace in painful ways once they are recognized" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace: vfs: Remove incorrect debugging WARN in prepend_path mnt: Update fs_fully_visible to test for permanently empty directories sysfs: Create mountpoints with sysfs_create_mount_point sysfs: Add support for permanently empty directories to serve as mount points. kernfs: Add support for always empty directories. proc: Allow creating permanently empty directories that serve as mount points sysctl: Allow creating permanently empty directories that serve as mountpoints. fs: Add helper functions for permanently empty directories. vfs: Ignore unlocked mounts in fs_fully_visible mnt: Modify fs_fully_visible to deal with locked ro nodev and atime mnt: Refactor the logic for mounting sysfs and proc in a user namespace
-rw-r--r--arch/s390/hypfs/inode.c12
-rw-r--r--drivers/firmware/efi/efi.c6
-rw-r--r--fs/configfs/mount.c10
-rw-r--r--fs/dcache.c11
-rw-r--r--fs/debugfs/inode.c11
-rw-r--r--fs/fuse/inode.c9
-rw-r--r--fs/kernfs/dir.c38
-rw-r--r--fs/kernfs/inode.c2
-rw-r--r--fs/libfs.c95
-rw-r--r--fs/namespace.c39
-rw-r--r--fs/proc/generic.c23
-rw-r--r--fs/proc/inode.c4
-rw-r--r--fs/proc/internal.h6
-rw-r--r--fs/proc/proc_sysctl.c37
-rw-r--r--fs/proc/root.c9
-rw-r--r--fs/pstore/inode.c12
-rw-r--r--fs/sysfs/dir.c34
-rw-r--r--fs/sysfs/mount.c5
-rw-r--r--fs/tracefs/inode.c6
-rw-r--r--include/linux/fs.h4
-rw-r--r--include/linux/kernfs.h3
-rw-r--r--include/linux/sysctl.h3
-rw-r--r--include/linux/sysfs.h15
-rw-r--r--kernel/cgroup.c10
-rw-r--r--kernel/sysctl.c8
-rw-r--r--security/inode.c10
-rw-r--r--security/selinux/selinuxfs.c11
-rw-r--r--security/smack/smackfs.c8
28 files changed, 340 insertions, 101 deletions
diff --git a/arch/s390/hypfs/inode.c b/arch/s390/hypfs/inode.c
index d3f896a35b98..2eeb0a0f506d 100644
--- a/arch/s390/hypfs/inode.c
+++ b/arch/s390/hypfs/inode.c
@@ -456,8 +456,6 @@ static const struct super_operations hypfs_s_ops = {
.show_options = hypfs_show_options,
};
-static struct kobject *s390_kobj;
-
static int __init hypfs_init(void)
{
int rc;
@@ -481,18 +479,16 @@ static int __init hypfs_init(void)
rc = -ENODATA;
goto fail_hypfs_sprp_exit;
}
- s390_kobj = kobject_create_and_add("s390", hypervisor_kobj);
- if (!s390_kobj) {
- rc = -ENOMEM;
+ rc = sysfs_create_mount_point(hypervisor_kobj, "s390");
+ if (rc)
goto fail_hypfs_diag0c_exit;
- }
rc = register_filesystem(&hypfs_type);
if (rc)
goto fail_filesystem;
return 0;
fail_filesystem:
- kobject_put(s390_kobj);
+ sysfs_remove_mount_point(hypervisor_kobj, "s390");
fail_hypfs_diag0c_exit:
hypfs_diag0c_exit();
fail_hypfs_sprp_exit:
@@ -510,7 +506,7 @@ fail_dbfs_exit:
static void __exit hypfs_exit(void)
{
unregister_filesystem(&hypfs_type);
- kobject_put(s390_kobj);
+ sysfs_remove_mount_point(hypervisor_kobj, "s390");
hypfs_diag0c_exit();
hypfs_sprp_exit();
hypfs_vm_exit();
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index ca617f40574a..9fa8084a7c8d 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -66,7 +66,6 @@ static int __init parse_efi_cmdline(char *str)
early_param("efi", parse_efi_cmdline);
struct kobject *efi_kobj;
-static struct kobject *efivars_kobj;
/*
* Let's not leave out systab information that snuck into
@@ -218,10 +217,9 @@ static int __init efisubsys_init(void)
goto err_remove_group;
/* and the standard mountpoint for efivarfs */
- efivars_kobj = kobject_create_and_add("efivars", efi_kobj);
- if (!efivars_kobj) {
+ error = sysfs_create_mount_point(efi_kobj, "efivars");
+ if (error) {
pr_err("efivars: Subsystem registration failed.\n");
- error = -ENOMEM;
goto err_remove_group;
}
diff --git a/fs/configfs/mount.c b/fs/configfs/mount.c
index 537356742091..a8f3b589a2df 100644
--- a/fs/configfs/mount.c
+++ b/fs/configfs/mount.c
@@ -129,8 +129,6 @@ void configfs_release_fs(void)
}
-static struct kobject *config_kobj;
-
static int __init configfs_init(void)
{
int err = -ENOMEM;
@@ -141,8 +139,8 @@ static int __init configfs_init(void)
if (!configfs_dir_cachep)
goto out;
- config_kobj = kobject_create_and_add("config", kernel_kobj);
- if (!config_kobj)
+ err = sysfs_create_mount_point(kernel_kobj, "config");
+ if (err)
goto out2;
err = register_filesystem(&configfs_fs_type);
@@ -152,7 +150,7 @@ static int __init configfs_init(void)
return 0;
out3:
pr_err("Unable to register filesystem!\n");
- kobject_put(config_kobj);
+ sysfs_remove_mount_point(kernel_kobj, "config");
out2:
kmem_cache_destroy(configfs_dir_cachep);
configfs_dir_cachep = NULL;
@@ -163,7 +161,7 @@ out:
static void __exit configfs_exit(void)
{
unregister_filesystem(&configfs_fs_type);
- kobject_put(config_kobj);
+ sysfs_remove_mount_point(kernel_kobj, "config");
kmem_cache_destroy(configfs_dir_cachep);
configfs_dir_cachep = NULL;
}
diff --git a/fs/dcache.c b/fs/dcache.c
index 592c4b582495..910968b4b6bf 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -2927,17 +2927,6 @@ restart:
vfsmnt = &mnt->mnt;
continue;
}
- /*
- * Filesystems needing to implement special "root names"
- * should do so with ->d_dname()
- */
- if (IS_ROOT(dentry) &&
- (dentry->d_name.len != 1 ||
- dentry->d_name.name[0] != '/')) {
- WARN(1, "Root dentry has weird name <%.*s>\n",
- (int) dentry->d_name.len,
- dentry->d_name.name);
- }
if (!error)
error = is_mounted(vfsmnt) ? 1 : 2;
break;
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
index 7eaec88ea970..d6d1cf004123 100644
--- a/fs/debugfs/inode.c
+++ b/fs/debugfs/inode.c
@@ -716,20 +716,17 @@ bool debugfs_initialized(void)
}
EXPORT_SYMBOL_GPL(debugfs_initialized);
-
-static struct kobject *debug_kobj;
-
static int __init debugfs_init(void)
{
int retval;
- debug_kobj = kobject_create_and_add("debug", kernel_kobj);
- if (!debug_kobj)
- return -EINVAL;
+ retval = sysfs_create_mount_point(kernel_kobj, "debug");
+ if (retval)
+ return retval;
retval = register_filesystem(&debug_fs_type);
if (retval)
- kobject_put(debug_kobj);
+ sysfs_remove_mount_point(kernel_kobj, "debug");
else
debugfs_registered = true;
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index ac81f48ab2f4..2913db2a5b99 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1294,7 +1294,6 @@ static void fuse_fs_cleanup(void)
}
static struct kobject *fuse_kobj;
-static struct kobject *connections_kobj;
static int fuse_sysfs_init(void)
{
@@ -1306,11 +1305,9 @@ static int fuse_sysfs_init(void)
goto out_err;
}
- connections_kobj = kobject_create_and_add("connections", fuse_kobj);
- if (!connections_kobj) {
- err = -ENOMEM;
+ err = sysfs_create_mount_point(fuse_kobj, "connections");
+ if (err)
goto out_fuse_unregister;
- }
return 0;
@@ -1322,7 +1319,7 @@ static int fuse_sysfs_init(void)
static void fuse_sysfs_cleanup(void)
{
- kobject_put(connections_kobj);
+ sysfs_remove_mount_point(fuse_kobj, "connections");
kobject_put(fuse_kobj);
}
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index fffca9517321..2d48d28e1640 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -592,6 +592,9 @@ int kernfs_add_one(struct kernfs_node *kn)
goto out_unlock;
ret = -ENOENT;
+ if (parent->flags & KERNFS_EMPTY_DIR)
+ goto out_unlock;
+
if ((parent->flags & KERNFS_ACTIVATED) && !kernfs_active(parent))
goto out_unlock;
@@ -783,6 +786,38 @@ struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
return ERR_PTR(rc);
}
+/**
+ * kernfs_create_empty_dir - create an always empty directory
+ * @parent: parent in which to create a new directory
+ * @name: name of the new directory
+ *
+ * Returns the created node on success, ERR_PTR() value on failure.
+ */
+struct kernfs_node *kernfs_create_empty_dir(struct kernfs_node *parent,
+ const char *name)
+{
+ struct kernfs_node *kn;
+ int rc;
+
+ /* allocate */
+ kn = kernfs_new_node(parent, name, S_IRUGO|S_IXUGO|S_IFDIR, KERNFS_DIR);
+ if (!kn)
+ return ERR_PTR(-ENOMEM);
+
+ kn->flags |= KERNFS_EMPTY_DIR;
+ kn->dir.root = parent->dir.root;
+ kn->ns = NULL;
+ kn->priv = NULL;
+
+ /* link in */
+ rc = kernfs_add_one(kn);
+ if (!rc)
+ return kn;
+
+ kernfs_put(kn);
+ return ERR_PTR(rc);
+}
+
static struct dentry *kernfs_iop_lookup(struct inode *dir,
struct dentry *dentry,
unsigned int flags)
@@ -1254,7 +1289,8 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent,
mutex_lock(&kernfs_mutex);
error = -ENOENT;
- if (!kernfs_active(kn) || !kernfs_active(new_parent))
+ if (!kernfs_active(kn) || !kernfs_active(new_parent) ||
+ (new_parent->flags & KERNFS_EMPTY_DIR))
goto out;
error = 0;
diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c
index 2da8493a380b..756dd56aaf60 100644
--- a/fs/kernfs/inode.c
+++ b/fs/kernfs/inode.c
@@ -296,6 +296,8 @@ static void kernfs_init_inode(struct kernfs_node *kn, struct inode *inode)
case KERNFS_DIR:
inode->i_op = &kernfs_dir_iops;
inode->i_fop = &kernfs_dir_fops;
+ if (kn->flags & KERNFS_EMPTY_DIR)
+ make_empty_dir_inode(inode);
break;
case KERNFS_FILE:
inode->i_size = kn->attr.size;
diff --git a/fs/libfs.c b/fs/libfs.c
index 65e1feca8b98..88a4cb418756 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -1108,3 +1108,98 @@ const struct inode_operations simple_symlink_inode_operations = {
.readlink = generic_readlink
};
EXPORT_SYMBOL(simple_symlink_inode_operations);
+
+/*
+ * Operations for a permanently empty directory.
+ */
+static struct dentry *empty_dir_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
+{
+ return ERR_PTR(-ENOENT);
+}
+
+static int empty_dir_getattr(struct vfsmount *mnt, struct dentry *dentry,
+ struct kstat *stat)
+{
+ struct inode *inode = d_inode(dentry);
+ generic_fillattr(inode, stat);
+ return 0;
+}
+
+static int empty_dir_setattr(struct dentry *dentry, struct iattr *attr)
+{
+ return -EPERM;
+}
+
+static int empty_dir_setxattr(struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ return -EOPNOTSUPP;
+}
+
+static ssize_t empty_dir_getxattr(struct dentry *dentry, const char *name,
+ void *value, size_t size)
+{
+ return -EOPNOTSUPP;
+}
+
+static int empty_dir_removexattr(struct dentry *dentry, const char *name)
+{
+ return -EOPNOTSUPP;
+}
+
+static ssize_t empty_dir_listxattr(struct dentry *dentry, char *list, size_t size)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct inode_operations empty_dir_inode_operations = {
+ .lookup = empty_dir_lookup,
+ .permission = generic_permission,
+ .setattr = empty_dir_setattr,
+ .getattr = empty_dir_getattr,
+ .setxattr = empty_dir_setxattr,
+ .getxattr = empty_dir_getxattr,
+ .removexattr = empty_dir_removexattr,
+ .listxattr = empty_dir_listxattr,
+};
+
+static loff_t empty_dir_llseek(struct file *file, loff_t offset, int whence)
+{
+ /* An empty directory has two entries . and .. at offsets 0 and 1 */
+ return generic_file_llseek_size(file, offset, whence, 2, 2);
+}
+
+static int empty_dir_readdir(struct file *file, struct dir_context *ctx)
+{
+ dir_emit_dots(file, ctx);
+ return 0;
+}
+
+static const struct file_operations empty_dir_operations = {
+ .llseek = empty_dir_llseek,
+ .read = generic_read_dir,
+ .iterate = empty_dir_readdir,
+ .fsync = noop_fsync,
+};
+
+
+void make_empty_dir_inode(struct inode *inode)
+{
+ set_nlink(inode, 2);
+ inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ inode->i_uid = GLOBAL_ROOT_UID;
+ inode->i_gid = GLOBAL_ROOT_GID;
+ inode->i_rdev = 0;
+ inode->i_size = 2;
+ inode->i_blkbits = PAGE_SHIFT;
+ inode->i_blocks = 0;
+
+ inode->i_op = &empty_dir_inode_operations;
+ inode->i_fop = &empty_dir_operations;
+}
+
+bool is_empty_dir_inode(struct inode *inode)
+{
+ return (inode->i_fop == &empty_dir_operations) &&
+ (inode->i_op == &empty_dir_inode_operations);
+}
diff --git a/fs/namespace.c b/fs/namespace.c
index e99f1f4e00cd..c7cb8a526c05 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2343,6 +2343,8 @@ unlock:
return err;
}
+static bool fs_fully_visible(struct file_system_type *fs_type, int *new_mnt_flags);
+
/*
* create a new mount for userspace and request it to be added into the
* namespace's tree
@@ -2374,6 +2376,10 @@ static int do_new_mount(struct path *path, const char *fstype, int flags,
flags |= MS_NODEV;
mnt_flags |= MNT_NODEV | MNT_LOCK_NODEV;
}
+ if (type->fs_flags & FS_USERNS_VISIBLE) {
+ if (!fs_fully_visible(type, &mnt_flags))
+ return -EPERM;
+ }
}
mnt = vfs_kern_mount(type, flags, name, data);
@@ -3175,9 +3181,10 @@ bool current_chrooted(void)
return chrooted;
}
-bool fs_fully_visible(struct file_system_type *type)
+static bool fs_fully_visible(struct file_system_type *type, int *new_mnt_flags)
{
struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+ int new_flags = *new_mnt_flags;
struct mount *mnt;
bool visible = false;
@@ -3196,16 +3203,36 @@ bool fs_fully_visible(struct file_system_type *type)
if (mnt->mnt.mnt_root != mnt->mnt.mnt_sb->s_root)
continue;
- /* This mount is not fully visible if there are any child mounts
- * that cover anything except for empty directories.
+ /* Verify the mount flags are equal to or more permissive
+ * than the proposed new mount.
+ */
+ if ((mnt->mnt.mnt_flags & MNT_LOCK_READONLY) &&
+ !(new_flags & MNT_READONLY))
+ continue;
+ if ((mnt->mnt.mnt_flags & MNT_LOCK_NODEV) &&
+ !(new_flags & MNT_NODEV))
+ continue;
+ if ((mnt->mnt.mnt_flags & MNT_LOCK_ATIME) &&
+ ((mnt->mnt.mnt_flags & MNT_ATIME_MASK) != (new_flags & MNT_ATIME_MASK)))
+ continue;
+
+ /* This mount is not fully visible if there are any
+ * locked child mounts that cover anything except for
+ * empty directories.
*/
list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
struct inode *inode = child->mnt_mountpoint->d_inode;
- if (!S_ISDIR(inode->i_mode))
- goto next;
- if (inode->i_nlink > 2)
+ /* Only worry about locked mounts */
+ if (!(mnt->mnt.mnt_flags & MNT_LOCKED))
+ continue;
+ /* Is the directory permanetly empty? */
+ if (!is_empty_dir_inode(inode))
goto next;
}
+ /* Preserve the locked attributes */
+ *new_mnt_flags |= mnt->mnt.mnt_flags & (MNT_LOCK_READONLY | \
+ MNT_LOCK_NODEV | \
+ MNT_LOCK_ATIME);
visible = true;
goto found;
next: ;
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index df6327a2b865..e5dee5c3188e 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -373,6 +373,10 @@ static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,
WARN(1, "create '/proc/%s' by hand\n", qstr.name);
return NULL;
}
+ if (is_empty_pde(*parent)) {
+ WARN(1, "attempt to add to permanently empty directory");
+ return NULL;
+ }
ent = kzalloc(sizeof(struct proc_dir_entry) + qstr.len + 1, GFP_KERNEL);
if (!ent)
@@ -455,6 +459,25 @@ struct proc_dir_entry *proc_mkdir(const char *name,
}
EXPORT_SYMBOL(proc_mkdir);
+struct proc_dir_entry *proc_create_mount_point(const char *name)
+{
+ umode_t mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ struct proc_dir_entry *ent, *parent = NULL;
+
+ ent = __proc_create(&parent, name, mode, 2);
+ if (ent) {
+ ent->data = NULL;
+ ent->proc_fops = NULL;
+ ent->proc_iops = NULL;
+ if (proc_register(parent, ent) < 0) {
+ kfree(ent);
+ parent->nlink--;
+ ent = NULL;
+ }
+ }
+ return ent;
+}
+
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct file_operations *proc_fops,
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index afe232b9df6e..bd95b9fdebb0 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -422,6 +422,10 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
PROC_I(inode)->pde = de;
+ if (is_empty_pde(de)) {
+ make_empty_dir_inode(inode);
+ return inode;
+ }
if (de->mode) {
inode->i_mode = de->mode;
inode->i_uid = de->uid;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index c835b94c0cd3..aa2781095bd1 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -191,6 +191,12 @@ static inline struct proc_dir_entry *pde_get(struct proc_dir_entry *pde)
}
extern void pde_put(struct proc_dir_entry *);
+static inline bool is_empty_pde(const struct proc_dir_entry *pde)
+{
+ return S_ISDIR(pde->mode) && !pde->proc_iops;
+}
+struct proc_dir_entry *proc_create_mount_point(const char *name);
+
/*
* inode.c
*/
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index fea2561d773b..fdda62e6115e 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -19,6 +19,28 @@ static const struct inode_operations proc_sys_inode_operations;
static const struct file_operations proc_sys_dir_file_operations;
static const struct inode_operations proc_sys_dir_operations;
+/* Support for permanently empty directories */
+
+struct ctl_table sysctl_mount_point[] = {
+ { }
+};
+
+static bool is_empty_dir(struct ctl_table_header *head)
+{
+ return head->ctl_table[0].child == sysctl_mount_point;
+}
+
+static void set_empty_dir(struct ctl_dir *dir)
+{
+ dir->header.ctl_table[0].child = sysctl_mount_point;
+}
+
+static void clear_empty_dir(struct ctl_dir *dir)
+
+{
+ dir->header.ctl_table[0].child = NULL;
+}
+
void proc_sys_poll_notify(struct ctl_table_poll *poll)
{
if (!poll)
@@ -187,6 +209,17 @@ static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header)
struct ctl_table *entry;
int err;
+ /* Is this a permanently empty directory? */
+ if (is_empty_dir(&dir->header))
+ return -EROFS;
+
+ /* Am I creating a permanently empty directory? */
+ if (header->ctl_table == sysctl_mount_point) {
+ if (!RB_EMPTY_ROOT(&dir->root))
+ return -EINVAL;
+ set_empty_dir(dir);
+ }
+
dir->header.nreg++;
header->parent = dir;
err = insert_links(header);
@@ -202,6 +235,8 @@ fail:
erase_header(header);
put_links(header);
fail_links:
+ if (header->ctl_table == sysctl_mount_point)
+ clear_empty_dir(dir);
header->parent = NULL;
drop_sysctl_table(&dir->header);
return err;
@@ -419,6 +454,8 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,
inode->i_mode |= S_IFDIR;
inode->i_op = &proc_sys_dir_operations;
inode->i_fop = &proc_sys_dir_file_operations;
+ if (is_empty_dir(head))
+ make_empty_dir_inode(inode);
}
out:
return inode;
diff --git a/fs/proc/root.c b/fs/proc/root.c
index b7fa4bfe896a..68feb0f70e63 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -112,9 +112,6 @@ static struct dentry *proc_mount(struct file_system_type *fs_type,
ns = task_active_pid_ns(current);
options = data;
- if (!capable(CAP_SYS_ADMIN) && !fs_fully_visible(fs_type))
- return ERR_PTR(-EPERM);
-
/* Does the mounter have privilege over the pid namespace? */
if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN))
return ERR_PTR(-EPERM);
@@ -159,7 +156,7 @@ static struct file_system_type proc_fs_type = {
.name = "proc",
.mount = proc_mount,
.kill_sb = proc_kill_sb,
- .fs_flags = FS_USERNS_MOUNT,
+ .fs_flags = FS_USERNS_VISIBLE | FS_USERNS_MOUNT,
};
void __init proc_root_init(void)
@@ -182,10 +179,10 @@ void __init proc_root_init(void)
#endif
proc_mkdir("fs", NULL);
proc_mkdir("driver", NULL);
- proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */
+ proc_create_mount_point("fs/nfsd"); /* somewhere for the nfsd filesystem to be mounted */
#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)
/* just give it a mountpoint */
- proc_mkdir("openprom", NULL);
+ proc_create_mount_point("openprom");
#endif
proc_tty_init();
proc_mkdir("bus", NULL);
diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c
index dc43b5f29305..3adcc4669fac 100644
--- a/fs/pstore/inode.c
+++ b/fs/pstore/inode.c
@@ -461,22 +461,18 @@ static struct file_system_type pstore_fs_type = {
.kill_sb = pstore_kill_sb,
};
-static struct kobject *pstore_kobj;
-
static int __init init_pstore_fs(void)
{
- int err = 0;
+ int err;
/* Create a convenient mount point for people to access pstore */
- pstore_kobj = kobject_create_and_add("pstore", fs_kobj);
- if (!pstore_kobj) {
- err = -ENOMEM;
+ err = sysfs_create_mount_point(fs_kobj, "pstore");
+ if (err)
goto out;
- }
err = register_filesystem(&pstore_fs_type);
if (err < 0)
- kobject_put(pstore_kobj);
+ sysfs_remove_mount_point(fs_kobj, "pstore");
out:
return err;
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index 0b45ff42f374..94374e435025 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -121,3 +121,37 @@ int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj,
return kernfs_rename_ns(kn, new_parent, kn->name, new_ns);
}
+
+/**
+ * sysfs_create_mount_point - create an always empty directory
+ * @parent_kobj: kobject that will contain this always empty directory
+ * @name: The name of the always empty directory to add
+ */
+int sysfs_create_mount_point(struct kobject *parent_kobj, const char *name)
+{
+ struct kernfs_node *kn, *parent = parent_kobj->sd;
+
+ kn = kernfs_create_empty_dir(parent, name);
+ if (IS_ERR(kn)) {
+ if (PTR_ERR(kn) == -EEXIST)
+ sysfs_warn_dup(parent, name);
+ return PTR_ERR(kn);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sysfs_create_mount_point);
+
+/**
+ * sysfs_remove_mount_point - remove an always empty directory.
+ * @parent_kobj: kobject that will contain this always empty directory
+ * @name: The name of the always empty directory to remove
+ *
+ */
+void sysfs_remove_mount_point(struct kobject *parent_kobj, const char *name)
+{
+ struct kernfs_node *parent = parent_kobj->sd;
+
+ kernfs_remove_by_name_ns(parent, name, NULL);
+}
+EXPORT_SYMBOL_GPL(sysfs_remove_mount_point);
diff --git a/fs/sysfs/mount.c b/fs/sysfs/mount.c
index 8a49486bf30c..1c6ac6fcee9f 100644
--- a/fs/sysfs/mount.c
+++ b/fs/sysfs/mount.c
@@ -31,9 +31,6 @@ static struct dentry *sysfs_mount(struct file_system_type *fs_type,
bool new_sb;
if (!(flags & MS_KERNMOUNT)) {
- if (!capable(CAP_SYS_ADMIN) && !fs_fully_visible(fs_type))
- return ERR_PTR(-EPERM);
-
if (!kobj_ns_current_may_mount(KOBJ_NS_TYPE_NET))
return ERR_PTR(-EPERM);
}
@@ -58,7 +55,7 @@ static struct file_system_type sysfs_fs_type = {
.name = "sysfs",
.mount = sysfs_mount,
.kill_sb = sysfs_kill_sb,
- .fs_flags = FS_USERNS_MOUNT,
+ .fs_flags = FS_USERNS_VISIBLE | FS_USERNS_MOUNT,
};
int __init sysfs_init(void)
diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c
index d92bdf3b079a..a43df11a163f 100644
--- a/fs/tracefs/inode.c
+++ b/fs/tracefs/inode.c
@@ -631,14 +631,12 @@ bool tracefs_initialized(void)
return tracefs_registered;
}
-static struct kobject *trace_kobj;
-
static int __init tracefs_init(void)
{
int retval;
- trace_kobj = kobject_create_and_add("tracing", kernel_kobj);
- if (!trace_kobj)
+ retval = sysfs_create_mount_point(kernel_kobj, "tracing");
+ if (retval)
return -EINVAL;
retval = register_filesystem(&trace_fs_type);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3f1a84635da8..8a81fcbb0074 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1917,6 +1917,7 @@ struct file_system_type {
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_USERNS_DEV_MOUNT 16 /* A userns mount does not imply MNT_NODEV */
+#define FS_USERNS_VISIBLE 32 /* FS must already be visible */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
@@ -2004,7 +2005,6 @@ extern int vfs_ustat(dev_t, struct kstatfs *);
extern int freeze_super(struct super_block *super);
extern int thaw_super(struct super_block *super);
extern bool our_mnt(struct vfsmount *mnt);
-extern bool fs_fully_visible(struct file_system_type *);
extern int current_umask(void);
@@ -2816,6 +2816,8 @@ extern struct dentry *simple_lookup(struct inode *, struct dentry *, unsigned in
extern ssize_t generic_read_dir(struct file *, char __user *, size_t, loff_t *);
extern const struct file_operations simple_dir_operations;
extern const struct inode_operations simple_dir_inode_operations;
+extern void make_empty_dir_inode(struct inode *inode);
+extern bool is_empty_dir_inode(struct inode *inode);
struct tree_descr { char *name; const struct file_operations *ops; int mode; };
struct dentry *d_alloc_name(struct dentry *, const char *);
extern int simple_fill_super(struct super_block *, unsigned long, struct tree_descr *);
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index e6b2f7db9c0c..123be25ea15a 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -45,6 +45,7 @@ enum kernfs_node_flag {
KERNFS_LOCKDEP = 0x0100,
KERNFS_SUICIDAL = 0x0400,
KERNFS_SUICIDED = 0x0800,
+ KERNFS_EMPTY_DIR = 0x1000,
};
/* @flags for kernfs_create_root() */
@@ -286,6 +287,8 @@ void kernfs_destroy_root(struct kernfs_root *root);
struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
const char *name, umode_t mode,
void *priv, const void *ns);
+struct kernfs_node *kernfs_create_empty_dir(struct kernfs_node *parent,
+ const char *name);
struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent,
const char *name,
umode_t mode, loff_t size,
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 795d5fea5697..fa7bc29925c9 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -188,6 +188,9 @@ struct ctl_table_header *register_sysctl_paths(const struct ctl_path *path,
void unregister_sysctl_table(struct ctl_table_header * table);
extern int sysctl_init(void);
+
+extern struct ctl_table sysctl_mount_point[];
+
#else /* CONFIG_SYSCTL */
static inline struct ctl_table_header *register_sysctl_table(struct ctl_table * table)
{
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index 99382c0df17e..9f65758311a4 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -210,6 +210,10 @@ int __must_check sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name,
int __must_check sysfs_move_dir_ns(struct kobject *kobj,
struct kobject *new_parent_kobj,
const void *new_ns);
+int __must_check sysfs_create_mount_point(struct kobject *parent_kobj,
+ const char *name);
+void sysfs_remove_mount_point(struct kobject *parent_kobj,
+ const char *name);
int __must_check sysfs_create_file_ns(struct kobject *kobj,
const struct attribute *attr,
@@ -298,6 +302,17 @@ static inline int sysfs_move_dir_ns(struct kobject *kobj,
return 0;
}
+static inline int sysfs_create_mount_point(struct kobject *parent_kobj,
+ const char *name)
+{
+ return 0;
+}
+
+static inline void sysfs_remove_mount_point(struct kobject *parent_kobj,
+ const char *name)
+{
+}
+
static inline int sysfs_create_file_ns(struct kobject *kobj,
const struct attribute *attr,
const void *ns)
diff --git a/kernel/cgroup.c b/kernel/cgroup.c
index 9ef9fc8a774b..f89d9292eee6 100644
--- a/kernel/cgroup.c
+++ b/kernel/cgroup.c
@@ -1939,8 +1939,6 @@ static struct file_system_type cgroup_fs_type = {
.kill_sb = cgroup_kill_sb,
};
-static struct kobject *cgroup_kobj;
-
/**
* task_cgroup_path - cgroup path of a task in the first cgroup hierarchy
* @task: target task
@@ -5070,13 +5068,13 @@ int __init cgroup_init(void)
ss->bind(init_css_set.subsys[ssid]);
}
- cgroup_kobj = kobject_create_and_add("cgroup", fs_kobj);
- if (!cgroup_kobj)
- return -ENOMEM;
+ err = sysfs_create_mount_point(fs_kobj, "cgroup");
+ if (err)
+ return err;
err = register_filesystem(&cgroup_fs_type);
if (err < 0) {
- kobject_put(cgroup_kobj);
+ sysfs_remove_mount_point(fs_kobj, "cgroup");
return err;
}
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 812fcc3fd390..19b62b522158 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1538,12 +1538,6 @@ static struct ctl_table vm_table[] = {
{ }
};
-#if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE)
-static struct ctl_table binfmt_misc_table[] = {
- { }
-};
-#endif
-
static struct ctl_table fs_table[] = {
{
.procname = "inode-nr",
@@ -1697,7 +1691,7 @@ static struct ctl_table fs_table[] = {
{
.procname = "binfmt_misc",
.mode = 0555,
- .child = binfmt_misc_table,
+ .child = sysctl_mount_point,
},
#endif
{
diff --git a/security/inode.c b/security/inode.c
index 91503b79c5f8..0e37e4fba8fa 100644
--- a/security/inode.c
+++ b/security/inode.c
@@ -215,19 +215,17 @@ void securityfs_remove(struct dentry *dentry)
}
EXPORT_SYMBOL_GPL(securityfs_remove);
-static struct kobject *security_kobj;
-
static int __init securityfs_init(void)
{
int retval;
- security_kobj = kobject_create_and_add("security", kernel_kobj);
- if (!security_kobj)
- return -EINVAL;
+ retval = sysfs_create_mount_point(kernel_kobj, "security");
+ if (retval)
+ return retval;
retval = register_filesystem(&fs_type);
if (retval)
- kobject_put(security_kobj);
+ sysfs_remove_mount_point(kernel_kobj, "security");
return retval;
}
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index d2787cca1fcb..3d2201413028 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -1853,7 +1853,6 @@ static struct file_system_type sel_fs_type = {
};
struct vfsmount *selinuxfs_mount;
-static struct kobject *selinuxfs_kobj;
static int __init init_sel_fs(void)
{
@@ -1862,13 +1861,13 @@ static int __init init_sel_fs(void)
if (!selinux_enabled)
return 0;
- selinuxfs_kobj = kobject_create_and_add("selinux", fs_kobj);
- if (!selinuxfs_kobj)
- return -ENOMEM;
+ err = sysfs_create_mount_point(fs_kobj, "selinux");
+ if (err)
+ return err;
err = register_filesystem(&sel_fs_type);
if (err) {
- kobject_put(selinuxfs_kobj);
+ sysfs_remove_mount_point(fs_kobj, "selinux");
return err;
}
@@ -1887,7 +1886,7 @@ __initcall(init_sel_fs);
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
void exit_sel_fs(void)
{
- kobject_put(selinuxfs_kobj);
+ sysfs_remove_mount_point(fs_kobj, "selinux");
kern_unmount(selinuxfs_mount);
unregister_filesystem(&sel_fs_type);
}
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index 5e0a64ebdf23..2716d02119f3 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -2314,16 +2314,16 @@ static const struct file_operations smk_revoke_subj_ops = {
.llseek = generic_file_llseek,
};
-static struct kset *smackfs_kset;
/**
* smk_init_sysfs - initialize /sys/fs/smackfs
*
*/
static int smk_init_sysfs(void)
{
- smackfs_kset = kset_create_and_add("smackfs", NULL, fs_kobj);
- if (!smackfs_kset)
- return -ENOMEM;
+ int err;
+ err = sysfs_create_mount_point(fs_kobj, "smackfs");
+ if (err)
+ return err;
return 0;
}