* 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.
This commit is contained in:
Corinna Vinschen 2008-12-03 16:37:53 +00:00
parent 5c1cf8812f
commit e1984134be
2 changed files with 103 additions and 48 deletions

View File

@ -1,3 +1,11 @@
2008-12-03 Pierre A. Humblet <Pierre.Humblet@ieee.org>
* 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.
2008-12-02 Corinna Vinschen <corinna@vinschen.de>
* fhandler_registry.cc (must_encode): Fix condition changed for testing.

View File

@ -1,6 +1,6 @@
/* minires.c. Stub synchronous resolver for Cygwin.
Copyright 2006 Red Hat, Inc.
Copyright 2006, 2008 Red Hat, Inc.
Written by Pierre A. Humblet <Pierre.Humblet@ieee.org>
@ -225,6 +225,17 @@ static int open_sock(struct sockaddr_in *CliAddr, int debug)
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 unsigned char * MsgPtr,
/* 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 unsigned char * MsgPtr,
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,24 +508,39 @@ int res_nsend( res_state statp, const unsigned char * MsgPtr,
statp->res_h_errno = NETDB_INTERNAL;
return -1;
}
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]);
&& (*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) && (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 /* 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 */
@ -508,32 +550,24 @@ int res_nsend( res_state statp, const unsigned char * MsgPtr,
NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
NO_DATA 4 /* Valid name, no data record of requested type */
#endif
if ((AnsPtr[3] & ERR_MASK) == NOERROR) {
if ((AnsPtr[2] & TC) && !(statp->options & RES_IGNTC)) { /* Truncated. Try TCP */
rslt = get_tcp(&statp->nsaddr_list[wServ], MsgPtr, MsgLength,
AnsPtr, AnsLength, statp->options & RES_DEBUG);
continue;
}
else if ((AnsPtr[6] | AnsPtr[7])!= 0)
return rslt;
else
statp->res_h_errno = NO_DATA;
}
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,9 +610,9 @@ int res_nmkquery (res_state statp,
statp->res_h_errno = NETDB_INTERNAL;
return -1;
}
/* Fill the header */
id = statp->id;
PUTSHORT(id, buf);
PUTSHORT(statp->id, buf);
PUTSHORT(RD, buf);
PUTSHORT(1, buf); /* Number of questions */
for (i = 0; i < 3; i++)
@ -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;