mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-22 10:07:12 +00:00
259 lines
7.4 KiB
Diff
259 lines
7.4 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 | 159 +++++++++++++++++++++++++++++++++++-------------------------
|
||
|
1 file changed, 95 insertions(+), 64 deletions(-)
|
||
|
|
||
|
--- a/fs/dcache.c
|
||
|
+++ b/fs/dcache.c
|
||
|
@@ -1747,51 +1747,53 @@ shouldnt_be_hashed:
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
- * d_path - return the path of a dentry
|
||
|
+ * __d_path - return the path of a dentry
|
||
|
* @dentry: dentry to report
|
||
|
* @vfsmnt: vfsmnt to which the dentry belongs
|
||
|
* @root: root dentry
|
||
|
* @rootmnt: vfsmnt to which the root dentry belongs
|
||
|
* @buffer: buffer to return value in
|
||
|
* @buflen: buffer length
|
||
|
+ * @fail_deleted: what to return for deleted files
|
||
|
*
|
||
|
- * Convert a dentry into an ASCII path name. If the entry has been deleted
|
||
|
+ * Convert a dentry into an ASCII path name. If the entry has been deleted,
|
||
|
+ * then if @fail_deleted is true, ERR_PTR(-ENOENT) is returned. Otherwise,
|
||
|
* the string " (deleted)" is appended. Note that this is ambiguous.
|
||
|
*
|
||
|
* Returns the buffer or an error code if the path was too long.
|
||
|
+ * If @dentry is not connected to @root, the path returned will be relative
|
||
|
+ * (i.e., it will not start with a slash).
|
||
|
*
|
||
|
- * "buflen" should be positive. Caller holds the dcache_lock.
|
||
|
+ * Returns the buffer or an error code.
|
||
|
*/
|
||
|
static char *__d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
|
||
|
- struct path *root, char *buffer, int buflen)
|
||
|
+ struct path *root, char *buffer, int buflen,
|
||
|
+ int fail_deleted)
|
||
|
{
|
||
|
- char * end = buffer+buflen;
|
||
|
- char * retval;
|
||
|
- int namelen;
|
||
|
+ int namelen, is_slash;
|
||
|
+
|
||
|
+ if (buflen < 2)
|
||
|
+ return ERR_PTR(-ENAMETOOLONG);
|
||
|
+ buffer += --buflen;
|
||
|
+ *buffer = '\0';
|
||
|
|
||
|
- *--end = '\0';
|
||
|
- buflen--;
|
||
|
+ spin_lock(&dcache_lock);
|
||
|
if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
|
||
|
- buflen -= 10;
|
||
|
- end -= 10;
|
||
|
- if (buflen < 0)
|
||
|
+ if (fail_deleted) {
|
||
|
+ buffer = ERR_PTR(-ENOENT);
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+ if (buflen < 10)
|
||
|
goto Elong;
|
||
|
- memcpy(end, " (deleted)", 10);
|
||
|
+ buflen -= 10;
|
||
|
+ buffer -= 10;
|
||
|
+ memcpy(buffer, " (deleted)", 10);
|
||
|
}
|
||
|
|
||
|
- 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? */
|
||
|
spin_lock(&vfsmount_lock);
|
||
|
if (vfsmnt->mnt_parent == vfsmnt) {
|
||
|
spin_unlock(&vfsmount_lock);
|
||
|
@@ -1805,28 +1807,68 @@ static char *__d_path(struct dentry *den
|
||
|
parent = dentry->d_parent;
|
||
|
prefetch(parent);
|
||
|
namelen = dentry->d_name.len;
|
||
|
- buflen -= namelen + 1;
|
||
|
- if (buflen < 0)
|
||
|
+ if (buflen < namelen + 1)
|
||
|
goto Elong;
|
||
|
- end -= namelen;
|
||
|
- memcpy(end, dentry->d_name.name, namelen);
|
||
|
- *--end = '/';
|
||
|
- retval = end;
|
||
|
+ buflen -= namelen + 1;
|
||
|
+ buffer -= namelen;
|
||
|
+ memcpy(buffer, dentry->d_name.name, namelen);
|
||
|
+ *--buffer = '/';
|
||
|
dentry = parent;
|
||
|
}
|
||
|
+ /* Get '/' right. */
|
||
|
+ if (*buffer != '/')
|
||
|
+ *--buffer = '/';
|
||
|
|
||
|
- return retval;
|
||
|
+out:
|
||
|
+ spin_unlock(&dcache_lock);
|
||
|
+ return buffer;
|
||
|
|
||
|
global_root:
|
||
|
+ /*
|
||
|
+ * 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;
|
||
|
- buflen -= namelen;
|
||
|
- if (buflen < 0)
|
||
|
+ is_slash = (namelen == 1 && *dentry->d_name.name == '/');
|
||
|
+ if (is_slash || (dentry->d_sb->s_flags & MS_NOUSER)) {
|
||
|
+ /*
|
||
|
+ * Make sure we won't return a pathname starting with '/'.
|
||
|
+ *
|
||
|
+ * Historically, we also glue together the root dentry and
|
||
|
+ * remaining name for pseudo filesystems like pipefs, which
|
||
|
+ * have the MS_NOUSER flag set. This results in pathnames
|
||
|
+ * like "pipe:[439336]".
|
||
|
+ */
|
||
|
+ if (*buffer == '/') {
|
||
|
+ buffer++;
|
||
|
+ buflen++;
|
||
|
+ }
|
||
|
+ if (is_slash)
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+ if (buflen < namelen)
|
||
|
goto Elong;
|
||
|
- retval -= namelen-1; /* hit the slash */
|
||
|
- memcpy(retval, dentry->d_name.name, namelen);
|
||
|
- return retval;
|
||
|
+ buffer -= namelen;
|
||
|
+ memcpy(buffer, dentry->d_name.name, namelen);
|
||
|
+ goto out;
|
||
|
+
|
||
|
Elong:
|
||
|
- return ERR_PTR(-ENAMETOOLONG);
|
||
|
+ buffer = ERR_PTR(-ENAMETOOLONG);
|
||
|
+ goto out;
|
||
|
+}
|
||
|
+
|
||
|
+static char *__connect_d_path(char *path, char *buffer, struct dentry *dentry)
|
||
|
+{
|
||
|
+ if (!IS_ERR(path) && *path != '/' &&
|
||
|
+ !(dentry->d_sb->s_flags & MS_NOUSER)) {
|
||
|
+ /* Pretend that disconnected paths are hanging off the root. */
|
||
|
+ if (path == buffer)
|
||
|
+ path = ERR_PTR(-ENAMETOOLONG);
|
||
|
+ else
|
||
|
+ *--path = '/';
|
||
|
+ }
|
||
|
+ return path;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
@@ -1861,9 +1903,8 @@ char *d_path(struct path *path, char *bu
|
||
|
root = current->fs->root;
|
||
|
path_get(¤t->fs->root);
|
||
|
read_unlock(¤t->fs->lock);
|
||
|
- spin_lock(&dcache_lock);
|
||
|
- res = __d_path(path->dentry, path->mnt, &root, buf, buflen);
|
||
|
- spin_unlock(&dcache_lock);
|
||
|
+ res = __d_path(path->dentry, path->mnt, &root, buf, buflen, 0);
|
||
|
+ res = __connect_d_path(res, buf, path->dentry);
|
||
|
path_put(&root);
|
||
|
return res;
|
||
|
}
|
||
|
@@ -1909,9 +1950,9 @@ char *dynamic_dname(struct dentry *dentr
|
||
|
*/
|
||
|
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;
|
||
|
@@ -1923,29 +1964,19 @@ asmlinkage long sys_getcwd(char __user *
|
||
|
path_get(¤t->fs->root);
|
||
|
read_unlock(¤t->fs->lock);
|
||
|
|
||
|
- error = -ENOENT;
|
||
|
- /* Has the current directory has been unlinked? */
|
||
|
- spin_lock(&dcache_lock);
|
||
|
- if (pwd.dentry->d_parent == pwd.dentry || !d_unhashed(pwd.dentry)) {
|
||
|
- unsigned long len;
|
||
|
- char * cwd;
|
||
|
-
|
||
|
- cwd = __d_path(pwd.dentry, pwd.mnt, &root, 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.dentry, pwd.mnt, &root, page, PAGE_SIZE, 1);
|
||
|
+ cwd = __connect_d_path(cwd, page, pwd.dentry);
|
||
|
+ 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);
|