Date: Mon, 12 Jun 2006 14:22:20 GMT From: James Juran <James.Juran@baesystems.com> To: freebsd-gnats-submit@FreeBSD.org Subject: kern/98858: Local denial of service (mbuf exhaustion) via setsockopt() Message-ID: <200606121422.k5CEMKFa004711@www.freebsd.org> Resent-Message-ID: <200606121430.k5CEUIjX016815@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
>Number: 98858 >Category: kern >Synopsis: Local denial of service (mbuf exhaustion) via setsockopt() >Confidential: no >Severity: serious >Priority: medium >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Mon Jun 12 14:30:18 GMT 2006 >Closed-Date: >Last-Modified: >Originator: James Juran >Release: 6.0-RELEASE >Organization: BAE Systems Inc. >Environment: FreeBSD bsdtest 6.0-RELEASE FreeBSD 6.0-RELEASE #7: Fri Jun 9 03:46:52 EDT 2006 root@bsdtest:/usr/src/sys/i386/compile/TEST >Description: Impact ------ A non-root user can consume virtually all available mbufs, even if the "sbsize" resource limit is set to a low value. This results in a local denial-of-service, because other users then may not be able to perform network operations that require mbuf allocation. [The one thing I'm not sure of here is whether the UMA will be able to reclaim memory from other areas of the system to use for mbufs. If it does this and this makes this a non-issue, please let me know]. Versions Affected ----------------- All supported versions of FreeBSD. Tested on 6.0-RELEASE. Problem ------- kern_setsockopt() tries to check that the valsize parameter is non-negative, but because valsize is unsigned this check always fails. Thus sopt.sopt_valsize gets set to whatever the user passed in. In soopt_getm(), sopt->sopt_valsize is assigned to the int variable sopt_size. min() promotes both arguments to unsigned, so m->m_len will be positive. Thus the already-negative sopt_size will have a postive value subtracted from it, making it more negative. This happens each time around the while loop, with sopt_size growing more and more negative. An mbuf is allocated for each time through the loop, and we eventually run out of mbufs and block in MGET(). soopt_getm() can be reached via ip6_ctloutput()'s processing of the IPV6_2292PKTOPTIONS option. There is no define for this constant in the headers available to applications, but an application can use the hardcoded value of 25. PR filed per request of gnn. >How-To-Repeat: Compile and run this code as an ordinary user: #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> int main(void) { int s; char opts[255]; s = socket(AF_INET6, SOCK_STREAM, 0); if (s == -1) { perror("socket"); exit(1); } if (setsockopt(s, IPPROTO_IPV6, 25, &opts, -1) == -1) { perror("setsockopt"); exit(1); } return 0; } After running this program, netstat -m will report: 17026/134/17160 mbufs in use (current/cache/total) 17024/64/17088/17088 mbuf clusters in use (current/cache/total/max) 0/2/4528 sfbufs in use (current/peak/max) 38304K/161K/38466K bytes allocated to network (current/cache/total) 0 requests for sfbufs denied 0 requests for sfbufs delayed 0 requests for I/O initiated by sendfile 0 calls to protocol drain routines and the process running the above program will be in state D+ according to ps. After switching to another shell as another user, some network operations such as connection attempts) block, presumably waiting to obtain an mbuf. >Fix: This patch makes the code do what it was indended to do (that is, prevents a negative value from contining past the initial check): --- uipc_syscalls.c.orig Fri Jun 9 03:44:57 2006 +++ uipc_syscalls.c Fri Jun 9 03:45:29 2006 @@ -1305,7 +1305,7 @@ if (val == NULL && valsize != 0) return (EFAULT); - if (valsize < 0) + if ((int)valsize < 0) return (EINVAL); sopt.sopt_dir = SOPT_SET; @@ -1388,7 +1388,7 @@ if (val == NULL) *valsize = 0; - if (*valsize < 0) + if ((int)*valsize < 0) return (EINVAL); sopt.sopt_dir = SOPT_GET; I have tested that this makes the program above return EINVAL on the setsockopt call and that no mbufs are allocated. An alternative fix would be to make soopt_getm() and other functions use "size_t" for sopt_size, and possibly to make m->mlen be unsigned as well. This would be a signficantly more invasive change though. >Release-Note: >Audit-Trail: >Unformatted:
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200606121422.k5CEMKFa004711>