COMMAND

    kernel (knfsd)

SYSTEMS AFFECTED

    Linux

PROBLEM

    Chris Evans found following.  A DoS condition exists in the  Linux
    kernel knfsd  server.   Remote, unauthenticated  users (i.e. those
    with neither a directory mounted nor permission to mount one)  can
    OOPS the host  kernel.  The  OOPS does not  bring down the  target
    host, but  it is  possible to  render the  NFS service  inoperable
    until a reboot.

    This issue is  caused by signed/unsigned  issues. A size  check is
    performed on  an "int"  (i.e. signed)  variable.   This size check
    does  not  check  for  negative  values.   As  a consequence it is
    possible  to  point  an  internal  buffer pointer _way_ before the
    beginning of the  buffer.  The  kernel will try  to read a  memory
    address   in   limbo,   and   OOPS.    The   faulty   code  is  in
    kernel/net/sunrpc/svcauth.c, svcauth_unix()

        int slen;
        ...
                slen = ntohl(*bufp++);                  /* gids length */
                if (slen > 16 || (len -= slen + 2) < 0)
                        goto badcred;
        ...
                bufp += (slen - i);

    As  a  general  note,  signed/unsigned  errors  like the above are
    _almost_ as prevalent as buffer overflows. Once you start  looking
    for   them.    Re-auditing   previously   audited   software  with
    signed/unsigned issues in  mind often yields  new problems.   Note
    that  the  userland  sunrpc  implementation  suffered  from a very
    similar flaw a while   back. No, it certainly wasn't  a copy/paste
    error.

    Some  signed/unsigned  errors  can  lead to exploitable conditions
    rather  than  just  DoS.   The  following  code  fragment   should
    illustrate this:

        func()
        {
          char name[NAMELEN];
        
          int len = get_from_network();
          if (len > NAMELEN)
            goto i_dont_think_so;
        
          memcpy(name, get_from_network(), len); /* -1 implicitly converted to
                                                    4Gb */
        }
        
        An interesting variant on this (that HAS been observed) is
        
        func()
        {
          char* bufp;
          char* bufmax = bufp + 100;    /* Or whatever */
        
          unsigned int len = get_from_network();
          if (bufp + len > bufmax)
            goto i_dont_think_so;
          ...
          bufp += len;
        }

    On machines  with a  small 4Gb  address space,  e.g. Intel  x86, a
    suitable large  value of  len will  wrap bufp  around the  address
    space, leading to a position  outside of the buffer boundary,  but
    satisfying the "bufp + len <= bufmax" constraint.

    Programs  which  have  in  their  history  been  found  to include
    signed/unsigned issues include

        - knfsd
        - sunrpc part of glibc
        - xfs
        - libORBIT
        - issues at syscall API of various kernels
        - issues in networking stacks

SOLUTION

    The fix is present in the following kernels

        - 2.3.99pre7-pre1
        - 2.2.15pre20 (only available as a patch on top of 2.2.15pre19)
        - The kernel update recently release by RedHat

    Fix is also bwlow:

    --- net/sunrpc/svcauth.c.old	Tue Apr 18 05:13:47 2000
    +++ net/sunrpc/svcauth.c	Tue Apr 18 06:36:20 2000
    @@ -4,6 +4,9 @@
      * The generic interface for RPC authentication on the server side.
      *
      * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
    + *
    + * CHANGES
    + * 19-Apr-2000 Chris Evans      - Security fix
      */
    
     #include <linux/types.h>
    @@ -117,7 +120,8 @@
 	    struct svc_buf	*resp = &rqstp->rq_resbuf;
 	    struct svc_cred	*cred = &rqstp->rq_cred;
 	    u32		*bufp = argp->buf;
    -	int		len   = argp->len, slen, i;
    +	int		len   = argp->len;
    +	u32		slen, i;
    
 	    if ((len -= 3) < 0) {
 		    *statp = rpc_garbage_args;
    @@ -127,7 +131,7 @@
 	    bufp++;					/* length */
 	    bufp++;					/* time stamp */
 	    slen = (ntohl(*bufp++) + 3) >> 2;	/* machname length */
    -	if (slen > 64 || (len -= slen) < 0)
    +	if (slen > 64 || (len -= slen + 3) < 0)
 		    goto badcred;
 	    bufp += slen;				/* skip machname */