Messages in this thread Patch in this message | | | Date | Tue, 31 Mar 2009 02:54:44 +0400 | From | Alexey Dobriyan <> | Subject | [PATCH] proc 3/6: make /proc/net it's own filesystem (CVE-____-____) |
| |
From 1b292cb4a9e028dc33f77c68bc21bff84a716abe Mon Sep 17 00:00:00 2001 From: Eric W. Biederman <ebiederm@xmission.com> Date: Tue, 31 Mar 2009 00:57:01 +0400 Subject: [PATCH 08/11] proc 3/6: make /proc/net it's own filesystem (CVE-____-____)
Make the VFS happy with /proc/net by making it it's own filesystem avoiding issues with hard links to directories and other silliness that confuse the vfs today.
We preserve backwards compatibility by automatically mounting /proc/self/net and marking it as a shrinkable mount so userspace doesn't need to care about it.
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> [This patch depends on 459c19f524a9d89c65717a7d061d5f11ecf6bcb8 "SELinux: correctly detect proc filesystems of the form "proc/foo"" 12204e24b1330428c3062faee10a0d80b8a5cb61 "security: pass mount flags to security_sb_kern_mount()" 74192246910ff4fb95309ba1a683215644beeb62 "SELinux: don't check permissions for kernel mounts" --adobriyan ] Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com> --- fs/proc/base.c | 4 +- fs/proc/internal.h | 1 - fs/proc/proc_net.c | 198 ++++++++++++++++++++++++++++++------------- include/linux/magic.h | 1 + include/net/net_namespace.h | 1 + security/selinux/hooks.c | 29 +++++-- 6 files changed, 168 insertions(+), 66 deletions(-)
diff --git a/fs/proc/base.c b/fs/proc/base.c index e0afd32..71c9caa 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -126,6 +126,8 @@ struct pid_entry { NOD(NAME, (S_IFREG|(MODE)), \ NULL, &proc_single_file_operations, \ { .proc_show = show } ) +#define MNT(NAME, MODE, iops) \ + NOD(NAME, (S_IFDIR|(MODE)), &iops, NULL, {}) /* * Count the number of hardlinks for the pid_entry table, excluding the . @@ -2470,7 +2472,7 @@ static const struct pid_entry tgid_base_stuff[] = { DIR("fd", S_IRUSR|S_IXUSR, proc_fd_inode_operations, proc_fd_operations), DIR("fdinfo", S_IRUSR|S_IXUSR, proc_fdinfo_inode_operations, proc_fdinfo_operations), #ifdef CONFIG_NET - DIR("net", S_IRUGO|S_IXUGO, proc_net_inode_operations, proc_net_operations), + MNT("net", S_IRUGO|S_IXUGO, proc_net_inode_operations), #endif REG("environ", S_IRUSR, proc_environ_operations), INF("auxv", S_IRUSR, proc_pid_auxv), diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 7e77759..ef482f5 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -58,7 +58,6 @@ extern const struct file_operations proc_numa_maps_operations; extern const struct file_operations proc_smaps_operations; extern const struct file_operations proc_clear_refs_operations; extern const struct file_operations proc_pagemap_operations; -extern const struct file_operations proc_net_operations; extern const struct inode_operations proc_net_inode_operations; void free_proc_entry(struct proc_dir_entry *de); diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c index 04d1270..54fc2c8 100644 --- a/fs/proc/proc_net.c +++ b/fs/proc/proc_net.c @@ -20,11 +20,13 @@ #include <linux/bitops.h> #include <linux/mount.h> #include <linux/nsproxy.h> +#include <linux/namei.h> #include <net/net_namespace.h> #include <linux/seq_file.h> #include "internal.h" +static struct file_system_type proc_net_fs_type; static struct net *get_proc_net(const struct inode *inode) { @@ -117,63 +119,53 @@ static struct net *get_proc_task_net(struct inode *dir) return net; } -static struct dentry *proc_tgid_net_lookup(struct inode *dir, - struct dentry *dentry, struct nameidata *nd) +static void *proc_net_follow_link(struct dentry *dentry, struct nameidata *nd) { - struct dentry *de; + /* Follow to a mount point of the proper network namespace. */ + struct vfsmount *mnt; struct net *net; - - de = ERR_PTR(-ENOENT); - net = get_proc_task_net(dir); - if (net != NULL) { - de = proc_lookup_de(net->proc_net, dir, dentry); - put_net(net); - } - return de; -} - -static int proc_tgid_net_getattr(struct vfsmount *mnt, struct dentry *dentry, - struct kstat *stat) -{ - struct inode *inode = dentry->d_inode; - struct net *net; - - net = get_proc_task_net(inode); - - generic_fillattr(inode, stat); - - if (net != NULL) { - stat->nlink = net->proc_net->nlink; - put_net(net); + int err = -ENOENT; + + net = get_proc_task_net(dentry->d_inode); + if (!net) + goto out_err; + + mnt = kern_mount_data(&proc_net_fs_type, net); + if (IS_ERR(mnt)) + goto out_err; + + dput(nd->path.dentry); + nd->path.dentry = dget(dentry); + + err = do_add_mount(mntget(mnt), &nd->path, MNT_SHRINKABLE, + &proc_automounts); + if (err < 0) { + mntput(mnt); + if (err == -EBUSY) + goto out_follow; + goto out_err; } - - return 0; + err = 0; + path_put(&nd->path); + nd->path.mnt = mnt; + nd->path.dentry = dget(mnt->mnt_root); + put_net(net); +out: + return ERR_PTR(err); +out_err: + path_put(&nd->path); + goto out; +out_follow: + /* We raced with ourselves so just walk the mounts */ + while (d_mountpoint(nd->path.dentry) && + follow_down(&nd->path.mnt, &nd->path.dentry)) + ; + err = 0; + goto out; } const struct inode_operations proc_net_inode_operations = { - .lookup = proc_tgid_net_lookup, - .getattr = proc_tgid_net_getattr, -}; - -static int proc_tgid_net_readdir(struct file *filp, void *dirent, - filldir_t filldir) -{ - int ret; - struct net *net; - - ret = -EINVAL; - net = get_proc_task_net(filp->f_path.dentry->d_inode); - if (net != NULL) { - ret = proc_readdir_de(net->proc_net, filp, dirent, filldir); - put_net(net); - } - return ret; -} - -const struct file_operations proc_net_operations = { - .llseek = generic_file_llseek, - .read = generic_read_dir, - .readdir = proc_tgid_net_readdir, + .follow_link = proc_net_follow_link, }; @@ -190,21 +182,95 @@ void proc_net_remove(struct net *net, const char *name) } EXPORT_SYMBOL_GPL(proc_net_remove); +static int proc_net_fill_super(struct super_block *sb) +{ + struct net *net = sb->s_fs_info; + struct proc_dir_entry *netd = net->proc_net; + struct inode *root_inode = NULL; + + sb->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC; + sb->s_blocksize = PAGE_SIZE; + sb->s_blocksize_bits = PAGE_SHIFT; + sb->s_magic = PROC_NET_SUPER_MAGIC; + sb->s_op = &proc_sops; + sb->s_time_gran = 1; + + de_get(netd); + root_inode = proc_get_inode(sb, netd->low_ino, netd); + if (!root_inode) + goto out_no_root; + root_inode->i_uid = 0; + root_inode->i_gid = 0; + sb->s_root = d_alloc_root(root_inode); + if (!sb->s_root) + goto out_no_root; + return 0; + +out_no_root: + printk("%s: get root inode failed\n", __func__); + iput(root_inode); + de_put(netd); + return -ENOMEM; +} + +static int proc_net_test_super(struct super_block *sb, void *data) +{ + return sb->s_fs_info == data; +} + +static int proc_net_set_super(struct super_block *sb, void *data) +{ + sb->s_fs_info = data; + return set_anon_super(sb, NULL); +} + +static int proc_net_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data, struct vfsmount *mnt) +{ + struct super_block *sb; + + if (!(flags & MS_KERNMOUNT)) + data = current->nsproxy->net_ns; + + sb = sget(fs_type, proc_net_test_super, proc_net_set_super, data); + if (IS_ERR(sb)) + return PTR_ERR(sb); + + if (!sb->s_root) { + int err; + sb->s_flags = flags; + err = proc_net_fill_super(sb); + if (err) { + up_write(&sb->s_umount); + deactivate_super(sb); + return err; + } + + sb->s_flags |= MS_ACTIVE; + } + + simple_set_mnt(mnt, sb); + return 0; +} + +static struct file_system_type proc_net_fs_type = { + .name = "proc/net", + .get_sb = proc_net_get_sb, + .kill_sb = kill_litter_super, +}; + static __net_init int proc_net_ns_init(struct net *net) { struct proc_dir_entry *netd, *net_statd; + struct vfsmount *mnt; int err; err = -ENOMEM; - netd = kzalloc(sizeof(*netd), GFP_KERNEL); + netd = proc_create_root(); if (!netd) goto out; netd->data = net; - netd->nlink = 2; - netd->name = "net"; - netd->namelen = 3; - netd->parent = &proc_root; err = -EEXIST; net_statd = proc_net_mkdir(net, "stat", netd); @@ -213,8 +279,17 @@ static __net_init int proc_net_ns_init(struct net *net) net->proc_net = netd; net->proc_net_stat = net_statd; + + mnt = kern_mount_data(&proc_net_fs_type, net); + if (IS_ERR(mnt)) + goto free_stat; + + net->proc_mnt = mnt; + return 0; +free_stat: + remove_proc_entry("stat", netd); free_net: kfree(netd); out: @@ -224,7 +299,14 @@ out: static __net_exit void proc_net_ns_exit(struct net *net) { remove_proc_entry("stat", net->proc_net); - kfree(net->proc_net); + release_proc_entry(net->proc_net); + /* + * We won't be looking up this super block any more so set s_fs_info to + * NULL to ensure it doesn't conflict with network namespaces allocated + * in the future at the same address. + */ + net->proc_mnt->mnt_sb->s_fs_info = NULL; + mntput(net->proc_mnt); } static struct pernet_operations __net_initdata proc_net_ns_ops = { @@ -235,6 +317,6 @@ static struct pernet_operations __net_initdata proc_net_ns_ops = { int __init proc_net_init(void) { proc_symlink("net", NULL, "self/net"); - + register_filesystem(&proc_net_fs_type); return register_pernet_subsys(&proc_net_ns_ops); } diff --git a/include/linux/magic.h b/include/linux/magic.h index 5b4e28b..0cde7c7 100644 --- a/include/linux/magic.h +++ b/include/linux/magic.h @@ -33,6 +33,7 @@ #define NFS_SUPER_MAGIC 0x6969 #define OPENPROM_SUPER_MAGIC 0x9fa1 #define PROC_SUPER_MAGIC 0x9fa0 +#define PROC_NET_SUPER_MAGIC 0x706e6574 #define QNX4_SUPER_MAGIC 0x002f /* qnx4 fs detection */ #define REISERFS_SUPER_MAGIC 0x52654973 /* used by gcc */ diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index ded434b..281af6e 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -41,6 +41,7 @@ struct net { struct proc_dir_entry *proc_net; struct proc_dir_entry *proc_net_stat; + struct vfsmount *proc_mnt; #ifdef CONFIG_SYSCTL struct ctl_table_set sysctls; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index ba808ef..3828002 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -713,7 +713,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, goto out; } - if (strcmp(sb->s_type->name, "proc") == 0) + /* "proc", "proc/net" */ + if (strncmp(sb->s_type->name, "proc", 4) == 0) sbsec->flags |= SE_SBPROC; /* Determine the labeling behavior to use for this filesystem type. */ @@ -1174,16 +1175,18 @@ static inline u16 socket_type_to_security_class(int family, int type, int protoc } #ifdef CONFIG_PROC_FS -static int selinux_proc_get_sid(struct proc_dir_entry *de, +static int selinux_proc_get_sid(struct super_block *sb, + struct proc_dir_entry *de, u16 tclass, u32 *sid) { int buflen, rc; char *buffer, *path, *end; + rc = -ENOMEM; buffer = (char *)__get_free_page(GFP_KERNEL); if (!buffer) - return -ENOMEM; + goto out; buflen = PAGE_SIZE; end = buffer+buflen; @@ -1194,19 +1197,32 @@ static int selinux_proc_get_sid(struct proc_dir_entry *de, while (de && de != de->parent) { buflen -= de->namelen + 1; if (buflen < 0) - break; + goto out_free; end -= de->namelen; memcpy(end, de->name, de->namelen); *--end = '/'; path = end; de = de->parent; } + if (strcmp(sb->s_type->name, "proc") != 0) { + const char *name = sb->s_type->name + 4; + int namelen = strlen(name); + buflen -= namelen; + if (buflen < 0) + goto out_free; + end -= namelen; + memcpy(end, name, namelen); + path = end; + } rc = security_genfs_sid("proc", path, tclass, sid); +out_free: free_page((unsigned long)buffer); +out: return rc; } #else -static int selinux_proc_get_sid(struct proc_dir_entry *de, +static int selinux_proc_get_sid(struct super_block *sb, + struct proc_dir_entry *de, u16 tclass, u32 *sid) { @@ -1372,7 +1388,8 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent struct proc_inode *proci = PROC_I(inode); if (proci->pde) { isec->sclass = inode_mode_to_security_class(inode->i_mode); - rc = selinux_proc_get_sid(proci->pde, + rc = selinux_proc_get_sid(inode->i_sb, + proci->pde, isec->sclass, &sid); if (rc) -- 1.5.6.5
| |