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
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
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