[PATCH 2/2] vfs: fix d_path() for unreachable paths

From: Miklos Szeredi
Date: Mon Sep 21 2009 - 08:51:58 EST


(The two fixes have been folded into this one)

----
From: Miklos Szeredi <mszeredi@xxxxxxx>

John Johansen pointed out, that getcwd(2) will give a garbled result
if a bind mount of a non-filesystem-root directory is detached:

> mkdir /mnt/foo
> mount --bind /etc /mnt/foo
> cd /mnt/foo/skel
> umount -l /mnt/foo
> /bin/pwd
etcskel

If it was the root of the filesystem which was detached, it will give
a saner looking result, but it still won't be a valid absolute path by
which the CWD can be reached (assuming the process's root is not also
on the detached mount).

A similar issue happens if the CWD is outside the process's root or in
a different namespace. These problems are relevant to symlinks under
/proc/<pid>/ and /proc/<pid>/fd/ as well.

This patch addresses all these issues, by prefixing such unreachable
paths with "(unreachable)". This isn't perfect since the returned
path may still be a valid _relative_ path, and applications may not
check the result of getcwd() for starting with a '/' before using it.

For this reason Andreas Gruenbacher thinks getcwd(2) should return
ENOENT in these cases, but that breaks /bin/pwd and bash in the above
cases.

Hugh Dickins reported that an old version of gnome-vfs-daemon crashes
because it finds an entry in /proc/mounts where the mountpoint is
unreachable. So revert /proc/mounts to the old behavior (or rather a
less crazy version of the old behavior).

Also revert the effect on /proc/${PID}/maps for memory maps set up
with shmem_file_setup() or hugetlb_file_setup(). These functions set
up unlinked files under a kernel-private vfsmount. Since this
vfsmount is unreachable from userspace, these maps will be reported
with the "(unreachable)" prefix, which is undesirable, because it
changes the kernel ABI and might break applications for no good
reason.

Reported-by: John Johansen <jjohansen@xxxxxxx>
CC: Matthew Wilcox <matthew@xxxxxx>
CC: Andreas Gruenbacher <agruen@xxxxxxx>
Signed-off-by: Miklos Szeredi <mszeredi@xxxxxxx>

---
fs/dcache.c | 20 ++++++++++++++++-
fs/hugetlbfs/inode.c | 17 +++++++++++++++
fs/namespace.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++-
mm/shmem.c | 17 +++++++++++++++
4 files changed, 108 insertions(+), 3 deletions(-)

Index: linux-2.6/fs/dcache.c
===================================================================
--- linux-2.6.orig/fs/dcache.c 2009-09-21 14:31:35.000000000 +0200
+++ linux-2.6/fs/dcache.c 2009-09-21 14:31:37.000000000 +0200
@@ -1883,6 +1883,12 @@ static int prepend_name(char **buffer, i
return prepend(buffer, buflen, name->name, name->len);
}

+static bool is_pseudo_root(struct dentry *dentry)
+{
+ return IS_ROOT(dentry) &&
+ (dentry->d_name.len != 1 || dentry->d_name.name[0] != '/');
+}
+
/**
* __d_path - return the path of a dentry
* @path: the dentry/vfsmount to report
@@ -1950,8 +1956,18 @@ out:

global_root:
retval += 1; /* hit the slash */
- if (prepend_name(&retval, &buflen, &dentry->d_name) != 0)
- goto Elong;
+
+ if (is_pseudo_root(dentry)) {
+ /* Pseudo filesystem with "foo:" prefix */
+ if (prepend_name(&retval, &buflen, &dentry->d_name) != 0)
+ goto Elong;
+ } else {
+ /*
+ * Unreachable (detached or outside root or outside namespace)
+ */
+ if (prepend(&retval, &buflen, "(unreachable)/", 14) != 0)
+ goto Elong;
+ }
root->mnt = vfsmnt;
root->dentry = dentry;
goto out;
Index: linux-2.6/fs/hugetlbfs/inode.c
===================================================================
--- linux-2.6.orig/fs/hugetlbfs/inode.c 2009-09-21 14:31:35.000000000 +0200
+++ linux-2.6/fs/hugetlbfs/inode.c 2009-09-21 14:31:37.000000000 +0200
@@ -931,6 +931,21 @@ static struct file_system_type hugetlbfs

static struct vfsmount *hugetlbfs_vfsmount;

+/*
+ * Do a special d_dname function so that these are not prefixed by
+ * "(unreachable)".
+ */
+static char *hugetlb_unlinked_d_dname(struct dentry *dentry, char *buf,
+ int buflen)
+{
+ return dynamic_dname(dentry, buf, buflen, "/%s (deleted)",
+ dentry->d_name.name);
+}
+
+static struct dentry_operations hugetlb_unlinked_dentry_operations = {
+ .d_dname = hugetlb_unlinked_d_dname,
+};
+
static int can_do_hugetlb_shm(void)
{
return capable(CAP_IPC_LOCK) || in_group_p(sysctl_hugetlb_shm_group);
@@ -968,6 +983,8 @@ struct file *hugetlb_file_setup(const ch
if (!dentry)
goto out_shm_unlock;

+ dentry->d_op = &hugetlb_unlinked_dentry_operations;
+
error = -ENOSPC;
inode = hugetlbfs_get_inode(root->d_sb, current_fsuid(),
current_fsgid(), S_IFREG | S_IRWXUGO, 0);
Index: linux-2.6/fs/namespace.c
===================================================================
--- linux-2.6.orig/fs/namespace.c 2009-09-21 14:31:35.000000000 +0200
+++ linux-2.6/fs/namespace.c 2009-09-21 14:31:37.000000000 +0200
@@ -789,6 +789,61 @@ static void show_type(struct seq_file *m
}
}

+/*
+ * Same as d_path() except it doesn't stick "(unreachable)" in front
+ * of unreachable paths.
+ */
+static char *d_path_compat(struct path *path, char *buf, int buflen)
+{
+ char *res;
+ struct path root;
+ struct path tmp;
+
+ read_lock(&current->fs->lock);
+ root = current->fs->root;
+ path_get(&root);
+ read_unlock(&current->fs->lock);
+ spin_lock(&dcache_lock);
+ tmp = root;
+ res = __d_path(path, &tmp, buf, buflen);
+ if (!IS_ERR(res) &&
+ (tmp.mnt != root.mnt || tmp.dentry != root.dentry)) {
+ /*
+ * Unreachable path found, redo with the global root
+ * so we get a normal looking path.
+ */
+ res = __d_path(path, &tmp, buf, buflen);
+ }
+ spin_unlock(&dcache_lock);
+ path_put(&root);
+
+ return res;
+}
+
+/*
+ * Some old programs break if /proc/mounts contains a mountpoint
+ * beginning with "(unreachable)". Revert this back to the old way of
+ * displaying the path from the global root instead.
+ */
+static int show_path_old(struct seq_file *m, struct path *path, char *esc)
+{
+ char *buf;
+ size_t size = seq_get_buf(m, &buf);
+ int res = -1;
+
+ if (size) {
+ char *p = d_path_compat(path, buf, size);
+ if (!IS_ERR(p)) {
+ char *end = mangle_path(buf, p, esc);
+ if (end)
+ res = end - buf;
+ }
+ }
+ seq_commit(m, res);
+
+ return res;
+}
+
static int show_vfsmnt(struct seq_file *m, void *v)
{
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
@@ -797,7 +852,7 @@ static int show_vfsmnt(struct seq_file *

mangle(m, mnt->mnt_devname ? mnt->mnt_devname : "none");
seq_putc(m, ' ');
- seq_path(m, &mnt_path, " \t\n\\");
+ show_path_old(m, &mnt_path, " \t\n\\");
seq_putc(m, ' ');
show_type(m, mnt->mnt_sb);
seq_puts(m, __mnt_is_readonly(mnt) ? " ro" : " rw");
Index: linux-2.6/mm/shmem.c
===================================================================
--- linux-2.6.orig/mm/shmem.c 2009-09-21 14:31:35.000000000 +0200
+++ linux-2.6/mm/shmem.c 2009-09-21 14:31:37.000000000 +0200
@@ -2601,6 +2601,21 @@ int shmem_unuse(swp_entry_t entry, struc

/* common code */

+/*
+ * Do a special d_dname function so that these are not prefixed by
+ * "(unreachable)".
+ */
+static char *shmem_unlinked_d_dname(struct dentry *dentry, char *buf,
+ int buflen)
+{
+ return dynamic_dname(dentry, buf, buflen, "/%s (deleted)",
+ dentry->d_name.name);
+}
+
+static struct dentry_operations shmem_unlinked_dentry_operations = {
+ .d_dname = shmem_unlinked_d_dname,
+};
+
/**
* shmem_file_setup - get an unlinked file living in tmpfs
* @name: name for dentry (to be seen in /proc/<pid>/maps
@@ -2633,6 +2648,8 @@ struct file *shmem_file_setup(const char
if (!dentry)
goto put_memory;

+ dentry->d_op = &shmem_unlinked_dentry_operations;
+
error = -ENFILE;
file = get_empty_filp();
if (!file)
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/