Home
Reading
Searching
Subscribe
Sponsors
Statistics
Posting
Contact
Spam
Lists
Links
About
Hosting
Filtering
Features Download
Marketing
Archives
FAQ
Blog
 
Gmane
From: Al Viro <viro <at> ZenIV.linux.org.uk>
Subject: [RFC] __d_path() API change (was Re: [PATCH] Remove use of mnt_ns->root and fix a couple of bugs in d_namespace_path)
Newsgroups: gmane.linux.kernel
Date: Monday 5th December 2011 03:27:11 UTC (over 4 years ago)
I think I have a saner solution; the root of the problem is that
__d_path() API is asking for trouble.  What I'm proposing is:

	* Give prepend_path() an extra argument for reporting which
fatherless thing has it reached if it has missed the root - struct
path *bastard.  struct path *root becomes const on that *and* what
we put into *bastard is properly grabbed.  We do that only if that
pointer is not NULL, of course.  In any case we return 1 if we go
out through global_root:

	* modify the callers accordingly; everything except __d_path()
will simply pass NULL as that extra argument and instead of checking
if root has been changed, just check if return value was positive.

	* __d_path() gets the similar extra argument itself; if it's non-NULL
and we miss the root, the caller can expect to get (referenced) point where
the walk has stopped stored there.  _Maybe_ it ought to fill it with
NULL/NULL otherwise; I've just done that in the only such caller before
calling __d_path().  If it is NULL *and* root has not been NULL/NULL (i.e.
we asked for subtree explicitly and have nowhere to put the stopping
point),
we return NULL instead of pointer to relative pathname.

	* seq_path_root() does _NOT_ return -ENAMETOOLONG (it's stupid anyway -
the normal seq_file logics will take care of growing the buffer and redoing
the call of ->show() just fine).  However, if it gets path not reachable
from root, it returns SEQ_SKIP.  The only caller adjusted (i.e. stopped
ignoring the return value as it used to do).

	* unlike seq_path_root(), the caller in tomoyo passes NULL/NULL in
*root (it wants absolute path).  It still calls with bastard == NULL, so
no references are grabbed.

	* apparmor d_namespace_path() calls __d_path() passing it a pointer
to local struct path to fill.  It's been earlier initialized to NULL/NULL
(see above in part about __d_path()), so the check for "has it been outside
of chroot jail" is simply bastard.mnt != NULL after the call.  Moreover,
in this case it's safe to access vfsmount and dentry; we can do the checks
just fine, they won't be freed under us.  One more thing - I've exported
uninlined variant of check_mnt() (called our_mnt(), for obvious reasons)
and used it here.

Completely untested patch follows:

Signed-off-by: Al Viro 
---
diff --git a/fs/dcache.c b/fs/dcache.c
index 2ac8e7e..d8a1783 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -2440,7 +2440,8 @@ static int prepend_name(char **buffer, int *buflen,
struct qstr *name)
 /**
  * prepend_path - Prepend path string to a buffer
  * @path: the dentry/vfsmount to report
- * @root: root vfsmnt/dentry (may be modified by this function)
+ * @root: root vfsmnt/dentry
+ * @bastard: the fatherless thing we'd walked up to if we missed root
  * @buffer: pointer to the end of the buffer
  * @buflen: pointer to buffer length
  *
@@ -2449,7 +2450,9 @@ static int prepend_name(char **buffer, int *buflen,
struct qstr *name)
  * If path is not reachable from the supplied root, then the value of
  * root is changed (without modifying refcounts).
  */
-static int prepend_path(const struct path *path, struct path *root,
+static int prepend_path(const struct path *path,
+			const struct path *root,
+			struct path *bastard,
 			char **buffer, int *buflen)
 {
 	struct dentry *dentry = path->dentry;
@@ -2483,10 +2486,10 @@ static int prepend_path(const struct path *path,
struct path *root,
 		dentry = parent;
 	}
 
-out:
 	if (!error && !slash)
 		error = prepend(buffer, buflen, "/", 1);
 
+out:
 	br_read_unlock(vfsmount_lock);
 	return error;
 
@@ -2500,8 +2503,14 @@ global_root:
 		WARN(1, "Root dentry has weird name <%.*s>\n",
 		     (int) dentry->d_name.len, dentry->d_name.name);
 	}
-	root->mnt = vfsmnt;
-	root->dentry = dentry;
+	if (bastard) {
+		bastard->mnt = mntget(vfsmnt);
+		bastard->dentry = dget(dentry);
+	}
+	if (!slash)
+		error = prepend(buffer, buflen, "/", 1);
+	if (!error)
+		error = 1;
 	goto out;
 }
 
@@ -2521,8 +2530,12 @@ global_root:
  *
  * If path is not reachable from the supplied root, then the value of
  * root is changed (without modifying refcounts).
+ *
+ * XXX: rewrite description
  */
-char *__d_path(const struct path *path, struct path *root,
+char *__d_path(const struct path *path,
+	       const struct path *root,
+	       struct path *bastard,
 	       char *buf, int buflen)
 {
 	char *res = buf + buflen;
@@ -2530,19 +2543,26 @@ char *__d_path(const struct path *path, struct path
*root,
 
 	prepend(&res, &buflen, "\0", 1);
 	write_seqlock(&rename_lock);
-	error = prepend_path(path, root, &res, &buflen);
+	error = prepend_path(path, root, bastard, &res, &buflen);
 	write_sequnlock(&rename_lock);
 
-	if (error)
+	if (error < 0)
 		return ERR_PTR(error);
+	/*
+	 * we'd been asked to stop at specific point, missed it and
+	 * had not been asked to tell where we'd ended up.
+	 */
+	if (error > 0 && !bastard && root->mnt)
+		return NULL;
 	return res;
 }
 
 /*
  * same as __d_path but appends "(deleted)" for unlinked files.
  */
-static int path_with_deleted(const struct path *path, struct path *root,
-				 char **buf, int *buflen)
+static int path_with_deleted(const struct path *path,
+			     const struct path *root,
+			     char **buf, int *buflen)
 {
 	prepend(buf, buflen, "\0", 1);
 	if (d_unlinked(path->dentry)) {
@@ -2551,7 +2571,7 @@ static int path_with_deleted(const struct path *path,
struct path *root,
 			return error;
 	}
 
-	return prepend_path(path, root, buf, buflen);
+	return prepend_path(path, root, NULL, buf, buflen);
 }
 
 static int prepend_unreachable(char **buffer, int *buflen)
@@ -2579,7 +2599,6 @@ char *d_path(const struct path *path, char *buf, int
buflen)
 {
 	char *res = buf + buflen;
 	struct path root;
-	struct path tmp;
 	int error;
 
 	/*
@@ -2594,9 +2613,8 @@ char *d_path(const struct path *path, char *buf, int
buflen)
 
 	get_fs_root(current->fs, &root);
 	write_seqlock(&rename_lock);
-	tmp = root;
-	error = path_with_deleted(path, &tmp, &res, &buflen);
-	if (error)
+	error = path_with_deleted(path, &root, &res, &buflen);
+	if (error < 0)
 		res = ERR_PTR(error);
 	write_sequnlock(&rename_lock);
 	path_put(&root);
@@ -2617,7 +2635,6 @@ char *d_path_with_unreachable(const struct path
*path, char *buf, int buflen)
 {
 	char *res = buf + buflen;
 	struct path root;
-	struct path tmp;
 	int error;
 
 	if (path->dentry->d_op && path->dentry->d_op->d_dname)
@@ -2625,9 +2642,8 @@ char *d_path_with_unreachable(const struct path
*path, char *buf, int buflen)
 
 	get_fs_root(current->fs, &root);
 	write_seqlock(&rename_lock);
-	tmp = root;
-	error = path_with_deleted(path, &tmp, &res, &buflen);
-	if (!error && !path_equal(&tmp, &root))
+	error = path_with_deleted(path, &root, &res, &buflen);
+	if (error > 0)
 		error = prepend_unreachable(&res, &buflen);
 	write_sequnlock(&rename_lock);
 	path_put(&root);
@@ -2758,19 +2774,18 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf,
unsigned long, size)
 	write_seqlock(&rename_lock);
 	if (!d_unlinked(pwd.dentry)) {
 		unsigned long len;
-		struct path tmp = root;
 		char *cwd = page + PAGE_SIZE;
 		int buflen = PAGE_SIZE;
 
 		prepend(&cwd, &buflen, "\0", 1);
-		error = prepend_path(&pwd, &tmp, &cwd, &buflen);
+		error = prepend_path(&pwd, &root, NULL, &cwd, &buflen);
 		write_sequnlock(&rename_lock);
 
-		if (error)
+		if (error < 0)
 			goto out;
 
 		/* Unreachable from current root */
-		if (!path_equal(&tmp, &root)) {
+		if (error > 0) {
 			error = prepend_unreachable(&cwd, &buflen);
 			if (error)
 				goto out;
diff --git a/fs/namespace.c b/fs/namespace.c
index ea1cf5a..58c64c3 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -1023,15 +1023,12 @@ static int show_mountinfo(struct seq_file *m, void
*v)
 	if (err)
 		goto out;
 	seq_putc(m, ' ');
-	seq_path_root(m, &mnt_path, &root, " \t\n\\");
-	if (root.mnt != p->root.mnt || root.dentry != p->root.dentry) {
-		/*
-		 * Mountpoint is outside root, discard that one.  Ugly,
-		 * but less so than trying to do that in iterator in a
-		 * race-free way (due to renames).
-		 */
-		return SEQ_SKIP;
-	}
+
+	/* mountpoints outside of chroot jail will give SEQ_SKIP on this */
+	err = seq_path_root(m, &mnt_path, &root, " \t\n\\");
+	if (err)
+		goto out;
+
 	seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw");
 	show_mnt_opts(m, mnt);
 
@@ -2761,3 +2758,9 @@ void kern_unmount(struct vfsmount *mnt)
 	}
 }
 EXPORT_SYMBOL(kern_unmount);
+
+bool our_mnt(struct vfsmount *mnt)
+{
+	return check_mnt(mnt);
+}
+EXPORT_SYMBOL(our_mnt);
diff --git a/fs/seq_file.c b/fs/seq_file.c
index 05d6b0e..688642d 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -449,8 +449,6 @@ EXPORT_SYMBOL(seq_path);
 
 /*
  * Same as seq_path, but relative to supplied root.
- *
- * root may be changed, see __d_path().
  */
 int seq_path_root(struct seq_file *m, struct path *path, struct path
*root,
 		  char *esc)
@@ -462,7 +460,9 @@ int seq_path_root(struct seq_file *m, struct path
*path, struct path *root,
 	if (size) {
 		char *p;
 
-		p = __d_path(path, root, buf, size);
+		p = __d_path(path, root, NULL, buf, size);
+		if (!p)
+			return SEQ_SKIP;
 		res = PTR_ERR(p);
 		if (!IS_ERR(p)) {
 			char *end = mangle_path(buf, p, esc);
@@ -474,7 +474,7 @@ int seq_path_root(struct seq_file *m, struct path
*path, struct path *root,
 	}
 	seq_commit(m, res);
 
-	return res < 0 ? res : 0;
+	return res < 0 && res != -ENAMETOOLONG ? res : 0;
 }
 
 /*
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 4df9261..2b6cf1e 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -339,7 +339,8 @@ extern int d_validate(struct dentry *, struct dentry
*);
  */
 extern char *dynamic_dname(struct dentry *, char *, int, const char *,
...);
 
-extern char *__d_path(const struct path *path, struct path *root, char *,
int);
+extern char *__d_path(const struct path *, const struct path *,
+			struct path *, char *, int);
 extern char *d_path(const struct path *, char *, int);
 extern char *d_path_with_unreachable(const struct path *, char *, int);
 extern char *dentry_path_raw(struct dentry *, char *, int);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index e313022..019dc55 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1942,6 +1942,7 @@ extern int fd_statfs(int, struct kstatfs *);
 extern int statfs_by_dentry(struct dentry *, 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 int current_umask(void);
 
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
index 36cc0cc..ed300cc 100644
--- a/security/apparmor/path.c
+++ b/security/apparmor/path.c
@@ -57,23 +57,16 @@ static int prepend(char **buffer, int buflen, const
char *str, int namelen)
 static int d_namespace_path(struct path *path, char *buf, int buflen,
 			    char **name, int flags)
 {
-	struct path root, tmp;
+	struct path root = {};
+	struct path bastard = {};
 	char *res;
-	int connected, error = 0;
+	int error = 0;
 
-	/* Get the root we want to resolve too, released below */
-	if (flags & PATH_CHROOT_REL) {
-		/* resolve paths relative to chroot */
+	/* resolve paths relative to chroot?*/
+	if (flags & PATH_CHROOT_REL)
 		get_fs_root(current->fs, &root);
-	} else {
-		/* resolve paths relative to namespace */
-		root.mnt = current->nsproxy->mnt_ns->root;
-		root.dentry = root.mnt->mnt_root;
-		path_get(&root);
-	}
 
-	tmp = root;
-	res = __d_path(path, &tmp, buf, buflen);
+	res = __d_path(path, &root, &bastard, buf, buflen);
 
 	*name = res;
 	/* handle error conditions - and still allow a partial path to
@@ -97,10 +90,7 @@ static int d_namespace_path(struct path *path, char
*buf, int buflen,
 			goto out;
 	}
 
-	/* Determine if the path is connected to the expected root */
-	connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
-
-	/* If the path is not connected,
+	/* If the path is not connected to the expected root,
 	 * check if it is a sysctl and handle specially else remove any
 	 * leading / that __d_path may have returned.
 	 * Unless
@@ -111,9 +101,9 @@ static int d_namespace_path(struct path *path, char
*buf, int buflen,
 	 *     of chroot) and specifically directed to connect paths to
 	 *     namespace root.
 	 */
-	if (!connected) {
+	if (bastard.mnt) {
 		/* is the disconnect path a sysctl? */
-		if (tmp.dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
+		if (bastard.dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
 		    strncmp(*name, "/sys/", 5) == 0) {
 			/* TODO: convert over to using a per namespace
 			 * control instead of hard coded /proc
@@ -121,8 +111,7 @@ static int d_namespace_path(struct path *path, char
*buf, int buflen,
 			error = prepend(name, *name - buf, "/proc", 5);
 		} else if (!(flags & PATH_CONNECT_PATH) &&
 			   !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
-			     (tmp.mnt == current->nsproxy->mnt_ns->root &&
-			      tmp.dentry == tmp.mnt->mnt_root))) {
+			     our_mnt(bastard.mnt))) {
 			/* disconnected path, don't return pathname starting
 			 * with '/'
 			 */
@@ -133,7 +122,10 @@ static int d_namespace_path(struct path *path, char
*buf, int buflen,
 	}
 
 out:
-	path_put(&root);
+	if (bastard.mnt)
+		path_put(&bastard);
+	if (flags & PATH_CHROOT_REL)
+		path_put(&root);
 
 	return error;
 }
diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c
index e1eb982..d5f469a 100644
--- a/security/tomoyo/realpath.c
+++ b/security/tomoyo/realpath.c
@@ -95,7 +95,7 @@ static char *tomoyo_get_absolute_path(struct path *path,
char * const buffer,
 	if (buflen >= 256) {
 		struct path ns_root = { };
 		/* go to whatever namespace root we are under */
-		pos = __d_path(path, &ns_root, buffer, buflen - 1);
+		pos = __d_path(path, &ns_root, NULL, buffer, buflen - 1);
 		if (!IS_ERR(pos) && *pos == '/' && pos[1]) {
 			struct inode *inode = path->dentry->d_inode;
 			if (inode && S_ISDIR(inode->i_mode)) {
--
To unsubscribe from this list: send the line "unsubscribe
linux-security-module" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
 
CD: 3ms