mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 08:24:42 +01:00
257 lines
7.9 KiB
Diff
257 lines
7.9 KiB
Diff
From: Andreas Gruenbacher <agruen@suse.de>
|
|
Subject: Fix __d_path() for lazy unmounts and make it unambiguous
|
|
|
|
First, when __d_path() hits a lazily unmounted mount point, it tries to prepend
|
|
the name of the lazily unmounted dentry to the path name. It gets this wrong,
|
|
and also overwrites the slash that separates the name from the following
|
|
pathname component. This patch fixes that; if a process was in directory
|
|
/foo/bar and /foo got lazily unmounted, the old result was ``foobar'' (note the
|
|
missing slash), while the new result with this patch is ``foo/bar''.
|
|
|
|
Second, it isn't always possible to tell from the __d_path() result whether the
|
|
specified root and rootmnt (i.e., the chroot) was reached. We need an
|
|
unambiguous result for AppArmor at least though, so we make sure that paths
|
|
will only start with a slash if the path leads all the way up to the root.
|
|
|
|
We also add a @fail_deleted argument, which allows to get rid of some of the
|
|
mess in sys_getcwd().
|
|
|
|
This patch leaves getcwd() and d_path() as they were before for everything
|
|
except for bind-mounted directories; for them, it reports ``/foo/bar'' instead
|
|
of ``foobar'' in the example described above.
|
|
|
|
Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
|
|
Signed-off-by: John Johansen <jjohansen@suse.de>
|
|
Acked-by: Alan Cox <alan@lxorguk.ukuu.org.uk>
|
|
|
|
---
|
|
fs/dcache.c | 126 ++++++++++++++++++++++++++-----------------------
|
|
fs/seq_file.c | 4 -
|
|
include/linux/dcache.h | 5 +
|
|
3 files changed, 74 insertions(+), 61 deletions(-)
|
|
|
|
--- a/fs/dcache.c
|
|
+++ b/fs/dcache.c
|
|
@@ -1768,44 +1768,46 @@ static int prepend_name(char **buffer, i
|
|
* @root: root vfsmnt/dentry (may be modified by this function)
|
|
* @buffer: buffer to return value in
|
|
* @buflen: buffer length
|
|
+ * @flags: flags controling behavior of d_path
|
|
*
|
|
- * Convert a dentry into an ASCII path name. If the entry has been deleted
|
|
- * the string " (deleted)" is appended. Note that this is ambiguous.
|
|
- *
|
|
- * Returns the buffer or an error code if the path was too long.
|
|
- *
|
|
- * "buflen" should be positive. Caller holds the dcache_lock.
|
|
+ * Convert a dentry into an ASCII path name. If the entry has been deleted,
|
|
+ * then if @flags has D_PATH_FAIL_DELETED set, ERR_PTR(-ENOENT) is returned.
|
|
+ * Otherwise, the string " (deleted)" is appended. Note that this is ambiguous.
|
|
*
|
|
* If path is not reachable from the supplied root, then the value of
|
|
- * root is changed (without modifying refcounts).
|
|
+ * root is changed (without modifying refcounts). The path returned in this
|
|
+ * case will be relative (i.e., it will not start with a slash).
|
|
+ *
|
|
+ * Returns the buffer or an error code if the path was too long.
|
|
*/
|
|
char *__d_path(const struct path *path, struct path *root,
|
|
- char *buffer, int buflen)
|
|
+ char *buffer, int buflen, int flags)
|
|
{
|
|
struct dentry *dentry = path->dentry;
|
|
struct vfsmount *vfsmnt = path->mnt;
|
|
- char *end = buffer + buflen;
|
|
- char *retval;
|
|
+ const unsigned char *name;
|
|
+ int namelen;
|
|
+
|
|
+ buffer += buflen;
|
|
+ prepend(&buffer, &buflen, "\0", 1);
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
- prepend(&end, &buflen, "\0", 1);
|
|
- if (!IS_ROOT(dentry) && d_unhashed(dentry) &&
|
|
- (prepend(&end, &buflen, " (deleted)", 10) != 0))
|
|
+ spin_lock(&dcache_lock);
|
|
+ if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
|
|
+ if (flags & D_PATH_FAIL_DELETED) {
|
|
+ buffer = ERR_PTR(-ENOENT);
|
|
+ goto out;
|
|
+ }
|
|
+ if (prepend(&buffer, &buflen, " (deleted)", 10) != 0)
|
|
goto Elong;
|
|
-
|
|
+ }
|
|
if (buflen < 1)
|
|
goto Elong;
|
|
- /* Get '/' right */
|
|
- retval = end-1;
|
|
- *retval = '/';
|
|
|
|
- for (;;) {
|
|
+ while (dentry != root->dentry || vfsmnt != root->mnt) {
|
|
struct dentry * parent;
|
|
|
|
- if (dentry == root->dentry && vfsmnt == root->mnt)
|
|
- break;
|
|
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
|
|
- /* Global root? */
|
|
if (vfsmnt->mnt_parent == vfsmnt) {
|
|
goto global_root;
|
|
}
|
|
@@ -1815,27 +1817,51 @@ char *__d_path(const struct path *path,
|
|
}
|
|
parent = dentry->d_parent;
|
|
prefetch(parent);
|
|
- if ((prepend_name(&end, &buflen, &dentry->d_name) != 0) ||
|
|
- (prepend(&end, &buflen, "/", 1) != 0))
|
|
+ if ((prepend_name(&buffer, &buflen, &dentry->d_name) != 0) ||
|
|
+ (prepend(&buffer, &buflen, "/", 1) != 0))
|
|
goto Elong;
|
|
- retval = end;
|
|
dentry = parent;
|
|
}
|
|
+ /* Get '/' right. */
|
|
+ if (*buffer != '/' && prepend(&buffer, &buflen, "/", 1))
|
|
+ goto Elong;
|
|
|
|
out:
|
|
+ spin_unlock(&dcache_lock);
|
|
spin_unlock(&vfsmount_lock);
|
|
- return retval;
|
|
+ return buffer;
|
|
|
|
global_root:
|
|
- retval += 1; /* hit the slash */
|
|
- if (prepend_name(&retval, &buflen, &dentry->d_name) != 0)
|
|
+ /*
|
|
+ * We went past the (vfsmount, dentry) we were looking for and have
|
|
+ * either hit a root dentry, a lazily unmounted dentry, an
|
|
+ * unconnected dentry, or the file is on a pseudo filesystem.
|
|
+ */
|
|
+ namelen = dentry->d_name.len;
|
|
+ name = dentry->d_name.name;
|
|
+
|
|
+ /*
|
|
+ * If this is a root dentry, then overwrite the slash. This
|
|
+ * will also DTRT with pseudo filesystems which have root
|
|
+ * dentries named "foo:".
|
|
+ */
|
|
+ if (IS_ROOT(dentry) && *buffer == '/') {
|
|
+ buffer++;
|
|
+ buflen++;
|
|
+ }
|
|
+ if ((flags & D_PATH_DISCONNECT) && *name == '/') {
|
|
+ /* Make sure we won't return a pathname starting with '/' */
|
|
+ name++;
|
|
+ namelen--;
|
|
+ }
|
|
+ if (prepend(&buffer, &buflen, name, namelen))
|
|
goto Elong;
|
|
root->mnt = vfsmnt;
|
|
root->dentry = dentry;
|
|
goto out;
|
|
|
|
Elong:
|
|
- retval = ERR_PTR(-ENAMETOOLONG);
|
|
+ buffer = ERR_PTR(-ENAMETOOLONG);
|
|
goto out;
|
|
}
|
|
|
|
@@ -1872,10 +1898,8 @@ char *d_path(const struct path *path, ch
|
|
root = current->fs->root;
|
|
path_get(&root);
|
|
read_unlock(¤t->fs->lock);
|
|
- spin_lock(&dcache_lock);
|
|
tmp = root;
|
|
- res = __d_path(path, &tmp, buf, buflen);
|
|
- spin_unlock(&dcache_lock);
|
|
+ res = __d_path(path, &tmp, buf, buflen, 0);
|
|
path_put(&root);
|
|
return res;
|
|
}
|
|
@@ -1958,9 +1982,9 @@ Elong:
|
|
*/
|
|
asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
|
|
{
|
|
- int error;
|
|
+ int error, len;
|
|
struct path pwd, root;
|
|
- char *page = (char *) __get_free_page(GFP_USER);
|
|
+ char *page = (char *) __get_free_page(GFP_USER), *cwd;
|
|
|
|
if (!page)
|
|
return -ENOMEM;
|
|
@@ -1972,30 +1996,18 @@ asmlinkage long sys_getcwd(char __user *
|
|
path_get(&root);
|
|
read_unlock(¤t->fs->lock);
|
|
|
|
- error = -ENOENT;
|
|
- /* Has the current directory has been unlinked? */
|
|
- spin_lock(&dcache_lock);
|
|
- if (IS_ROOT(pwd.dentry) || !d_unhashed(pwd.dentry)) {
|
|
- unsigned long len;
|
|
- struct path tmp = root;
|
|
- char * cwd;
|
|
-
|
|
- cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE);
|
|
- spin_unlock(&dcache_lock);
|
|
-
|
|
- error = PTR_ERR(cwd);
|
|
- if (IS_ERR(cwd))
|
|
- goto out;
|
|
-
|
|
- error = -ERANGE;
|
|
- len = PAGE_SIZE + page - cwd;
|
|
- if (len <= size) {
|
|
- error = len;
|
|
- if (copy_to_user(buf, cwd, len))
|
|
- error = -EFAULT;
|
|
- }
|
|
- } else
|
|
- spin_unlock(&dcache_lock);
|
|
+ cwd = __d_path(&pwd, &root, page, PAGE_SIZE, 0);
|
|
+ error = PTR_ERR(cwd);
|
|
+ if (IS_ERR(cwd))
|
|
+ goto out;
|
|
+
|
|
+ error = -ERANGE;
|
|
+ len = PAGE_SIZE + page - cwd;
|
|
+ if (len <= size) {
|
|
+ error = len;
|
|
+ if (copy_to_user(buf, cwd, len))
|
|
+ error = -EFAULT;
|
|
+ }
|
|
|
|
out:
|
|
path_put(&pwd);
|
|
--- a/fs/seq_file.c
|
|
+++ b/fs/seq_file.c
|
|
@@ -405,9 +405,7 @@ int seq_path_root(struct seq_file *m, st
|
|
char *s = m->buf + m->count;
|
|
char *p;
|
|
|
|
- spin_lock(&dcache_lock);
|
|
- p = __d_path(path, root, s, m->size - m->count);
|
|
- spin_unlock(&dcache_lock);
|
|
+ p = __d_path(path, root, s, m->size - m->count, 0);
|
|
err = PTR_ERR(p);
|
|
if (!IS_ERR(p)) {
|
|
s = mangle_path(s, p, esc);
|
|
--- a/include/linux/dcache.h
|
|
+++ b/include/linux/dcache.h
|
|
@@ -297,9 +297,12 @@ extern int d_validate(struct dentry *, s
|
|
/*
|
|
* helper function for dentry_operations.d_dname() members
|
|
*/
|
|
+#define D_PATH_FAIL_DELETED 1
|
|
+#define D_PATH_DISCONNECT 2
|
|
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 *path, struct path *root, char *, int,
|
|
+ int);
|
|
extern char *d_path(const struct path *, char *, int);
|
|
extern char *dentry_path(struct dentry *, char *, int);
|
|
|