Re: [git pull] apparmor fix for __d_path() misuse

From: John Johansen
Date: Tue Dec 06 2011 - 22:12:04 EST


On 12/06/2011 05:37 PM, Al Viro wrote:
> On Wed, Dec 07, 2011 at 01:10:47AM +0000, Al Viro wrote:
>
>> So let's add d_absolute_path(path, buf, buflen). Having it check that
>> we'd walked to something mounted. And returning NULL otherwise. _Never_
>
> Turns out that returning ERR_PTR(-EINVAL) is more convenient.
> All right, here's a variant that does *NOT* return any vfsmount/dentry
> pointers at all. And it does best-sane-effort wrt returning the pathname;
> i.e. if it *is* something outside of chroot, that absolute pathname is
> what we'll get.
>
I haven't had a chance to build and test yet but this looks good to me. I do
think splitting the two uses cases makes sense. I am building a kernel now
and will update when I am done with it.



> diff --git a/fs/dcache.c b/fs/dcache.c
> index 10ba92d..89509b5 100644
> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -2439,16 +2439,14 @@ 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
> * @buffer: pointer to the end of the buffer
> * @buflen: pointer to buffer length
> *
> * Caller holds the rename_lock.
> - *
> - * 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,
> char **buffer, int *buflen)
> {
> struct dentry *dentry = path->dentry;
> @@ -2483,10 +2481,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,15 +2498,17 @@ 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 (!slash)
> + error = prepend(buffer, buflen, "/", 1);
> + if (!error)
> + error = vfsmnt->mnt_ns ? 1 : 2;
> goto out;
> }
>
> /**
> * __d_path - return the path of a dentry
> * @path: the dentry/vfsmount to report
> - * @root: root vfsmnt/dentry (may be modified by this function)
> + * @root: root vfsmnt/dentry
> * @buf: buffer to return value in
> * @buflen: buffer length
> *
> @@ -2519,10 +2519,10 @@ global_root:
> *
> * "buflen" should be positive.
> *
> - * If path is not reachable from the supplied root, then the value of
> - * root is changed (without modifying refcounts).
> + * If the path is not reachable from the supplied root, return %NULL.
> */
> -char *__d_path(const struct path *path, struct path *root,
> +char *__d_path(const struct path *path,
> + const struct path *root,
> char *buf, int buflen)
> {
> char *res = buf + buflen;
> @@ -2533,7 +2533,28 @@ char *__d_path(const struct path *path, struct path *root,
> error = prepend_path(path, root, &res, &buflen);
> write_sequnlock(&rename_lock);
>
> - if (error)
> + if (error < 0)
> + return ERR_PTR(error);
> + if (error > 0)
> + return NULL;
> + return res;
> +}
> +
> +char *d_absolute_path(const struct path *path,
> + char *buf, int buflen)
> +{
> + struct path root = {};
> + char *res = buf + buflen;
> + int error;
> +
> + prepend(&res, &buflen, "\0", 1);
> + write_seqlock(&rename_lock);
> + error = prepend_path(path, &root, &res, &buflen);
> + write_sequnlock(&rename_lock);
> +
> + if (error > 1)
> + error = -EINVAL;
> + if (error < 0)
> return ERR_PTR(error);
> return res;
> }
> @@ -2541,8 +2562,9 @@ char *__d_path(const struct path *path, struct path *root,
> /*
> * 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)) {
> @@ -2579,7 +2601,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 +2615,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 +2637,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 +2644,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 +2776,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, &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 6d3a196..cfc6d44 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -1048,15 +1048,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);
>
> @@ -2776,3 +2773,8 @@ void kern_unmount(struct vfsmount *mnt)
> }
> }
> EXPORT_SYMBOL(kern_unmount);
> +
> +bool our_mnt(struct vfsmount *mnt)
> +{
> + return check_mnt(mnt);
> +}
> diff --git a/fs/seq_file.c b/fs/seq_file.c
> index 05d6b0e..dba43c3 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)
> @@ -463,6 +461,8 @@ int seq_path_root(struct seq_file *m, struct path *path, struct path *root,
> char *p;
>
> p = __d_path(path, root, 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..ed9f74f 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 *, char *, int);
> +extern char *d_absolute_path(const 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..b566eba 100644
> --- a/security/apparmor/path.c
> +++ b/security/apparmor/path.c
> @@ -57,23 +57,44 @@ 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;
> char *res;
> - int connected, error = 0;
> + int error = 0;
> + int connected = 1;
> +
> + if (path->mnt->mnt_flags & MNT_INTERNAL) {
> + /* it's not mounted anywhere */
> + res = dentry_path(path->dentry, buf, buflen);
> + *name = res;
> + if (IS_ERR(res)) {
> + *name = buf;
> + return PTR_ERR(res);
> + }
> + if (path->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
> + */
> + return prepend(name, *name - buf, "/proc", 5);
> + }
> + return 0;
> + }
>
> - /* Get the root we want to resolve too, released below */
> + /* resolve paths relative to chroot?*/
> if (flags & PATH_CHROOT_REL) {
> - /* resolve paths relative to chroot */
> + struct path root;
> 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);
> + res = __d_path(path, &root, buf, buflen);
> + if (res && !IS_ERR(res)) {
> + /* everything's fine */
> + *name = res;
> + path_put(&root);
> + goto ok;
> + }
> + path_put(&root);
> + connected = 0;
> }
>
> - tmp = root;
> - res = __d_path(path, &tmp, buf, buflen);
> + res = d_absolute_path(path, buf, buflen);
>
> *name = res;
> /* handle error conditions - and still allow a partial path to
> @@ -84,7 +105,10 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
> *name = buf;
> goto out;
> }
> + if (!our_mnt(path->mnt))
> + connected = 0;
>
> +ok:
> /* Handle two cases:
> * 1. A deleted dentry && profile is not allowing mediation of deleted
> * 2. On some filesystems, newly allocated dentries appear to the
> @@ -97,10 +121,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
> @@ -112,17 +133,9 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
> * namespace root.
> */
> if (!connected) {
> - /* is the disconnect path a sysctl? */
> - if (tmp.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
> - */
> - error = prepend(name, *name - buf, "/proc", 5);
> - } else if (!(flags & PATH_CONNECT_PATH) &&
> + 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(path->mnt))) {
> /* disconnected path, don't return pathname starting
> * with '/'
> */
> @@ -133,8 +146,6 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
> }
>
> out:
> - path_put(&root);
> -
> return error;
> }
>
> diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c
> index 738bbdf..36fa7c9 100644
> --- a/security/tomoyo/realpath.c
> +++ b/security/tomoyo/realpath.c
> @@ -101,9 +101,8 @@ static char *tomoyo_get_absolute_path(struct path *path, char * const buffer,
> {
> char *pos = ERR_PTR(-ENOMEM);
> 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_absolute_path(path, 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-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/