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