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));