diff options
author | Andrew Morton <akpm@osdl.org> | 2005-08-09 18:29:19 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2005-08-09 18:29:19 -0400 |
commit | d64d3873721cfe870d49d73c3744f06260779ce7 (patch) | |
tree | b49a930e65ed4f30b4f8f2aac4ddb08c41bc4b79 /arch/sparc64 | |
parent | 3501466941347f0e1992b2672affb3feb92925fd (diff) |
[NET]: Fix memory leak in sys_{send,recv}msg() w/compat
From: Dave Johnson <djohnson+linux-kernel@sw.starentnetworks.com>
sendmsg()/recvmsg() syscalls from o32/n32 apps to a 64bit kernel will
cause a kernel memory leak if iov_len > UIO_FASTIOV for each syscall!
This is because both sys_sendmsg() and verify_compat_iovec() kmalloc a
new iovec structure. Only the one from sys_sendmsg() is free'ed.
I wrote a simple test program to confirm this after identifying the
problem:
http://davej.org/programs/testsendmsg.c
Note that the below fix will break solaris_sendmsg()/solaris_recvmsg() as
it also calls verify_compat_iovec() but expects it to malloc internally.
[ I fixed that. -DaveM ]
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'arch/sparc64')
-rw-r--r-- | arch/sparc64/solaris/socket.c | 193 |
1 files changed, 119 insertions, 74 deletions
diff --git a/arch/sparc64/solaris/socket.c b/arch/sparc64/solaris/socket.c index 06740582717e..d3a66ea74a7f 100644 --- a/arch/sparc64/solaris/socket.c +++ b/arch/sparc64/solaris/socket.c | |||
@@ -16,6 +16,7 @@ | |||
16 | #include <linux/net.h> | 16 | #include <linux/net.h> |
17 | #include <linux/compat.h> | 17 | #include <linux/compat.h> |
18 | #include <net/compat.h> | 18 | #include <net/compat.h> |
19 | #include <net/sock.h> | ||
19 | 20 | ||
20 | #include <asm/uaccess.h> | 21 | #include <asm/uaccess.h> |
21 | #include <asm/string.h> | 22 | #include <asm/string.h> |
@@ -297,121 +298,165 @@ asmlinkage int solaris_sendmsg(int fd, struct sol_nmsghdr __user *user_msg, unsi | |||
297 | { | 298 | { |
298 | struct socket *sock; | 299 | struct socket *sock; |
299 | char address[MAX_SOCK_ADDR]; | 300 | char address[MAX_SOCK_ADDR]; |
300 | struct iovec iov[UIO_FASTIOV]; | 301 | struct iovec iovstack[UIO_FASTIOV], *iov = iovstack; |
301 | unsigned char ctl[sizeof(struct cmsghdr) + 20]; | 302 | unsigned char ctl[sizeof(struct cmsghdr) + 20]; |
302 | unsigned char *ctl_buf = ctl; | 303 | unsigned char *ctl_buf = ctl; |
303 | struct msghdr kern_msg; | 304 | struct msghdr msg_sys; |
304 | int err, total_len; | 305 | int err, ctl_len, iov_size, total_len; |
305 | 306 | ||
306 | if(msghdr_from_user32_to_kern(&kern_msg, user_msg)) | 307 | err = -EFAULT; |
307 | return -EFAULT; | 308 | if (msghdr_from_user32_to_kern(&msg_sys, user_msg)) |
308 | if(kern_msg.msg_iovlen > UIO_MAXIOV) | 309 | goto out; |
309 | return -EINVAL; | 310 | |
310 | err = verify_compat_iovec(&kern_msg, iov, address, VERIFY_READ); | 311 | sock = sockfd_lookup(fd, &err); |
311 | if (err < 0) | 312 | if (!sock) |
312 | goto out; | 313 | goto out; |
314 | |||
315 | /* do not move before msg_sys is valid */ | ||
316 | err = -EMSGSIZE; | ||
317 | if (msg_sys.msg_iovlen > UIO_MAXIOV) | ||
318 | goto out_put; | ||
319 | |||
320 | /* Check whether to allocate the iovec area*/ | ||
321 | err = -ENOMEM; | ||
322 | iov_size = msg_sys.msg_iovlen * sizeof(struct iovec); | ||
323 | if (msg_sys.msg_iovlen > UIO_FASTIOV) { | ||
324 | iov = sock_kmalloc(sock->sk, iov_size, GFP_KERNEL); | ||
325 | if (!iov) | ||
326 | goto out_put; | ||
327 | } | ||
328 | |||
329 | err = verify_compat_iovec(&msg_sys, iov, address, VERIFY_READ); | ||
330 | if (err < 0) | ||
331 | goto out_freeiov; | ||
313 | total_len = err; | 332 | total_len = err; |
314 | 333 | ||
315 | if(kern_msg.msg_controllen) { | 334 | err = -ENOBUFS; |
316 | struct sol_cmsghdr __user *ucmsg = kern_msg.msg_control; | 335 | if (msg_sys.msg_controllen > INT_MAX) |
336 | goto out_freeiov; | ||
337 | |||
338 | ctl_len = msg_sys.msg_controllen; | ||
339 | if (ctl_len) { | ||
340 | struct sol_cmsghdr __user *ucmsg = msg_sys.msg_control; | ||
317 | unsigned long *kcmsg; | 341 | unsigned long *kcmsg; |
318 | compat_size_t cmlen; | 342 | compat_size_t cmlen; |
319 | 343 | ||
320 | if (kern_msg.msg_controllen <= sizeof(compat_size_t)) | 344 | err = -EINVAL; |
321 | return -EINVAL; | 345 | if (ctl_len <= sizeof(compat_size_t)) |
346 | goto out_freeiov; | ||
322 | 347 | ||
323 | if(kern_msg.msg_controllen > sizeof(ctl)) { | 348 | if (ctl_len > sizeof(ctl)) { |
324 | err = -ENOBUFS; | 349 | err = -ENOBUFS; |
325 | ctl_buf = kmalloc(kern_msg.msg_controllen, GFP_KERNEL); | 350 | ctl_buf = kmalloc(ctl_len, GFP_KERNEL); |
326 | if(!ctl_buf) | 351 | if (!ctl_buf) |
327 | goto out_freeiov; | 352 | goto out_freeiov; |
328 | } | 353 | } |
329 | __get_user(cmlen, &ucmsg->cmsg_len); | 354 | __get_user(cmlen, &ucmsg->cmsg_len); |
330 | kcmsg = (unsigned long *) ctl_buf; | 355 | kcmsg = (unsigned long *) ctl_buf; |
331 | *kcmsg++ = (unsigned long)cmlen; | 356 | *kcmsg++ = (unsigned long)cmlen; |
332 | err = -EFAULT; | 357 | err = -EFAULT; |
333 | if(copy_from_user(kcmsg, &ucmsg->cmsg_level, | 358 | if (copy_from_user(kcmsg, &ucmsg->cmsg_level, |
334 | kern_msg.msg_controllen - sizeof(compat_size_t))) | 359 | ctl_len - sizeof(compat_size_t))) |
335 | goto out_freectl; | 360 | goto out_freectl; |
336 | kern_msg.msg_control = ctl_buf; | 361 | msg_sys.msg_control = ctl_buf; |
337 | } | 362 | } |
338 | kern_msg.msg_flags = solaris_to_linux_msgflags(user_flags); | 363 | msg_sys.msg_flags = solaris_to_linux_msgflags(user_flags); |
339 | 364 | ||
340 | lock_kernel(); | 365 | if (sock->file->f_flags & O_NONBLOCK) |
341 | sock = sockfd_lookup(fd, &err); | 366 | msg_sys.msg_flags |= MSG_DONTWAIT; |
342 | if (sock != NULL) { | 367 | err = sock_sendmsg(sock, &msg_sys, total_len); |
343 | if (sock->file->f_flags & O_NONBLOCK) | ||
344 | kern_msg.msg_flags |= MSG_DONTWAIT; | ||
345 | err = sock_sendmsg(sock, &kern_msg, total_len); | ||
346 | sockfd_put(sock); | ||
347 | } | ||
348 | unlock_kernel(); | ||
349 | 368 | ||
350 | out_freectl: | 369 | out_freectl: |
351 | /* N.B. Use kfree here, as kern_msg.msg_controllen might change? */ | 370 | if (ctl_buf != ctl) |
352 | if(ctl_buf != ctl) | 371 | sock_kfree_s(sock->sk, ctl_buf, ctl_len); |
353 | kfree(ctl_buf); | ||
354 | out_freeiov: | 372 | out_freeiov: |
355 | if(kern_msg.msg_iov != iov) | 373 | if (iov != iovstack) |
356 | kfree(kern_msg.msg_iov); | 374 | sock_kfree_s(sock->sk, iov, iov_size); |
357 | out: | 375 | out_put: |
376 | sockfd_put(sock); | ||
377 | out: | ||
358 | return err; | 378 | return err; |
359 | } | 379 | } |
360 | 380 | ||
361 | asmlinkage int solaris_recvmsg(int fd, struct sol_nmsghdr __user *user_msg, unsigned int user_flags) | 381 | asmlinkage int solaris_recvmsg(int fd, struct sol_nmsghdr __user *user_msg, unsigned int user_flags) |
362 | { | 382 | { |
363 | struct iovec iovstack[UIO_FASTIOV]; | ||
364 | struct msghdr kern_msg; | ||
365 | char addr[MAX_SOCK_ADDR]; | ||
366 | struct socket *sock; | 383 | struct socket *sock; |
384 | struct iovec iovstack[UIO_FASTIOV]; | ||
367 | struct iovec *iov = iovstack; | 385 | struct iovec *iov = iovstack; |
386 | struct msghdr msg_sys; | ||
387 | unsigned long cmsg_ptr; | ||
388 | int err, iov_size, total_len, len; | ||
389 | |||
390 | /* kernel mode address */ | ||
391 | char addr[MAX_SOCK_ADDR]; | ||
392 | |||
393 | /* user mode address pointers */ | ||
368 | struct sockaddr __user *uaddr; | 394 | struct sockaddr __user *uaddr; |
369 | int __user *uaddr_len; | 395 | int __user *uaddr_len; |
370 | unsigned long cmsg_ptr; | ||
371 | int err, total_len, len = 0; | ||
372 | 396 | ||
373 | if(msghdr_from_user32_to_kern(&kern_msg, user_msg)) | 397 | if (msghdr_from_user32_to_kern(&msg_sys, user_msg)) |
374 | return -EFAULT; | 398 | return -EFAULT; |
375 | if(kern_msg.msg_iovlen > UIO_MAXIOV) | ||
376 | return -EINVAL; | ||
377 | 399 | ||
378 | uaddr = kern_msg.msg_name; | 400 | sock = sockfd_lookup(fd, &err); |
401 | if (!sock) | ||
402 | goto out; | ||
403 | |||
404 | err = -EMSGSIZE; | ||
405 | if (msg_sys.msg_iovlen > UIO_MAXIOV) | ||
406 | goto out_put; | ||
407 | |||
408 | /* Check whether to allocate the iovec area*/ | ||
409 | err = -ENOMEM; | ||
410 | iov_size = msg_sys.msg_iovlen * sizeof(struct iovec); | ||
411 | if (msg_sys.msg_iovlen > UIO_FASTIOV) { | ||
412 | iov = sock_kmalloc(sock->sk, iov_size, GFP_KERNEL); | ||
413 | if (!iov) | ||
414 | goto out_put; | ||
415 | } | ||
416 | |||
417 | /* | ||
418 | * Save the user-mode address (verify_iovec will change the | ||
419 | * kernel msghdr to use the kernel address space) | ||
420 | */ | ||
421 | |||
422 | uaddr = (void __user *) msg_sys.msg_name; | ||
379 | uaddr_len = &user_msg->msg_namelen; | 423 | uaddr_len = &user_msg->msg_namelen; |
380 | err = verify_compat_iovec(&kern_msg, iov, addr, VERIFY_WRITE); | 424 | err = verify_compat_iovec(&msg_sys, iov, addr, VERIFY_WRITE); |
381 | if (err < 0) | 425 | if (err < 0) |
382 | goto out; | 426 | goto out_freeiov; |
383 | total_len = err; | 427 | total_len = err; |
384 | 428 | ||
385 | cmsg_ptr = (unsigned long) kern_msg.msg_control; | 429 | cmsg_ptr = (unsigned long) msg_sys.msg_control; |
386 | kern_msg.msg_flags = 0; | 430 | msg_sys.msg_flags = MSG_CMSG_COMPAT; |
387 | 431 | ||
388 | lock_kernel(); | 432 | if (sock->file->f_flags & O_NONBLOCK) |
389 | sock = sockfd_lookup(fd, &err); | 433 | user_flags |= MSG_DONTWAIT; |
390 | if (sock != NULL) { | 434 | |
391 | if (sock->file->f_flags & O_NONBLOCK) | 435 | err = sock_recvmsg(sock, &msg_sys, total_len, user_flags); |
392 | user_flags |= MSG_DONTWAIT; | 436 | if(err < 0) |
393 | err = sock_recvmsg(sock, &kern_msg, total_len, user_flags); | 437 | goto out_freeiov; |
394 | if(err >= 0) | 438 | |
395 | len = err; | 439 | len = err; |
396 | sockfd_put(sock); | 440 | |
397 | } | 441 | if (uaddr != NULL) { |
398 | unlock_kernel(); | 442 | err = move_addr_to_user(addr, msg_sys.msg_namelen, uaddr, uaddr_len); |
399 | 443 | if (err < 0) | |
400 | if(uaddr != NULL && err >= 0) | 444 | goto out_freeiov; |
401 | err = move_addr_to_user(addr, kern_msg.msg_namelen, uaddr, uaddr_len); | ||
402 | if(err >= 0) { | ||
403 | err = __put_user(linux_to_solaris_msgflags(kern_msg.msg_flags), &user_msg->msg_flags); | ||
404 | if(!err) { | ||
405 | /* XXX Convert cmsg back into userspace 32-bit format... */ | ||
406 | err = __put_user((unsigned long)kern_msg.msg_control - cmsg_ptr, | ||
407 | &user_msg->msg_controllen); | ||
408 | } | ||
409 | } | 445 | } |
446 | err = __put_user(linux_to_solaris_msgflags(msg_sys.msg_flags), &user_msg->msg_flags); | ||
447 | if (err) | ||
448 | goto out_freeiov; | ||
449 | err = __put_user((unsigned long)msg_sys.msg_control - cmsg_ptr, | ||
450 | &user_msg->msg_controllen); | ||
451 | if (err) | ||
452 | goto out_freeiov; | ||
453 | err = len; | ||
410 | 454 | ||
411 | if(kern_msg.msg_iov != iov) | 455 | out_freeiov: |
412 | kfree(kern_msg.msg_iov); | 456 | if (iov != iovstack) |
457 | sock_kfree_s(sock->sk, iov, iov_size); | ||
458 | out_put: | ||
459 | sockfd_put(sock); | ||
413 | out: | 460 | out: |
414 | if(err < 0) | 461 | return err; |
415 | return err; | ||
416 | return len; | ||
417 | } | 462 | } |