Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

readlinkat: ensure we return ENOENT for the empty pathname for normal lookups

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>
Cc: <stable@kernel.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>

authored by

Andy Whitcroft and committed by
Christoph Hellwig
1fa1e7f6 32096ea1

+17 -7
+13 -5
fs/namei.c
··· 137 137 return retval; 138 138 } 139 139 140 - static char *getname_flags(const char __user * filename, int flags) 140 + static char *getname_flags(const char __user *filename, int flags, int *empty) 141 141 { 142 142 char *tmp, *result; 143 143 ··· 148 148 149 149 result = tmp; 150 150 if (retval < 0) { 151 + if (retval == -ENOENT && empty) 152 + *empty = 1; 151 153 if (retval != -ENOENT || !(flags & LOOKUP_EMPTY)) { 152 154 __putname(tmp); 153 155 result = ERR_PTR(retval); ··· 162 160 163 161 char *getname(const char __user * filename) 164 162 { 165 - return getname_flags(filename, 0); 163 + return getname_flags(filename, 0, 0); 166 164 } 167 165 168 166 #ifdef CONFIG_AUDITSYSCALL ··· 1800 1798 return __lookup_hash(&this, base, NULL); 1801 1799 } 1802 1800 1803 - int user_path_at(int dfd, const char __user *name, unsigned flags, 1804 - struct path *path) 1801 + int user_path_at_empty(int dfd, const char __user *name, unsigned flags, 1802 + struct path *path, int *empty) 1805 1803 { 1806 1804 struct nameidata nd; 1807 - char *tmp = getname_flags(name, flags); 1805 + char *tmp = getname_flags(name, flags, empty); 1808 1806 int err = PTR_ERR(tmp); 1809 1807 if (!IS_ERR(tmp)) { 1810 1808 ··· 1816 1814 *path = nd.path; 1817 1815 } 1818 1816 return err; 1817 + } 1818 + 1819 + int user_path_at(int dfd, const char __user *name, unsigned flags, 1820 + struct path *path) 1821 + { 1822 + return user_path_at_empty(dfd, name, flags, path, 0); 1819 1823 } 1820 1824 1821 1825 static int user_path_parent(int dfd, const char __user *path,
+3 -2
fs/stat.c
··· 294 294 { 295 295 struct path path; 296 296 int error; 297 + int empty = 0; 297 298 298 299 if (bufsiz <= 0) 299 300 return -EINVAL; 300 301 301 - error = user_path_at(dfd, pathname, LOOKUP_EMPTY, &path); 302 + error = user_path_at_empty(dfd, pathname, LOOKUP_EMPTY, &path, &empty); 302 303 if (!error) { 303 304 struct inode *inode = path.dentry->d_inode; 304 305 305 - error = -EINVAL; 306 + error = empty ? -ENOENT : -EINVAL; 306 307 if (inode->i_op->readlink) { 307 308 error = security_inode_readlink(path.dentry); 308 309 if (!error) {
+1
include/linux/namei.h
··· 67 67 #define LOOKUP_EMPTY 0x4000 68 68 69 69 extern int user_path_at(int, const char __user *, unsigned, struct path *); 70 + extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty); 70 71 71 72 #define user_path(name, path) user_path_at(AT_FDCWD, name, LOOKUP_FOLLOW, path) 72 73 #define user_lpath(name, path) user_path_at(AT_FDCWD, name, 0, path)