mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-05 08:45:22 +00:00
446 lines
13 KiB
Diff
446 lines
13 KiB
Diff
From: Andreas Gruenbacher <agruen@suse.de>
|
|
|
|
Fix __d_path() for lazy unmounts and make it unambiguous; exclude unreachable
|
|
mount points from /proc/mounts
|
|
|
|
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.
|
|
|
|
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: lazy unmounts of
|
|
bind mounts will produce a path that does start with a non-slash so we can
|
|
tell from that, but other lazy unmounts will produce a path that starts with a
|
|
slash, just like "ordinary" paths.
|
|
|
|
Third, sys_getcwd() shouldn't return disconnected paths. The patch checks for
|
|
that, and makes it fail with -ENOENT in that case.
|
|
|
|
Fourth, this now allows us to tell unreachable mount points from reachable
|
|
ones when generating the /proc/mounts and /proc/$pid/mountstats files.
|
|
Unreachable mount points are not interesting to processes (they can't get
|
|
there, anyway), so we hide unreachable mounts. In particular, ordinary
|
|
processes also will no longer see the rootfs mount (it is unreachable, after
|
|
all). The rootfs mount point will still be reachable to processes like the
|
|
initial initrd init process, and so those processes will continue to see this
|
|
mount point.
|
|
|
|
The attached patch cleans up __d_path() to fix the bug with overlapping
|
|
pathname components. It also adds a @fail_deleted argument, which allows to
|
|
get rid of some of the mess in sys_getcwd(). We make sure that paths will
|
|
only start with a slash if the path leads all the way up to the root. If the
|
|
resulting path would otherwise be empty, we return "." instead so that some
|
|
users of seq_path for files in /proc won't break.
|
|
|
|
The @fail_deleted argument allows sys_getcwd() to be simplified. Grabbing the
|
|
dcache_lock can be moved into __d_path().
|
|
|
|
The @fail_deleted argument could be added to d_path() as well: this would
|
|
allow callers to recognize deleted files without having to resort to the
|
|
ambiguous check for the " (deleted)" string at the end of the pathnames. This
|
|
is not currently done, but it might be worthwhile.
|
|
|
|
This patch also removes some code duplication between mounts_open() and
|
|
mountstats_open().
|
|
|
|
Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
|
|
Reviewed-by: NeilBrown <neilb@suse.de>
|
|
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
|
|
|
|
Index: linux-2.6-apparmor/fs/dcache.c
|
|
===================================================================
|
|
--- linux-2.6-apparmor.orig/fs/dcache.c
|
|
+++ linux-2.6-apparmor/fs/dcache.c
|
|
@@ -1732,52 +1732,51 @@ 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 dentry *root, struct vfsmount *rootmnt,
|
|
- char *buffer, int buflen)
|
|
-{
|
|
- char * end = buffer+buflen;
|
|
- char * retval;
|
|
- int namelen;
|
|
+static char *__d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
|
|
+ struct dentry *root, struct vfsmount *rootmnt,
|
|
+ char *buffer, int buflen, int fail_deleted)
|
|
+{
|
|
+ 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 || vfsmnt != rootmnt) {
|
|
struct dentry * parent;
|
|
|
|
- if (dentry == root && vfsmnt == rootmnt)
|
|
- break;
|
|
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
|
|
- /* Global root? */
|
|
spin_lock(&vfsmount_lock);
|
|
if (vfsmnt->mnt_parent == vfsmnt) {
|
|
spin_unlock(&vfsmount_lock);
|
|
@@ -1791,33 +1790,63 @@ static char * __d_path( struct dentry *d
|
|
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) {
|
|
+ if (*buffer == '\0')
|
|
+ *--buffer = '.';
|
|
+ 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;
|
|
}
|
|
|
|
/* write full pathname into buffer and return start of pathname */
|
|
-char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
|
|
- char *buf, int buflen)
|
|
+char *d_path(struct dentry *dentry, struct vfsmount *vfsmnt, char *buf,
|
|
+ int buflen)
|
|
{
|
|
char *res;
|
|
struct vfsmount *rootmnt;
|
|
@@ -1827,9 +1856,7 @@ char * d_path(struct dentry *dentry, str
|
|
rootmnt = mntget(current->fs->rootmnt);
|
|
root = dget(current->fs->root);
|
|
read_unlock(¤t->fs->lock);
|
|
- spin_lock(&dcache_lock);
|
|
- res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen);
|
|
- spin_unlock(&dcache_lock);
|
|
+ res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen, 0);
|
|
dput(root);
|
|
mntput(rootmnt);
|
|
return res;
|
|
@@ -1855,10 +1882,10 @@ char * d_path(struct dentry *dentry, str
|
|
*/
|
|
asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
|
|
{
|
|
- int error;
|
|
+ int error, len;
|
|
struct vfsmount *pwdmnt, *rootmnt;
|
|
struct dentry *pwd, *root;
|
|
- char *page = (char *) __get_free_page(GFP_USER);
|
|
+ char *page = (char *) __get_free_page(GFP_USER), *cwd;
|
|
|
|
if (!page)
|
|
return -ENOMEM;
|
|
@@ -1870,29 +1897,21 @@ asmlinkage long sys_getcwd(char __user *
|
|
root = dget(current->fs->root);
|
|
read_unlock(¤t->fs->lock);
|
|
|
|
+ cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE, 1);
|
|
+ error = PTR_ERR(cwd);
|
|
+ if (IS_ERR(cwd))
|
|
+ goto out;
|
|
error = -ENOENT;
|
|
- /* Has the current directory has been unlinked? */
|
|
- spin_lock(&dcache_lock);
|
|
- if (pwd->d_parent == pwd || !d_unhashed(pwd)) {
|
|
- unsigned long len;
|
|
- char * cwd;
|
|
-
|
|
- cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE);
|
|
- spin_unlock(&dcache_lock);
|
|
-
|
|
- error = PTR_ERR(cwd);
|
|
- if (IS_ERR(cwd))
|
|
- goto out;
|
|
+ if (*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);
|
|
+ error = -ERANGE;
|
|
+ len = PAGE_SIZE + page - cwd;
|
|
+ if (len <= size) {
|
|
+ error = len;
|
|
+ if (copy_to_user(buf, cwd, len))
|
|
+ error = -EFAULT;
|
|
+ }
|
|
|
|
out:
|
|
dput(pwd);
|
|
Index: linux-2.6-apparmor/fs/namespace.c
|
|
===================================================================
|
|
--- linux-2.6-apparmor.orig/fs/namespace.c
|
|
+++ linux-2.6-apparmor/fs/namespace.c
|
|
@@ -348,8 +348,16 @@ static inline void mangle(struct seq_fil
|
|
seq_escape(m, s, " \t\n\\");
|
|
}
|
|
|
|
+/* Keep in sync with fs/proc/base.c! */
|
|
+struct proc_mounts {
|
|
+ struct seq_file m;
|
|
+ void *page;
|
|
+ int event;
|
|
+};
|
|
+
|
|
static int show_vfsmnt(struct seq_file *m, void *v)
|
|
{
|
|
+ void *page = container_of(m, struct proc_mounts, m)->page;
|
|
struct vfsmount *mnt = v;
|
|
int err = 0;
|
|
static struct proc_fs_info {
|
|
@@ -371,10 +379,15 @@ static int show_vfsmnt(struct seq_file *
|
|
{ 0, NULL }
|
|
};
|
|
struct proc_fs_info *fs_infop;
|
|
+ char *path;
|
|
+
|
|
+ path = d_path(mnt->mnt_root, mnt, page, PAGE_SIZE);
|
|
+ if (IS_ERR(path) || *path != '/')
|
|
+ return err;
|
|
|
|
mangle(m, mnt->mnt_devname ? mnt->mnt_devname : "none");
|
|
seq_putc(m, ' ');
|
|
- seq_path(m, mnt, mnt->mnt_root, " \t\n\\");
|
|
+ mangle(m, path);
|
|
seq_putc(m, ' ');
|
|
mangle(m, mnt->mnt_sb->s_type->name);
|
|
seq_puts(m, mnt->mnt_sb->s_flags & MS_RDONLY ? " ro" : " rw");
|
|
@@ -401,8 +414,14 @@ struct seq_operations mounts_op = {
|
|
|
|
static int show_vfsstat(struct seq_file *m, void *v)
|
|
{
|
|
+ void *page = container_of(m, struct proc_mounts, m)->page;
|
|
struct vfsmount *mnt = v;
|
|
int err = 0;
|
|
+ char *path;
|
|
+
|
|
+ path = d_path(mnt->mnt_root, mnt, page, PAGE_SIZE);
|
|
+ if (IS_ERR(path) || *path != '/')
|
|
+ return err; /* error or path unreachable from chroot */
|
|
|
|
/* device */
|
|
if (mnt->mnt_devname) {
|
|
@@ -413,7 +432,7 @@ static int show_vfsstat(struct seq_file
|
|
|
|
/* mount point */
|
|
seq_puts(m, " mounted on ");
|
|
- seq_path(m, mnt, mnt->mnt_root, " \t\n\\");
|
|
+ mangle(m, path);
|
|
seq_putc(m, ' ');
|
|
|
|
/* file system type */
|
|
Index: linux-2.6-apparmor/fs/proc/base.c
|
|
===================================================================
|
|
--- linux-2.6-apparmor.orig/fs/proc/base.c
|
|
+++ linux-2.6-apparmor/fs/proc/base.c
|
|
@@ -353,13 +353,16 @@ static const struct inode_operations pro
|
|
.setattr = proc_setattr,
|
|
};
|
|
|
|
+/* Keep in sync with fs/namespace.c! */
|
|
extern struct seq_operations mounts_op;
|
|
struct proc_mounts {
|
|
struct seq_file m;
|
|
+ void *page;
|
|
int event;
|
|
};
|
|
|
|
-static int mounts_open(struct inode *inode, struct file *file)
|
|
+static int __mounts_open(struct inode *inode, struct file *file,
|
|
+ struct seq_operations *seq_ops)
|
|
{
|
|
struct task_struct *task = get_proc_task(inode);
|
|
struct mnt_namespace *ns = NULL;
|
|
@@ -382,12 +385,16 @@ static int mounts_open(struct inode *ino
|
|
p = kmalloc(sizeof(struct proc_mounts), GFP_KERNEL);
|
|
if (p) {
|
|
file->private_data = &p->m;
|
|
- ret = seq_open(file, &mounts_op);
|
|
+ p->page = (void *)__get_free_page(GFP_KERNEL);
|
|
+ if (p->page)
|
|
+ ret = seq_open(file, seq_ops);
|
|
if (!ret) {
|
|
p->m.private = ns;
|
|
p->event = ns->event;
|
|
return 0;
|
|
}
|
|
+ if (p->page)
|
|
+ free_page((unsigned long)p->page);
|
|
kfree(p);
|
|
}
|
|
put_mnt_ns(ns);
|
|
@@ -395,17 +402,26 @@ static int mounts_open(struct inode *ino
|
|
return ret;
|
|
}
|
|
|
|
+static int mounts_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ return __mounts_open(inode, file, &mounts_op);
|
|
+}
|
|
+
|
|
static int mounts_release(struct inode *inode, struct file *file)
|
|
{
|
|
- struct seq_file *m = file->private_data;
|
|
- struct mnt_namespace *ns = m->private;
|
|
+ struct proc_mounts *p =
|
|
+ container_of(file->private_data, struct proc_mounts, m);
|
|
+ struct mnt_namespace *ns = p->m.private;
|
|
+
|
|
+ free_page((unsigned long)p->page);
|
|
put_mnt_ns(ns);
|
|
return seq_release(inode, file);
|
|
}
|
|
|
|
static unsigned mounts_poll(struct file *file, poll_table *wait)
|
|
{
|
|
- struct proc_mounts *p = file->private_data;
|
|
+ struct proc_mounts *p =
|
|
+ container_of(file->private_data, struct proc_mounts, m);
|
|
struct mnt_namespace *ns = p->m.private;
|
|
unsigned res = 0;
|
|
|
|
@@ -432,31 +448,7 @@ static const struct file_operations proc
|
|
extern struct seq_operations mountstats_op;
|
|
static int mountstats_open(struct inode *inode, struct file *file)
|
|
{
|
|
- int ret = seq_open(file, &mountstats_op);
|
|
-
|
|
- if (!ret) {
|
|
- struct seq_file *m = file->private_data;
|
|
- struct mnt_namespace *mnt_ns = NULL;
|
|
- struct task_struct *task = get_proc_task(inode);
|
|
-
|
|
- if (task) {
|
|
- task_lock(task);
|
|
- if (task->nsproxy)
|
|
- mnt_ns = task->nsproxy->mnt_ns;
|
|
- if (mnt_ns)
|
|
- get_mnt_ns(mnt_ns);
|
|
- task_unlock(task);
|
|
- put_task_struct(task);
|
|
- }
|
|
-
|
|
- if (mnt_ns)
|
|
- m->private = mnt_ns;
|
|
- else {
|
|
- seq_release(inode, file);
|
|
- ret = -EINVAL;
|
|
- }
|
|
- }
|
|
- return ret;
|
|
+ return __mounts_open(inode, file, &mountstats_op);
|
|
}
|
|
|
|
static const struct file_operations proc_mountstats_operations = {
|