Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752073AbcDOPqq (ORCPT ); Fri, 15 Apr 2016 11:46:46 -0400 Received: from out02.mta.xmission.com ([166.70.13.232]:50155 "EHLO out02.mta.xmission.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750965AbcDOPqm (ORCPT ); Fri, 15 Apr 2016 11:46:42 -0400 From: "Eric W. Biederman" To: Linus Torvalds Cc: "H. Peter Anvin" , Andy Lutomirski , security@debian.org, security@kernel.org, Al Viro , security@ubuntu.com, Peter Hurley , Serge Hallyn , Willy Tarreau , Aurelien Jarno , One Thousand Gnomes , Jann Horn , Greg KH , Linux Kernel Mailing List , Jiri Slaby , Florian Weimer , "Eric W. Biederman" Date: Fri, 15 Apr 2016 10:35:20 -0500 Message-Id: <1460734532-20134-4-git-send-email-ebiederm@xmission.com> X-Mailer: git-send-email 2.8.1 In-Reply-To: <1460734532-20134-1-git-send-email-ebiederm@xmission.com> References: <877ffyzy1j.fsf_-_@x220.int.ebiederm.org> <1460734532-20134-1-git-send-email-ebiederm@xmission.com> X-XM-AID: U2FsdGVkX19RgWOiL4zjDZqUQOwdx4jSX6WaUYPv+xY= X-SA-Exim-Connect-IP: 67.3.249.252 X-SA-Exim-Mail-From: ebiederm@xmission.com X-Spam-Report: * -1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP * 0.0 TVD_RCVD_IP Message was received from an IP address * 1.5 XMNoVowels Alpha-numberic number with no vowels * 0.7 XMSubLong Long Subject * 0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60% * [score: 0.5000] * -0.0 DCC_CHECK_NEGATIVE Not listed in DCC * [sa06 1397; Body=1 Fuz1=1 Fuz2=1] X-Spam-DCC: XMission; sa06 1397; Body=1 Fuz1=1 Fuz2=1 X-Spam-Combo: **;Linus Torvalds X-Spam-Relay-Country: X-Spam-Timing: total 991 ms - load_scoreonly_sql: 0.03 (0.0%), signal_user_changed: 3.3 (0.3%), b_tie_ro: 2.3 (0.2%), parse: 1.00 (0.1%), extract_message_metadata: 19 (1.9%), get_uri_detail_list: 6 (0.6%), tests_pri_-1000: 7 (0.8%), tests_pri_-950: 1.20 (0.1%), tests_pri_-900: 1.05 (0.1%), tests_pri_-400: 44 (4.4%), check_bayes: 42 (4.3%), b_tokenize: 17 (1.7%), b_tok_get_all: 14 (1.4%), b_comp_prob: 2.9 (0.3%), b_tok_touch_all: 6 (0.6%), b_finish: 0.69 (0.1%), tests_pri_0: 908 (91.6%), check_dkim_signature: 1.04 (0.1%), check_dkim_adsp: 24 (2.4%), tests_pri_500: 3.7 (0.4%), rewrite_mail: 0.00 (0.0%) Subject: [PATCH 04/16] devpts: Teach /dev/ptmx to automount the appropriate devpts via path lookup X-Spam-Flag: No X-SA-Exim-Version: 4.2.1 (built Wed, 24 Sep 2014 11:00:52 -0600) X-SA-Exim-Scanned: Yes (on in01.mta.xmission.com) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 9140 Lines: 322 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" --- 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 #include #include +#include #include #include #include @@ -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 /* for inode_has_buffers */ #include #include +#include #include #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 #include #include +#include #include #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 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