|
20 | 20 | #include <linux/syscalls.h>
|
21 | 21 | #include <linux/unistd.h>
|
22 | 22 | #include <linux/compat.h>
|
23 |
| - |
24 | 23 | #include <linux/uaccess.h>
|
25 | 24 |
|
| 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 | + |
26 | 80 | int iterate_dir(struct file *file, struct dir_context *ctx)
|
27 | 81 | {
|
28 | 82 | struct inode *inode = file_inode(file);
|
@@ -182,28 +236,31 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen,
|
182 | 236 | return -EOVERFLOW;
|
183 | 237 | }
|
184 | 238 | 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))) |
201 | 247 | 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 | + |
202 | 257 | buf->previous = dirent;
|
203 | 258 | dirent = (void __user *)dirent + reclen;
|
204 | 259 | buf->current_dir = dirent;
|
205 | 260 | buf->count -= reclen;
|
206 | 261 | return 0;
|
| 262 | +efault_end: |
| 263 | + user_access_end(); |
207 | 264 | efault:
|
208 | 265 | buf->error = -EFAULT;
|
209 | 266 | return -EFAULT;
|
@@ -263,30 +320,31 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen,
|
263 | 320 | if (reclen > buf->count)
|
264 | 321 | return -EINVAL;
|
265 | 322 | 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))) |
284 | 331 | 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 | + |
285 | 341 | buf->previous = dirent;
|
286 | 342 | dirent = (void __user *)dirent + reclen;
|
287 | 343 | buf->current_dir = dirent;
|
288 | 344 | buf->count -= reclen;
|
289 | 345 | return 0;
|
| 346 | +efault_end: |
| 347 | + user_access_end(); |
290 | 348 | efault:
|
291 | 349 | buf->error = -EFAULT;
|
292 | 350 | return -EFAULT;
|
|
0 commit comments