Skip to content

Commit 9f79b78

Browse files
committed
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: Linus Torvalds <[email protected]>
1 parent 4d856f7 commit 9f79b78

File tree

1 file changed

+93
-35
lines changed

1 file changed

+93
-35
lines changed

fs/readdir.c

Lines changed: 93 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,63 @@
2020
#include <linux/syscalls.h>
2121
#include <linux/unistd.h>
2222
#include <linux/compat.h>
23-
2423
#include <linux/uaccess.h>
2524

25+
#include <asm/unaligned.h>
26+
27+
/*
28+
* Note the "unsafe_put_user() semantics: we goto a
29+
* label for errors.
30+
*
31+
* Also note how we use a "while()" loop here, even though
32+
* only the biggest size needs to loop. The compiler (well,
33+
* at least gcc) is smart enough to turn the smaller sizes
34+
* into just if-statements, and this way we don't need to
35+
* care whether 'u64' or 'u32' is the biggest size.
36+
*/
37+
#define unsafe_copy_loop(dst, src, len, type, label) \
38+
while (len >= sizeof(type)) { \
39+
unsafe_put_user(get_unaligned((type *)src), \
40+
(type __user *)dst, label); \
41+
dst += sizeof(type); \
42+
src += sizeof(type); \
43+
len -= sizeof(type); \
44+
}
45+
46+
/*
47+
* We avoid doing 64-bit copies on 32-bit architectures. They
48+
* might be better, but the component names are mostly small,
49+
* and the 64-bit cases can end up being much more complex and
50+
* put much more register pressure on the code, so it's likely
51+
* not worth the pain of unaligned accesses etc.
52+
*
53+
* So limit the copies to "unsigned long" size. I did verify
54+
* that at least the x86-32 case is ok without this limiting,
55+
* but I worry about random other legacy 32-bit cases that
56+
* might not do as well.
57+
*/
58+
#define unsafe_copy_type(dst, src, len, type, label) do { \
59+
if (sizeof(type) <= sizeof(unsigned long)) \
60+
unsafe_copy_loop(dst, src, len, type, label); \
61+
} while (0)
62+
63+
/*
64+
* Copy the dirent name to user space, and NUL-terminate
65+
* it. This should not be a function call, since we're doing
66+
* the copy inside a "user_access_begin/end()" section.
67+
*/
68+
#define unsafe_copy_dirent_name(_dst, _src, _len, label) do { \
69+
char __user *dst = (_dst); \
70+
const char *src = (_src); \
71+
size_t len = (_len); \
72+
unsafe_copy_type(dst, src, len, u64, label); \
73+
unsafe_copy_type(dst, src, len, u32, label); \
74+
unsafe_copy_type(dst, src, len, u16, label); \
75+
unsafe_copy_type(dst, src, len, u8, label); \
76+
unsafe_put_user(0, dst, label); \
77+
} while (0)
78+
79+
2680
int iterate_dir(struct file *file, struct dir_context *ctx)
2781
{
2882
struct inode *inode = file_inode(file);
@@ -182,28 +236,31 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen,
182236
return -EOVERFLOW;
183237
}
184238
dirent = buf->previous;
185-
if (dirent) {
186-
if (signal_pending(current))
187-
return -EINTR;
188-
if (__put_user(offset, &dirent->d_off))
189-
goto efault;
190-
}
191-
dirent = buf->current_dir;
192-
if (__put_user(d_ino, &dirent->d_ino))
193-
goto efault;
194-
if (__put_user(reclen, &dirent->d_reclen))
195-
goto efault;
196-
if (copy_to_user(dirent->d_name, name, namlen))
197-
goto efault;
198-
if (__put_user(0, dirent->d_name + namlen))
199-
goto efault;
200-
if (__put_user(d_type, (char __user *) dirent + reclen - 1))
239+
if (dirent && signal_pending(current))
240+
return -EINTR;
241+
242+
/*
243+
* Note! This range-checks 'previous' (which may be NULL).
244+
* The real range was checked in getdents
245+
*/
246+
if (!user_access_begin(dirent, sizeof(*dirent)))
201247
goto efault;
248+
if (dirent)
249+
unsafe_put_user(offset, &dirent->d_off, efault_end);
250+
dirent = buf->current_dir;
251+
unsafe_put_user(d_ino, &dirent->d_ino, efault_end);
252+
unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
253+
unsafe_put_user(d_type, (char __user *) dirent + reclen - 1, efault_end);
254+
unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
255+
user_access_end();
256+
202257
buf->previous = dirent;
203258
dirent = (void __user *)dirent + reclen;
204259
buf->current_dir = dirent;
205260
buf->count -= reclen;
206261
return 0;
262+
efault_end:
263+
user_access_end();
207264
efault:
208265
buf->error = -EFAULT;
209266
return -EFAULT;
@@ -263,30 +320,31 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen,
263320
if (reclen > buf->count)
264321
return -EINVAL;
265322
dirent = buf->previous;
266-
if (dirent) {
267-
if (signal_pending(current))
268-
return -EINTR;
269-
if (__put_user(offset, &dirent->d_off))
270-
goto efault;
271-
}
272-
dirent = buf->current_dir;
273-
if (__put_user(ino, &dirent->d_ino))
274-
goto efault;
275-
if (__put_user(0, &dirent->d_off))
276-
goto efault;
277-
if (__put_user(reclen, &dirent->d_reclen))
278-
goto efault;
279-
if (__put_user(d_type, &dirent->d_type))
280-
goto efault;
281-
if (copy_to_user(dirent->d_name, name, namlen))
282-
goto efault;
283-
if (__put_user(0, dirent->d_name + namlen))
323+
if (dirent && signal_pending(current))
324+
return -EINTR;
325+
326+
/*
327+
* Note! This range-checks 'previous' (which may be NULL).
328+
* The real range was checked in getdents
329+
*/
330+
if (!user_access_begin(dirent, sizeof(*dirent)))
284331
goto efault;
332+
if (dirent)
333+
unsafe_put_user(offset, &dirent->d_off, efault_end);
334+
dirent = buf->current_dir;
335+
unsafe_put_user(ino, &dirent->d_ino, efault_end);
336+
unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
337+
unsafe_put_user(d_type, &dirent->d_type, efault_end);
338+
unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
339+
user_access_end();
340+
285341
buf->previous = dirent;
286342
dirent = (void __user *)dirent + reclen;
287343
buf->current_dir = dirent;
288344
buf->count -= reclen;
289345
return 0;
346+
efault_end:
347+
user_access_end();
290348
efault:
291349
buf->error = -EFAULT;
292350
return -EFAULT;

0 commit comments

Comments
 (0)