| From 1fa1e7f615f4d3ae436fa319af6e4eebdd4026a8 Mon Sep 17 00:00:00 2001 |
| From: Andy Whitcroft <apw@canonical.com> |
| Date: Wed, 2 Nov 2011 09:44:39 +0100 |
| Subject: readlinkat: ensure we return ENOENT for the empty pathname for normal lookups |
| |
| From: Andy Whitcroft <apw@canonical.com> |
| |
| commit 1fa1e7f615f4d3ae436fa319af6e4eebdd4026a8 upstream. |
| |
| Since the commit below which added O_PATH support to the *at() calls, the |
| error return for readlink/readlinkat for the empty pathname has switched |
| from ENOENT to EINVAL: |
| |
| commit 65cfc6722361570bfe255698d9cd4dccaf47570d |
| Author: Al Viro <viro@zeniv.linux.org.uk> |
| Date: Sun Mar 13 15:56:26 2011 -0400 |
| |
| readlinkat(), fchownat() and fstatat() with empty relative pathnames |
| |
| This is both unexpected for userspace and makes readlink/readlinkat |
| inconsistant with all other interfaces; and inconsistant with our stated |
| return for these pathnames. |
| |
| As the readlinkat call does not have a flags parameter we cannot use the |
| AT_EMPTY_PATH approach used in the other calls. Therefore expose whether |
| the original path is infact entry via a new user_path_at_empty() path |
| lookup function. Use this to determine whether to default to EINVAL or |
| ENOENT for failures. |
| |
| Addresses http://bugs.launchpad.net/bugs/817187 |
| |
| [akpm@linux-foundation.org: remove unused getname_flags()] |
| Signed-off-by: Andy Whitcroft <apw@canonical.com> |
| Cc: Christoph Hellwig <hch@lst.de> |
| Cc: Al Viro <viro@zeniv.linux.org.uk> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Christoph Hellwig <hch@lst.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| fs/namei.c | 18 +++++++++++++----- |
| fs/stat.c | 5 +++-- |
| include/linux/namei.h | 1 + |
| 3 files changed, 17 insertions(+), 7 deletions(-) |
| |
| --- a/fs/namei.c |
| +++ b/fs/namei.c |
| @@ -137,7 +137,7 @@ static int do_getname(const char __user |
| return retval; |
| } |
| |
| -static char *getname_flags(const char __user * filename, int flags) |
| +static char *getname_flags(const char __user *filename, int flags, int *empty) |
| { |
| char *tmp, *result; |
| |
| @@ -148,6 +148,8 @@ static char *getname_flags(const char __ |
| |
| result = tmp; |
| if (retval < 0) { |
| + if (retval == -ENOENT && empty) |
| + *empty = 1; |
| if (retval != -ENOENT || !(flags & LOOKUP_EMPTY)) { |
| __putname(tmp); |
| result = ERR_PTR(retval); |
| @@ -160,7 +162,7 @@ static char *getname_flags(const char __ |
| |
| char *getname(const char __user * filename) |
| { |
| - return getname_flags(filename, 0); |
| + return getname_flags(filename, 0, 0); |
| } |
| |
| #ifdef CONFIG_AUDITSYSCALL |
| @@ -1798,11 +1800,11 @@ struct dentry *lookup_one_len(const char |
| return __lookup_hash(&this, base, NULL); |
| } |
| |
| -int user_path_at(int dfd, const char __user *name, unsigned flags, |
| - struct path *path) |
| +int user_path_at_empty(int dfd, const char __user *name, unsigned flags, |
| + struct path *path, int *empty) |
| { |
| struct nameidata nd; |
| - char *tmp = getname_flags(name, flags); |
| + char *tmp = getname_flags(name, flags, empty); |
| int err = PTR_ERR(tmp); |
| if (!IS_ERR(tmp)) { |
| |
| @@ -1816,6 +1818,12 @@ int user_path_at(int dfd, const char __u |
| return err; |
| } |
| |
| +int user_path_at(int dfd, const char __user *name, unsigned flags, |
| + struct path *path) |
| +{ |
| + return user_path_at_empty(dfd, name, flags, path, 0); |
| +} |
| + |
| static int user_path_parent(int dfd, const char __user *path, |
| struct nameidata *nd, char **name) |
| { |
| --- a/fs/stat.c |
| +++ b/fs/stat.c |
| @@ -294,15 +294,16 @@ SYSCALL_DEFINE4(readlinkat, int, dfd, co |
| { |
| struct path path; |
| int error; |
| + int empty = 0; |
| |
| if (bufsiz <= 0) |
| return -EINVAL; |
| |
| - error = user_path_at(dfd, pathname, LOOKUP_EMPTY, &path); |
| + error = user_path_at_empty(dfd, pathname, LOOKUP_EMPTY, &path, &empty); |
| if (!error) { |
| struct inode *inode = path.dentry->d_inode; |
| |
| - error = -EINVAL; |
| + error = empty ? -ENOENT : -EINVAL; |
| if (inode->i_op->readlink) { |
| error = security_inode_readlink(path.dentry); |
| if (!error) { |
| --- a/include/linux/namei.h |
| +++ b/include/linux/namei.h |
| @@ -67,6 +67,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LA |
| #define LOOKUP_EMPTY 0x4000 |
| |
| extern int user_path_at(int, const char __user *, unsigned, struct path *); |
| +extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty); |
| |
| #define user_path(name, path) user_path_at(AT_FDCWD, name, LOOKUP_FOLLOW, path) |
| #define user_lpath(name, path) user_path_at(AT_FDCWD, name, 0, path) |