Commit 9f79b78e authored by Linus Torvalds's avatar Linus Torvalds

Convert filldir[64]() from __put_user() to unsafe_put_user()

We really should avoid the "__{get,put}_user()" functions entirely,
because they can easily be mis-used and the original intent of being
used for simple direct user accesses no longer holds in a post-SMAP/PAN
world.

Manually optimizing away the user access range check makes no sense any
more, when the range check is generally much cheaper than the "enable
user accesses" code that the __{get,put}_user() functions still need.

So instead of __put_user(), use the unsafe_put_user() interface with
user_access_{begin,end}() that really does generate better code these
days, and which is generally a nicer interface.  Under some loads, the
multiple user writes that filldir() does are actually quite noticeable.

This also makes the dirent name copy use unsafe_put_user() with a couple
of macros.  We do not want to make function calls with SMAP/PAN
disabled, and the code this generates is quite good when the
architecture uses "asm goto" for unsafe_put_user() like x86 does.

Note that this doesn't bother with the legacy cases.  Nobody should use
them anyway, so performance doesn't really matter there.
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 4d856f72
...@@ -20,9 +20,63 @@ ...@@ -20,9 +20,63 @@
#include <linux/syscalls.h> #include <linux/syscalls.h>
#include <linux/unistd.h> #include <linux/unistd.h>
#include <linux/compat.h> #include <linux/compat.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <asm/unaligned.h>
/*
* Note the "unsafe_put_user() semantics: we goto a
* label for errors.
*
* Also note how we use a "while()" loop here, even though
* only the biggest size needs to loop. The compiler (well,
* at least gcc) is smart enough to turn the smaller sizes
* into just if-statements, and this way we don't need to
* care whether 'u64' or 'u32' is the biggest size.
*/
#define unsafe_copy_loop(dst, src, len, type, label) \
while (len >= sizeof(type)) { \
unsafe_put_user(get_unaligned((type *)src), \
(type __user *)dst, label); \
dst += sizeof(type); \
src += sizeof(type); \
len -= sizeof(type); \
}
/*
* We avoid doing 64-bit copies on 32-bit architectures. They
* might be better, but the component names are mostly small,
* and the 64-bit cases can end up being much more complex and
* put much more register pressure on the code, so it's likely
* not worth the pain of unaligned accesses etc.
*
* So limit the copies to "unsigned long" size. I did verify
* that at least the x86-32 case is ok without this limiting,
* but I worry about random other legacy 32-bit cases that
* might not do as well.
*/
#define unsafe_copy_type(dst, src, len, type, label) do { \
if (sizeof(type) <= sizeof(unsigned long)) \
unsafe_copy_loop(dst, src, len, type, label); \
} while (0)
/*
* Copy the dirent name to user space, and NUL-terminate
* it. This should not be a function call, since we're doing
* the copy inside a "user_access_begin/end()" section.
*/
#define unsafe_copy_dirent_name(_dst, _src, _len, label) do { \
char __user *dst = (_dst); \
const char *src = (_src); \
size_t len = (_len); \
unsafe_copy_type(dst, src, len, u64, label); \
unsafe_copy_type(dst, src, len, u32, label); \
unsafe_copy_type(dst, src, len, u16, label); \
unsafe_copy_type(dst, src, len, u8, label); \
unsafe_put_user(0, dst, label); \
} while (0)
int iterate_dir(struct file *file, struct dir_context *ctx) int iterate_dir(struct file *file, struct dir_context *ctx)
{ {
struct inode *inode = file_inode(file); struct inode *inode = file_inode(file);
...@@ -182,28 +236,31 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen, ...@@ -182,28 +236,31 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen,
return -EOVERFLOW; return -EOVERFLOW;
} }
dirent = buf->previous; dirent = buf->previous;
if (dirent) { if (dirent && signal_pending(current))
if (signal_pending(current))
return -EINTR; return -EINTR;
if (__put_user(offset, &dirent->d_off))
/*
* Note! This range-checks 'previous' (which may be NULL).
* The real range was checked in getdents
*/
if (!user_access_begin(dirent, sizeof(*dirent)))
goto efault; goto efault;
} if (dirent)
unsafe_put_user(offset, &dirent->d_off, efault_end);
dirent = buf->current_dir; dirent = buf->current_dir;
if (__put_user(d_ino, &dirent->d_ino)) unsafe_put_user(d_ino, &dirent->d_ino, efault_end);
goto efault; unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
if (__put_user(reclen, &dirent->d_reclen)) unsafe_put_user(d_type, (char __user *) dirent + reclen - 1, efault_end);
goto efault; unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
if (copy_to_user(dirent->d_name, name, namlen)) user_access_end();
goto efault;
if (__put_user(0, dirent->d_name + namlen))
goto efault;
if (__put_user(d_type, (char __user *) dirent + reclen - 1))
goto efault;
buf->previous = dirent; buf->previous = dirent;
dirent = (void __user *)dirent + reclen; dirent = (void __user *)dirent + reclen;
buf->current_dir = dirent; buf->current_dir = dirent;
buf->count -= reclen; buf->count -= reclen;
return 0; return 0;
efault_end:
user_access_end();
efault: efault:
buf->error = -EFAULT; buf->error = -EFAULT;
return -EFAULT; return -EFAULT;
...@@ -263,30 +320,31 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen, ...@@ -263,30 +320,31 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen,
if (reclen > buf->count) if (reclen > buf->count)
return -EINVAL; return -EINVAL;
dirent = buf->previous; dirent = buf->previous;
if (dirent) { if (dirent && signal_pending(current))
if (signal_pending(current))
return -EINTR; return -EINTR;
if (__put_user(offset, &dirent->d_off))
/*
* Note! This range-checks 'previous' (which may be NULL).
* The real range was checked in getdents
*/
if (!user_access_begin(dirent, sizeof(*dirent)))
goto efault; goto efault;
} if (dirent)
unsafe_put_user(offset, &dirent->d_off, efault_end);
dirent = buf->current_dir; dirent = buf->current_dir;
if (__put_user(ino, &dirent->d_ino)) unsafe_put_user(ino, &dirent->d_ino, efault_end);
goto efault; unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
if (__put_user(0, &dirent->d_off)) unsafe_put_user(d_type, &dirent->d_type, efault_end);
goto efault; unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
if (__put_user(reclen, &dirent->d_reclen)) user_access_end();
goto efault;
if (__put_user(d_type, &dirent->d_type))
goto efault;
if (copy_to_user(dirent->d_name, name, namlen))
goto efault;
if (__put_user(0, dirent->d_name + namlen))
goto efault;
buf->previous = dirent; buf->previous = dirent;
dirent = (void __user *)dirent + reclen; dirent = (void __user *)dirent + reclen;
buf->current_dir = dirent; buf->current_dir = dirent;
buf->count -= reclen; buf->count -= reclen;
return 0; return 0;
efault_end:
user_access_end();
efault: efault:
buf->error = -EFAULT; buf->error = -EFAULT;
return -EFAULT; return -EFAULT;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment