[Patch] Minires update

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

[Patch] Minires update

Pierre A. Humblet-2
This patch syncs the built-in minires with the latest packaged version.
Also attaching the files to guarantee format preservation.

Pierre

2008-12-03  Pierre A. Humblet  <[hidden email]>

        * libc/minires.c (open_sock): Set non blocking and close on exec.
        (res_ninit): Set id pseudo-randomly.
        (res_nsend): Do not set close on exec. Initialize server from id.
        Flush socket. Tighten rules for answer acceptance.
        (res_nmkquery): Update id using current data.

Index: minires.c
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/libc/minires.c,v
retrieving revision 1.4
diff -u -p -r1.4 minires.c
--- minires.c 1 Apr 2008 10:22:33 -0000 1.4
+++ minires.c 3 Dec 2008 02:57:26 -0000
@@ -1,6 +1,6 @@
 /* minires.c.  Stub synchronous resolver for Cygwin.
 
-   Copyright 2006 Red Hat, Inc.
+   Copyright 2008 Red Hat, Inc.
 
    Written by Pierre A. Humblet <[hidden email]>
 
@@ -225,6 +225,17 @@ static int open_sock(struct sockaddr_in
     DPRINTF(debug, "socket(UDP): %s\n", strerror(errno));
     return -1;
   }
+  /* Set non-blocking */
+  if (fcntl64(fd, F_SETFL, O_NONBLOCK) < 0)  {
+    DPRINTF(debug, "fcntl: %s\n", strerror(errno));
+    return -1;
+  }
+  /* Set close on exec flag */
+  if (fcntl64(fd, F_SETFD, 1) == -1) {
+    DPRINTF(debug, "fcntl: %s\n", strerror(errno));
+    return -1;
+  }
+
   CliAddr->sin_family = AF_INET;
   CliAddr->sin_addr.s_addr = htonl(INADDR_ANY);
   CliAddr->sin_port = htons(0);
@@ -266,7 +277,10 @@ int res_ninit(res_state statp)
   statp->use_os = 1;            /* use os_query if available and allowed by get_resolv */
   statp->mypid = -1;
   statp->sockfd = -1;
-
+  /* Use the pid and the ppid for random seed, from the point of view of an outsider.
+     Mix the upper and lower bits as they are not used equally */
+  i = getpid();
+  statp->id = (ushort) (getppid() ^ (i << 8) ^ (i >> 8));
   for (i = 0; i < DIM(statp->dnsrch); i++)  statp->dnsrch[i] = 0;
 
   /* resolv.conf (dns servers & search list)*/
@@ -420,22 +434,15 @@ int res_nsend( res_state statp, const un
 
   /* Open a socket for this process */
   if (statp->sockfd == -1) {
-    /* Create a socket and bind it (to any port) */
+    /* Create a non-blocking, close on exec socket and bind it (to any port) */
     statp->sockfd = open_sock(& mySockAddr, debug);
     if (statp->sockfd  < 0 ) {
       statp->res_h_errno = NETDB_INTERNAL;
       return -1;
     }
-    /* Set close on exec flag */
-    if (fcntl64(statp->sockfd, F_SETFD, 1) == -1) {
-      DPRINTF(debug, "fcntl: %s\n",
-       strerror(errno));
-      statp->res_h_errno = NETDB_INTERNAL;
-      return -1;
-    }
     statp->mypid = getpid();
     if (SServ == 0XFFFFFFFF) /* Pseudo random */
-      SServ =  statp->mypid % statp->nscount;
+      SServ =  statp->id % statp->nscount;
   }
 
   transNum = 0;
@@ -443,6 +450,26 @@ int res_nsend( res_state statp, const un
     if ((wServ = SServ + 1) >= statp->nscount)
       wServ = 0;
     SServ = wServ;
+
+    /* There exists attacks on DNS where many wrong answers with guessed id's and
+       spoofed source address and port are generated at about the time when the
+       program is tricked into resolving a name.
+       This routine runs through the retry loop for each incorrect answer.
+       It is thus extremely likely that such attacks will cause a TRY_AGAIN return,
+       probably causing the calling program to retry after a delay.
+      
+       Note that valid late or duplicate answers to a previous questions also cause
+       a retry, although this is minimized by flushing the socket before sending the
+       new question.
+    */
+
+    /* Flush duplicate or late answers */
+    while ((rslt = cygwin_recvfrom( statp->sockfd, AnsPtr, AnsLength, 0, NULL, NULL)) >= 0) {
+      DPRINTF(debug, "Flushed %d bytes\n", rslt);
+    }
+    DPRINTF(debug && (errno != EWOULDBLOCK),
+     "Unexpected errno for flushing recvfrom: %s", strerror(errno));
+
     /* Send the message */
     rslt = cygwin_sendto(statp->sockfd, MsgPtr, MsgLength, 0,
     (struct sockaddr *) &statp->nsaddr_list[wServ],
@@ -481,59 +508,66 @@ int res_nsend( res_state statp, const un
       statp->res_h_errno = NETDB_INTERNAL;
       return -1;
     }
-    /*
-       Prepare to retry with tcp
+    DPRINTF(debug, "recvfrom: %d bytes from %08x\n", rslt, dnsSockAddr.sin_addr.s_addr);
+    /*
+       Prepare to retry with tcp
     */
     for (tcp = 0; tcp < 2; tcp++) {
-      /* Check if this is the message we expected */
-      if ((*MsgPtr == *AnsPtr)     /* Ids match */
-   && (*(MsgPtr + 1) == *(AnsPtr + 1))
-/* We have stopped checking this because the question may not be present on error,
-   in particular when the name in the question is not a valid name.
-   Simply check that the header is present. */
+      /* Check if this is the expected message from the expected server */
+      if ((memcmp(& dnsSockAddr, & statp->nsaddr_list[wServ],
+    (char *) & dnsSockAddr.sin_zero[0] - (char *) & dnsSockAddr) == 0)
    && (rslt >= HFIXEDSZ)
-/*        && (rslt >= MsgLength )
-   && (memcmp(MsgPtr + HFIXEDSZ, AnsPtr + HFIXEDSZ, MsgLength - HFIXEDSZ) == 0) */
-   && ((AnsPtr[2] & QR) != 0)) {
-
- DPRINTF(debug, "answer %u from %08x. Error %d. Count %d.\n",
-  rslt, dnsSockAddr.sin_addr.s_addr,
-  AnsPtr[3] & ERR_MASK, AnsPtr[6]*256 + AnsPtr[7]);
-#if 0
- NETDB_INTERNAL -1 /* see errno */
- NETDB_SUCCESS   0 /* no problem */
- HOST_NOT_FOUND  1 /* Authoritative Answer Host not found */
- TRY_AGAIN       2 /* Non-Authoritive Host not found, or SERVERFAIL */
-    Also seen returned by some servers when the name is too long
- NO_RECOVERY     3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
- NO_DATA         4 /* Valid name, no data record of requested type */
-#endif
+   && (*MsgPtr == *AnsPtr)     /* Ids match */
+   && (*(MsgPtr + 1) == *(AnsPtr + 1))
+   && ((AnsPtr[2] & QR) != 0)
+   && (AnsPtr[4] == 0)
+   /* We check the question if present.
+      Some servers don't return it on error, in particular
+      when the name in the question is not valid. */
+   && (((AnsPtr[5] == 0)
+        && ((AnsPtr[3] & ERR_MASK) != NOERROR))
+       || ((AnsPtr[5] == 1)
+    && (rslt >= MsgLength)
+    && (memcmp(MsgPtr + HFIXEDSZ, AnsPtr + HFIXEDSZ, MsgLength - HFIXEDSZ) == 0)))) {
  if ((AnsPtr[3] & ERR_MASK) == NOERROR) {
-   if ((AnsPtr[2] & TC) && !(statp->options & RES_IGNTC)) { /* Truncated. Try TCP */
+   if ((AnsPtr[2] & TC) && (tcp == 0) && !(statp->options & RES_IGNTC)) {
+     /* Truncated. Try TCP */
      rslt = get_tcp(&statp->nsaddr_list[wServ], MsgPtr, MsgLength,
       AnsPtr, AnsLength, statp->options & RES_DEBUG);
-     continue;
+     continue /* Tcp loop */;
    }
    else if ((AnsPtr[6] | AnsPtr[7])!= 0)
      return rslt;
    else
      statp->res_h_errno = NO_DATA;
  }
+#if 0
+ NETDB_INTERNAL -1 /* see errno */
+ NETDB_SUCCESS   0 /* no problem */
+ HOST_NOT_FOUND  1 /* Authoritative Answer Host not found */
+ TRY_AGAIN       2 /* Non-Authoritive Host not found, or SERVERFAIL */
+    Also seen returned by some servers when the name is too long
+ NO_RECOVERY     3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
+ NO_DATA         4 /* Valid name, no data record of requested type */
+#endif
  else {
+   switch (AnsPtr[3] & ERR_MASK) {
    /* return HOST_NOT_FOUND even for non-authoritative answers */
-   if ((AnsPtr[3] & ERR_MASK) == NXDOMAIN)
+   case NXDOMAIN:
+   case FORMERR:
      statp->res_h_errno = HOST_NOT_FOUND;
-   else if ((AnsPtr[3] & ERR_MASK) == SERVFAIL)
+     break;
+   case SERVFAIL:
      statp->res_h_errno = TRY_AGAIN;
-   else
+     break;
+   default:
      statp->res_h_errno = NO_RECOVERY;
+   }
  }
  return -1;
       }
       else {
- DPRINTF(debug, "unexpected answer %u from %x to query to %x\n",
-  rslt, dnsSockAddr.sin_addr.s_addr,
-  statp->nsaddr_list[wServ].sin_addr.s_addr);
+ DPRINTF(debug, "unexpected answer\n");
  break;
       }
     } /* TCP */
@@ -564,7 +598,8 @@ int res_nmkquery (res_state statp,
     const unsigned char * newrr, unsigned char * buf, int buflen)
 {
   int i, len;
-  short id;
+  const char * ptr;
+  unsigned int id4;
 
   if (op == QUERY) {
     /* Write the name and verify buffer length */
@@ -575,10 +610,10 @@ int res_nmkquery (res_state statp,
       statp->res_h_errno = NETDB_INTERNAL;
       return -1;
     }
+
     /* Fill the header */
-    id = statp->id;
-    PUTSHORT(id, buf);
-    PUTSHORT(RD, buf);
+    PUTSHORT(statp->id, buf);
+    PUTSHORT(RD, buf);
     PUTSHORT(1, buf); /* Number of questions */
     for (i = 0; i < 3; i++)
       PUTSHORT(0, buf); /* Number of answers */
@@ -587,7 +622,19 @@ int res_nmkquery (res_state statp,
     buf += len;
     PUTSHORT(qtype, buf);
     PUTSHORT(qclass, buf);
-    return len + 16; /* packet size */
+
+    /* Update id. The current query adds entropy to the next query id */
+    for (id4 = qtype, i = 0, ptr = dnameptr; *ptr; ptr++, i += 3)
+      id4 ^= *ptr << (i & 0xF);
+    i = 1 + statp->id % 15; /* Between 1 and 16 */
+    /* id dependent rotation, also brings MSW to LSW */
+    id4 = (id4 << i) ^ (id4 >> (16 - i)) ^ (id4 >> (32 - i));
+    if ((short) id4)
+      statp->id ^= (short) id4;
+    else
+      statp->id++; /* Force change */
+
+    return len + (HFIXEDSZ + QFIXEDSZ); /* packet size */
   }
   else { /* Not implemented */
     errno = ENOSYS;

minires.c.diff (9K) Download Attachment
ChangeLog.minires (345 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: [Patch] Minires update

Corinna Vinschen-2
On Dec  3 11:25, Pierre A. Humblet wrote:

> This patch syncs the built-in minires with the latest packaged version.
> Also attaching the files to guarantee format preservation.
>
> Pierre
>
> 2008-12-03  Pierre A. Humblet  <[hidden email]>
>
>         * libc/minires.c (open_sock): Set non blocking and close on exec.
>         (res_ninit): Set id pseudo-randomly.
>         (res_nsend): Do not set close on exec. Initialize server from id.
>         Flush socket. Tighten rules for answer acceptance.
>         (res_nmkquery): Update id using current data.

Applied with a very minor change:

> Index: minires.c
> ===================================================================
> RCS file: /cvs/src/src/winsup/cygwin/libc/minires.c,v
> retrieving revision 1.4
> diff -u -p -r1.4 minires.c
> --- minires.c 1 Apr 2008 10:22:33 -0000 1.4
> +++ minires.c 3 Dec 2008 02:57:26 -0000
> @@ -1,6 +1,6 @@
>  /* minires.c.  Stub synchronous resolver for Cygwin.
>  
> -   Copyright 2006 Red Hat, Inc.
> +   Copyright 2008 Red Hat, Inc.

The Copyright should keep the old dates, like this:

      Copyright 2006, 2008 Red Hat, Inc.


Thanks!
Corinna

--
Corinna Vinschen                  Please, send mails regarding Cygwin to
Cygwin Project Co-Leader          cygwin AT cygwin DOT com
Red Hat