lists.openwall.net | lists / announce owl-users owl-dev john-users john-dev passwdqc-users yescrypt popa3d-users / oss-security kernel-hardening musl sabotage tlsify passwords / crypt-dev xvendor / Bugtraq Full-Disclosure linux-kernel linux-netdev linux-ext4 linux-hardening linux-cve-announce PHC | |
Open Source and information security mailing list archives
| ||
|
Date: Fri, 15 Apr 2016 10:35:20 -0500 From: "Eric W. Biederman" <ebiederm@...ssion.com> To: Linus Torvalds <torvalds@...ux-foundation.org> Cc: "H. Peter Anvin" <hpa@...or.com>, Andy Lutomirski <luto@...capital.net>, security@...ian.org, security@...nel.org, Al Viro <viro@...iv.linux.org.uk>, security@...ntu.com, Peter Hurley <peter@...leysoftware.com>, Serge Hallyn <serge.hallyn@...ntu.com>, Willy Tarreau <w@....eu>, Aurelien Jarno <aurelien@...el32.net>, One Thousand Gnomes <gnomes@...rguk.ukuu.org.uk>, Jann Horn <jann@...jh.net>, Greg KH <greg@...ah.com>, Linux Kernel Mailing List <linux-kernel@...r.kernel.org>, Jiri Slaby <jslaby@...e.com>, Florian Weimer <fw@...eb.enyo.de>, "Eric W. Biederman" <ebiederm@...ssion.com> Subject: [PATCH 04/16] devpts: Teach /dev/ptmx to automount the appropriate devpts via path lookup This is in preparation for forcing each mount of devpts to be a distinct filesystem. The goal of this change is to cleanly allow each mount of devpts to be a distince filesystem while not introducing regressions in userspace. On each open of /dev/ptmx look at the relative path ../pts and see if devpts is mounted there. If a devpts filesystem is found via the path lookup mount it's ptmx node on /dev/ptmx. If no devpts filesystem is found via the path lookup mount the system devpts ptmx node on /dev/ptmx. This retains backwards compatibility for weird setups. This winds up using 3 new vfs helpers path_parent, path_pts, and vfs_loopback_mount. Additionally init_special_inode and follow_automount are updated with calls to is_dev_ptmx to add a tiny bit of extra code in those functions to allow /dev/ptmx to hook into the automount path. I endeavored to keep the vfs changes clean, but I did not strive for generality. Signed-off-by: "Eric W. Biederman" <ebiederm@...ssion.com> --- fs/devpts/inode.c | 55 +++++++++++++++++++++++++++++++ fs/inode.c | 3 ++ fs/namei.c | 83 +++++++++++++++++++++++++++++++++++++++-------- include/linux/devpts_fs.h | 13 ++++++++ include/linux/namei.h | 2 ++ 5 files changed, 143 insertions(+), 13 deletions(-) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 4fc6c49b0efd..0b84063a1e14 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -17,6 +17,7 @@ #include <linux/fs.h> #include <linux/sched.h> #include <linux/namei.h> +#include <linux/fs_struct.h> #include <linux/slab.h> #include <linux/mount.h> #include <linux/tty.h> @@ -695,6 +696,60 @@ void devpts_pty_kill(struct inode *inode) inode_unlock(d_inode(root)); } +static void ptmx_expire_automounts(struct work_struct *work); +static LIST_HEAD(ptmx_automounts); +static DECLARE_DELAYED_WORK(ptmx_automount_work, ptmx_expire_automounts); +static unsigned long ptmx_automount_timeout = 10 * 60 * HZ; + +static void ptmx_expire_automounts(struct work_struct *work) +{ + struct list_head *list = &ptmx_automounts; + + mark_mounts_for_expiry(list); + if (!list_empty(list)) + schedule_delayed_work(&ptmx_automount_work, + ptmx_automount_timeout); +} + +struct vfsmount *ptmx_automount(struct path *input_path) +{ + struct vfsmount *newmnt; + struct path path; + struct dentry *old; + + /* Can the pts filesystem be found with a path walk? */ + path = *input_path; + path_get(&path); + + if ((path_pts(&path) != 0) || + /* Is the path the root of a devpts filesystem? */ + (path.mnt->mnt_sb->s_magic != DEVPTS_SUPER_MAGIC) || + (path.mnt->mnt_root != path.mnt->mnt_sb->s_root)) { + /* No devpts filesystem found use the system devpts */ + path_put(&path); + path.mnt = devpts_mnt; + path.dentry = DEVPTS_SB(devpts_mnt->mnt_sb)->ptmx_dentry; + path_get(&path); + } + else { + /* Advance path to the ptmx dentry */ + old = path.dentry; + path.dentry = dget(DEVPTS_SB(path.mnt->mnt_sb)->ptmx_dentry); + dput(old); + } + + newmnt = vfs_loopback_mount(&path); + if (IS_ERR(newmnt)) + goto fail; + + mntget(newmnt); + mnt_set_expiry(newmnt, &ptmx_automounts); + schedule_delayed_work(&ptmx_automount_work, ptmx_automount_timeout); +fail: + path_put(&path); + return newmnt; +} + static int __init init_devpts_fs(void) { int err = register_filesystem(&devpts_fs_type); diff --git a/fs/inode.c b/fs/inode.c index 69b8b526c194..251330ba336e 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -18,6 +18,7 @@ #include <linux/buffer_head.h> /* for inode_has_buffers */ #include <linux/ratelimit.h> #include <linux/list_lru.h> +#include <linux/devpts_fs.h> #include <trace/events/writeback.h> #include "internal.h" @@ -1917,6 +1918,8 @@ void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev) if (S_ISCHR(mode)) { inode->i_fop = &def_chr_fops; inode->i_rdev = rdev; + if (is_dev_ptmx(inode)) + inode->i_flags |= S_AUTOMOUNT; } else if (S_ISBLK(mode)) { inode->i_fop = &def_blk_fops; inode->i_rdev = rdev; diff --git a/fs/namei.c b/fs/namei.c index 794f81dce766..a4bdbeec8067 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -35,6 +35,7 @@ #include <linux/fs_struct.h> #include <linux/posix_acl.h> #include <linux/hash.h> +#include <linux/devpts_fs.h> #include <asm/uaccess.h> #include "internal.h" @@ -1087,10 +1088,15 @@ EXPORT_SYMBOL(follow_up); static int follow_automount(struct path *path, struct nameidata *nd, bool *need_mntput) { + struct vfsmount *(*automount)(struct path *) = NULL; struct vfsmount *mnt; int err; - if (!path->dentry->d_op || !path->dentry->d_op->d_automount) + if (path->dentry->d_op) + automount = path->dentry->d_op->d_automount; + if (path->dentry->d_inode && is_dev_ptmx(path->dentry->d_inode)) + automount = ptmx_automount; + if (!automount) return -EREMOTE; /* We don't want to mount if someone's just doing a stat - @@ -1113,7 +1119,7 @@ static int follow_automount(struct path *path, struct nameidata *nd, if (nd->total_link_count >= 40) return -ELOOP; - mnt = path->dentry->d_op->d_automount(path); + mnt = automount(path); if (IS_ERR(mnt)) { /* * The filesystem is allowed to return -EISDIR here to indicate @@ -1415,29 +1421,41 @@ static void follow_mount(struct path *path) } } -static int follow_dotdot(struct nameidata *nd) +static int path_parent(struct path *root, struct path *path) { + int ret = 0; + while(1) { - struct dentry *old = nd->path.dentry; + struct dentry *old = path->dentry; - if (nd->path.dentry == nd->root.dentry && - nd->path.mnt == nd->root.mnt) { + if (old == root->dentry && + path->mnt == root->mnt) { break; } - if (nd->path.dentry != nd->path.mnt->mnt_root) { + if (old != path->mnt->mnt_root) { /* rare case of legitimate dget_parent()... */ - nd->path.dentry = dget_parent(nd->path.dentry); + path->dentry = dget_parent(path->dentry); dput(old); - if (unlikely(!path_connected(&nd->path))) + if (unlikely(!path_connected(path))) return -ENOENT; + ret = 1; break; } - if (!follow_up(&nd->path)) + if (!follow_up(path)) break; } - follow_mount(&nd->path); - nd->inode = nd->path.dentry->d_inode; - return 0; + follow_mount(path); + return ret; +} + +static int follow_dotdot(struct nameidata *nd) +{ + int ret = path_parent(&nd->root, &nd->path); + if (ret >= 0) { + ret = 0; + nd->inode = nd->path.dentry->d_inode; + } + return ret; } /* @@ -2374,6 +2392,45 @@ struct dentry *lookup_one_len_unlocked(const char *name, } EXPORT_SYMBOL(lookup_one_len_unlocked); +#ifdef CONFIG_UNIX98_PTYS +int path_pts(struct path *path) +{ + /* A pathwalk of "../pts" with no permission checks. */ + struct dentry *child, *parent = path->dentry; + struct qstr this; + struct path root; + int ret; + + get_fs_root(current->fs, &root); + ret = path_parent(&root, path); + path_put(&root); + if (ret != 1) + return -ENOENT; + + if (!d_can_lookup(parent)) + return -ENOENT; + + this.name = "pts"; + this.len = 3; + this.hash = full_name_hash(this.name, this.len); + if (parent->d_flags & DCACHE_OP_HASH) { + int err = parent->d_op->d_hash(parent, &this); + if (err < 0) + return err; + } + inode_lock(parent->d_inode); + child = d_lookup(parent, &this); + inode_unlock(parent->d_inode); + if (!child) + return -ENOENT; + + path->dentry = child; + dput(parent); + follow_mount(path); + return 0; +} +#endif + int user_path_at_empty(int dfd, const char __user *name, unsigned flags, struct path *path, int *empty) { diff --git a/include/linux/devpts_fs.h b/include/linux/devpts_fs.h index ff2b7c274435..5b2f6d6cd386 100644 --- a/include/linux/devpts_fs.h +++ b/include/linux/devpts_fs.h @@ -19,9 +19,12 @@ #define PTMX_MINOR 2 #ifdef CONFIG_UNIX98_PTYS +#include <linux/major.h> extern struct file_operations ptmx_fops; +struct vfsmount *ptmx_automount(struct path *path); + int devpts_new_index(struct inode *ptmx_inode); void devpts_kill_index(struct inode *ptmx_inode, int idx); void devpts_add_ref(struct inode *ptmx_inode); @@ -34,6 +37,10 @@ void *devpts_get_priv(struct inode *pts_inode); /* unlink */ void devpts_pty_kill(struct inode *inode); +static inline bool is_dev_ptmx(struct inode *inode) +{ + return inode->i_rdev == MKDEV(TTYAUX_MAJOR, PTMX_MINOR); +} #else /* Dummy stubs in the no-pty case */ @@ -52,6 +59,12 @@ static inline void *devpts_get_priv(struct inode *pts_inode) } static inline void devpts_pty_kill(struct inode *inode) { } +#define ptmx_automount NULL + +static inline bool is_dev_ptmx(struct inode *inode) +{ + return false; +} #endif diff --git a/include/linux/namei.h b/include/linux/namei.h index 77d01700daf7..f29abda31e6d 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -45,6 +45,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; #define LOOKUP_ROOT 0x2000 #define LOOKUP_EMPTY 0x4000 +extern int path_pts(struct path *path); + extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty); static inline int user_path_at(int dfd, const char __user *name, unsigned flags, -- 2.8.1
Powered by blists - more mailing lists