It appears sockaddr_in.sin_zero is not zeroed during certain operations 
returning IPv4 socket names, namely:

- getsockopt(...SO_ORIGINAL_DST...) (2.4 and 2.6)
  see getorigdst() in net/ipv4/netfilter/ip_conntrack_core.c
  (+ in net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c in 2.6?!)

- getsockname() and getpeername() (and accept()) (2.4 only)
  see inet_getname() in net/ipv4/af_inet.c

and several unitialized bytes of kernel stack (sizeof(sin_zero) == 6
to be precise) leak to the userspace.

This *might* have some security ramification.

The former problem can be found both in 2.4 and 2.6 (even the latest
releases according to a "visual check" of the source code in GIT), the
latter has been fixed in 2.6 but the fix has never made it back to 2.4.

A small patch for 2.4 fixing the problem is enclosed. Its first part
(fixing net/ipv4/af_inet.c) is identical to the change made in 2.6.
The other part should work on 2.6 as well but I am not sure whether one
should patch getorigdst() in net/ipv4/netfilter/ip_conntrack_core.c, in
net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c or in both files. I do
not want to teach you how to write your code, guys, but I think it's
the right time to clean it up. ;)

The presence of the bug can be checked with the enclosed test program
(bug.c). Run it with a single argument denoting the TCP port number
(e.g. "./bug 1234"), connect to that port, and the program will dump
sockaddr_in returned by accept() (== getpeername()), getsockname()
and getsockopt(...SO_ORIGINAL_DST...) respectively.

The results from a buggy 2.4 (all messed up):

data: 02 00 80 29 7f 00 00 01 34 7f d6 ca 41 7a 12 c0
data: 02 00 04 d2 7f 00 00 01 00 80 50 c9 36 00 00 00
data: 02 00 04 d2 7f 00 00 01 7f 00 00 01 80 29 06 00

The results from a buggy 2.6 (SO_ORIGINAL_DST messed up):

data: 02 00 ea 5e 7f 00 00 01 00 00 00 00 00 00 00 00
data: 02 00 04 d2 7f 00 00 01 00 00 00 00 00 00 00 00
data: 02 00 04 d2 7f 00 00 01 86 02 00 00 46 02 00 00

The results from a patched 2.4 (all clean):

data: 02 00 04 15 7f 00 00 01 00 00 00 00 00 00 00 00
data: 02 00 04 d2 7f 00 00 01 00 00 00 00 00 00 00 00
data: 02 00 04 d2 7f 00 00 01 00 00 00 00 00 00 00 00


--Pavel Kankovsky aka Peak  [ Boycott Microsoft--http://www.vcnet.com/bms ]
"Resistance is futile. Open your source code and prepare for assimilation."
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/netfilter_ipv4.h>

void
dump(const unsigned char *p, unsigned l)
{
  printf("data:");
  while (l > 0) {
    printf(" %02x", *p);
    ++p; --l;
  }
  printf("\n");
}

int
main(int argc, char **argv)
{
  int port;
  int ls, as, r, one;
  struct sockaddr_in sa;
  socklen_t sl;

  if (argc != 2 || (port = atoi(argv[1])) == 0) {
    fprintf(stderr, "usage: bug PORT\n");
    return (1);
  }

  ls = socket(PF_INET, SOCK_STREAM, 0);
  if (ls == -1) {
    perror("ls = socket");
    return (1);
  }
  one = 1;
  r = setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  if (r == -1) {
    perror("setsockopt(ls)");
    return (1);
  }
  sa.sin_family = PF_INET;
  sa.sin_addr.s_addr = INADDR_ANY;
  sa.sin_port = htons(port);
  r = bind(ls, (struct sockaddr *) &sa, sizeof(sa));
  if (r == -1) {
    perror("bind(ls)");
    return (1);
  }
  r = listen(ls, 1);
  if (r == -1) {
    perror("listen(ls)");
    return (1);
  }

  sl = sizeof(sa);
  as = accept(ls, (struct sockaddr *) &sa, &sl);
  if (as == -1) {
    perror("accept(ls)");
    return (1);
  }
  dump((unsigned char *) &sa, sizeof(sa));

  sl = sizeof(sa);
  r = getsockname(as, (struct sockaddr *) &sa, &sl);
  if (r == -1) {
    perror("getsockname(as)");
    return (1);
  }
  dump((unsigned char *) &sa, sizeof(sa));

  sl = sizeof(sa);
  r = getsockopt(as, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr *) &sa, &sl);
  if (r == -1) {
    perror("getsockname(as)");
    return (1);
  }
  dump((unsigned char *) &sa, sizeof(sa));

  return (0);
}

--- linux/net/ipv4/af_inet.c.sockname   Thu Feb 16 16:03:57 2006
+++ linux/net/ipv4/af_inet.c    Thu Feb 16 16:25:02 2006
@@ -724,6 +724,7 @@
                sin->sin_port = sk->sport;
                sin->sin_addr.s_addr = addr;
        }
+       memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
        *uaddr_len = sizeof(*sin);
        return(0);
 }
--- linux/net/ipv4/netfilter/ip_conntrack_core.c.sockname       Thu Feb 16 
16:03:58 2006
+++ linux/net/ipv4/netfilter/ip_conntrack_core.c        Thu Feb 16 16:26:13 2006
@@ -1341,6 +1341,7 @@
                        .tuple.dst.u.tcp.port;
                sin.sin_addr.s_addr = h->ctrack->tuplehash[IP_CT_DIR_ORIGINAL]
                        .tuple.dst.ip;
+               memset(sin.sin_zero, 0, sizeof(sin.sin_zero));
 
                DEBUGP("SO_ORIGINAL_DST: %u.%u.%u.%u %u\n",
                       NIPQUAD(sin.sin_addr.s_addr), ntohs(sin.sin_port));

Reply via email to