Skip site navigation (1)Skip section navigation (2)
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>