[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#164768: libc: IPv6 still not correct.



Package: libc6
Version: 2.2.5-11.2
Severity: important
Tags: patch

Hi,

This bug was in fact once #82468 (now archived), but having checked
the libc code (and retried the test case), it still exists (and so
really needs fixing.

Executive summary: libc uses an incorrectly-sized sockaddr_in6
structure, which causes programs running under 2.2 kernels to get
error messages when in fact they are behving correctly. Hence the
important severity.

A 2.2 kernel uses:
  struct sockaddr_in6 {
      unsigned short int  sin6_family;    /* AF_INET6 */
        __u16     sin6_port;      /* Transport layer port # */
          __u32     sin6_flowinfo;  /* IPv6 flow information */
            struct in6_addr   sin6_addr;      /* IPv6 address */
 };

While 2.4 (and glibc complies) use:
  struct sockaddr_in6 {
    unsigned short int  sin6_family;    /* AF_INET6 */
    __u16     sin6_port;      /* Transport layer port # */
    __u32     sin6_flowinfo;  /* IPv6 flow information */
    struct in6_addr   sin6_addr;      /* IPv6 address */
    __u32     sin6_scope_id;  /* scope id (new in RFC2553) */
  };


getpeername returns the kernel's idea of a sockaddr_in6 when using an
ipv6 socket.  Now getnameinfo has the following code 

  case AF_INET6:
        if (addrlen < sizeof (struct sockaddr_in6))
            return EAI_FAMILY;

in which the sizeof uses the libc idea of an sockaddr_in6...

Test code:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>

int
main(int argc, char **argv) {
  int sockfd;
  struct addrinfo req,*ans;
  struct sockaddr_storage tmp;
  int len = sizeof(struct sockaddr_storage);

  memset(&req,0,sizeof(req));
  req.ai_flags = 0;
  req.ai_family = PF_INET6;
  req.ai_socktype = SOCK_STREAM;
  req.ai_protocol =IPPROTO_TCP;
  if (getaddrinfo("spacelabs.nl","smtp",&req,&ans) != 0) {
  	printf("getaddrinfo failed\n");
  	return -1;
  }
  sockfd = socket(ans->ai_family,ans->ai_socktype,ans->ai_protocol);
  if (connect(sockfd,ans->ai_addr,ans->ai_addrlen) < 0) {
  	printf("connect failed\n");
  	return -1;
  }
  if (getpeername(sockfd,(struct sockaddr *) &tmp,&len) < 0) {
  	printf("getpeernamed failed\n");
  	return -1;
  }
  printf("getpeername len -> %d, sizeof(sockaddr_in6) -> %d\n",
      len, sizeof(struct sockaddr_in6));
  exit(0);
}

Running this on a current stable libc with 2.2 kernel gives:
  getpeername len -> 24, sizeof(sockaddr_in6) -> 28 
whilst with a 2.4 kernel, one gets:
  getpeername len -> 28, sizeof(sockaddr_in6) -> 28

Now this is a problem if you do, for example:
int
get_sock_port(int sock, int local)
{
        struct sockaddr_storage from;
        socklen_t fromlen;
        char strport[NI_MAXSERV];

        /* Get IP address of client. */
        fromlen = sizeof(from);
        memset(&from, 0, sizeof(from));
        if (local) {
                if (getsockname(sock, (struct sockaddr *)&from, &fromlen) < 0) {
                        error("getsockname failed: %.100s", strerror(errno));
                        return 0;
                }
        } else {
                if (getpeername(sock, (struct sockaddr *) & from, &fromlen) < 0)
 {
                        debug("getpeername failed: %.100s", strerror(errno));
                        fatal_cleanup();
                }
        }
        /* Return port number. */
        if (getnameinfo((struct sockaddr *)&from, fromlen, NULL, 0,
             strport, sizeof(strport), NI_NUMERICSERV) != 0)
                fatal("get_sock_port: getnameinfo NI_NUMERICSERV failed");
        return atoi(strport);
}

[code from SSH] getnameinfo barfs at this point on a 2.2 kernel.

Now, a fix to libc might be:
--- glibc-2.2.5/inet/getnameinfo.c	Tue Jan 30 00:23:05 2001
+++ glibc-2.2.5-fix/inet/getnameinfo.c	Sun Aug  4 01:15:42 2002
@@ -62,6 +62,13 @@
 # define min(x,y) (((x) > (y)) ? (y) : (x))
 #endif /* min */
 
+struct __sockaddr_in6_rfc2133
+  {
+      __SOCKADDR_COMMON (__sin6_);
+      in_port_t __sin6_port;
+      uint32_t __sin6_flowinfo;
+      struct in6_addr __sin6_addr;
+  };
 
 static char *
 internal_function
@@ -193,7 +200,7 @@
 	return EAI_FAMILY;
       break;
     case AF_INET6:
-      if (addrlen < sizeof (struct sockaddr_in6))
+      if (addrlen < sizeof (struct __sockaddr_in6_rfc2133))
 	return EAI_FAMILY;
       break;
     default:
@@ -298,7 +305,8 @@
 
 		    c = inet_ntop (AF_INET6,
 				   (const void *) &sin6p->sin6_addr, host, hostlen);
-		    scopeid = sin6p->sin6_scope_id;
+		    scopeid = (addrlen >= sizeof(struct sockaddr_in6)) ? 
+			      sin6p->sin6_scope_id : 0;
 		    if (scopeid != 0)
 		      {
 			/* Buffer is >= IFNAMSIZ+1.  */


For more information on this bug, please see
http://bugs.debian.org/82468

Thanks,

Matthew

-- 
Rapun.sel - outermost outpost of the Pick Empire
http://www.pick.ucam.org



Reply to: