he@spurv.runit.sintef.no (Havard Eidnes) (02/04/91)
We have had some problems with "undesired information leakage" in BIND.4.8.3. Let me illustrate with the following situation: Two name servers: A and B, registered in unrelated domains Two domains: dom.A and dom.B dom.A authoritative copy on A, secondary on B dom.B authoritative copy on B, secondary on A You decide to change the IP address of name server A. You will probably have a hard time stomping out the RR for the old A information. The reason is that during a zone transfer, BIND insists on sending glue records (containing A information) even though the glue record is for a name outside of the domain the zone contains. It may fetch this old A record from anywhere, including from cached-up information (making the need for a secondary-NS-loop like the above unneccessary for this misfeature to bite). In order to get rid of this stale record in the domain name servers you will have to simultaneously do this on both name servers: stop name server remove secondary zone data (to force a reload) start name server For various reasons this may be hard to do. Instead, I have developed a fix to BIND version 4.8.3 so that BIND will only supply glue records that are truly needed (eg. the NS in question is located in a subdomain of the "current" domain name) when doing a zone transfer. The fix follows below, possibly with tabs converted to spaces. I have not checked with the RFCs whether this is allowed behaviour, but my experience with the DNS says this makes sense. Comments gladly accepted. - Havard ------------------------------ Fix to BIND 4.8.3. Apply with "patch -p0 -l" *** /tmp/,RCSt1a21126 Sun Feb 3 23:41:34 1991 --- ns_req.c Sat Feb 2 12:54:09 1991 *************** *** 991,994 **** --- 991,995 ---- char *fname; char dname[MAXDNAME]; + char gname[MAXDNAME]; HEADER *hp = (HEADER *) msg; int fndns; *************** *** 1037,1040 **** --- 1038,1052 ---- if (gnp == NULL || fname != dp->d_data) continue; + /* Do not send glue record unless we delegate to a */ + /* domain name in this domain. This is an attempt at */ + /* preventing old RR's to circulate between domain */ + /* name servers that provide secondary service for */ + /* each others zones. */ + getname(gnp, gname, sizeof(gname)); + n = strlen(gname) - strlen(dname); + if (n < 0) /* can not possibly be a subdomain */ + continue; + if (strcasecmp(dname, gname + n) != 0) + continue; for(gdp=gnp->n_data; gdp != NULL; gdp=gdp->d_next) { if (gdp->d_type != T_A || stale(gdp))
del@algol.mlb.semi.harris.com (Don Lewis) (02/05/91)
In article <1991Feb3.225247.14168@ugle.unit.no> he@idt.unit.no (Havard Eidnes) writes: >We have had some problems with "undesired information leakage" in >BIND.4.8.3. Let me illustrate with the following situation: > >Two name servers: A and B, registered in unrelated domains >Two domains: dom.A and dom.B > >dom.A authoritative copy on A, secondary on B >dom.B authoritative copy on B, secondary on A > >You decide to change the IP address of name server A. You will probably >have a hard time stomping out the RR for the old A information. The >reason is that during a zone transfer, BIND insists on sending glue >records (containing A information) even though the glue record is for a >name outside of the domain the zone contains. It may fetch this old A >record from anywhere, including from cached-up information (making the >need for a secondary-NS-loop like the above unneccessary for this >misfeature to bite). In order to get rid of this stale record in the >domain name servers you will have to simultaneously do this on both name >servers: > >stop name server >remove secondary zone data (to force a reload) >start name server > Another way is to: stop name server A update A's address in dom.A (and update serial number). remove glue record for A from dom.B's secondary zone file on A (leaving serial number alone) restart name server on A The next zone transfer of dom.A from A -> B will update A's address on B, and subsequent B -> A transfers of dom.B will include the correct address. >For various reasons this may be hard to do. Instead, I have developed a >fix to BIND version 4.8.3 so that BIND will only supply glue records >that are truly needed (eg. the NS in question is located in a subdomain >of the "current" domain name) when doing a zone transfer. The fix >follows below, possibly with tabs converted to spaces. I made a similar change a while ago. My patch is a bit fancier (and much more complicated). It has the advantage that it only includes the extra A records if they reside below an NS RR delegating authority to a subzone. *** ORIG/ns_req.c Wed Jun 27 18:39:46 1990 --- ns_req.c Fri Feb 1 23:01:23 1991 *************** *** 975,984 **** /* * Do a zone transfer. SOA record already sent. */ ! doaxfr(np, rfp, isroot) register struct namebuf *np; FILE *rfp; int isroot; { register struct databuf *dp; register int n; --- 976,986 ---- /* * Do a zone transfer. SOA record already sent. */ ! doaxfr(np, rfp, isroot, top) register struct namebuf *np; FILE *rfp; int isroot; + struct namebuf *top; { register struct databuf *dp; register int n; *************** *** 985,990 **** --- 987,994 ---- struct hashbuf *htp; struct databuf *gdp; /* glue databuf */ struct namebuf *gnp; /* glue namebuf */ + struct namebuf *tnp; + struct databuf *tdp; struct namebuf **npp, **nppend; char msg[PACKETSZ]; char *cp; *************** *** 1010,1060 **** cp = msg + sizeof(HEADER); getname(np, dname, sizeof(dname)); ! /* first do data records */ for (dp = np->n_data; dp != NULL; dp = dp->d_next) { /* * Skip the root SOA record (marks end of data); * don't send SOA for subdomains, as we're not sending them. */ ! if (dp->d_type == T_SOA) continue; - if (dp->d_type == T_NS) - fndns = 1; if (dp->d_zone == 0 || stale(dp)) continue; if ((n = make_rr(dname, dp, cp, sizeof(msg)-sizeof(HEADER), 0)) < 0) continue; fwritemsg(rfp, msg, n + sizeof(HEADER)); - - if (dp->d_type == T_NS) { - /* Glue the sub domains together by sending - * the address records for the sub domain - * name servers along. - */ - htp = hashtab; - cp = msg + sizeof(HEADER); - gnp = nlookup(dp->d_data, &htp, &fname, 0); - if (gnp == NULL || fname != dp->d_data) - continue; - for(gdp=gnp->n_data; gdp != NULL; gdp=gdp->d_next) { - if (gdp->d_type != T_A || stale(gdp)) - continue; - if ((n = make_rr(fname, gdp, cp, - sizeof(msg)-sizeof(HEADER), 0)) < 0) - continue; - fwritemsg(rfp, msg, n + sizeof(HEADER)); - } - } } ! /* next do subdomains, unless delegated */ ! if ((isroot == 0 && fndns) || np->n_hash == NULL) return; npp = np->n_hash->h_tab; nppend = npp + np->n_hash->h_size; while (npp < nppend) { for (np = *npp++; np != NULL; np = np->n_next) { ! doaxfr(np, rfp, 0); } } #ifdef DEBUG --- 1014,1090 ---- cp = msg + sizeof(HEADER); getname(np, dname, sizeof(dname)); ! /* first do the NS records */ for (dp = np->n_data; dp != NULL; dp = dp->d_next) { + if (dp->d_type == T_NS) { + fndns = 1; + if ((n = make_rr(dname, dp, cp, sizeof(msg)-sizeof(HEADER), 0)) < 0) + continue; + fwritemsg(rfp, msg, n + sizeof(HEADER)); + if (!isroot) { + /* Glue the sub domains together by sending + * the address records for the sub domain + * name servers along if they belong at + * or below the subdomain + */ + htp = hashtab; + cp = msg + sizeof(HEADER); + gnp = nlookup(dp->d_data, &htp, &fname, 0); + if (gnp == NULL || fname != dp->d_data) + continue; + for (tnp = gnp; tnp != NULL; tnp = tnp->n_parent) + if ( tnp == top ) + break; + if ( tnp == NULL ) + continue; /* name server is not in a (sub)domain */ + for (tnp = gnp; tnp != top; tnp = tnp->n_parent) { + for (tdp = tnp->n_data; tdp != NULL; tdp = tdp->d_next) + if (tdp->d_type == T_NS) + break; + if (tdp != NULL) + break; + } + if (tnp == top) + continue; /* name server is not in a subdomain */ + for(gdp=gnp->n_data; gdp != NULL; gdp=gdp->d_next) { + if (gdp->d_type != T_A || stale(gdp)) + continue; + if ((n = make_rr(fname, gdp, cp, + sizeof(msg)-sizeof(HEADER), 0)) < 0) + continue; + fwritemsg(rfp, msg, n + sizeof(HEADER)); + } + } + } + } + /* no need to send anything else because of delegation */ + if (!isroot && fndns) + return; + + /* do the rest of the data records */ + for (dp = np->n_data; dp != NULL; dp = dp->d_next) { /* * Skip the root SOA record (marks end of data); * don't send SOA for subdomains, as we're not sending them. + * Skip the NS record because we did it first */ ! if (dp->d_type == T_SOA || dp->d_type == T_NS) continue; if (dp->d_zone == 0 || stale(dp)) continue; if ((n = make_rr(dname, dp, cp, sizeof(msg)-sizeof(HEADER), 0)) < 0) continue; fwritemsg(rfp, msg, n + sizeof(HEADER)); } ! /* finally do subdomains */ ! if (np->n_hash == NULL) return; npp = np->n_hash->h_tab; nppend = npp + np->n_hash->h_size; while (npp < nppend) { for (np = *npp++; np != NULL; np = np->n_next) { ! doaxfr(np, rfp, 0, top); } } #ifdef DEBUG *************** *** 1327,1333 **** if (fdstat != -1) (void) fcntl(qsp->s_rfd, F_SETFL, fdstat & ~FNDELAY); fwritemsg(rfp, msg, msglen); ! doaxfr(np, rfp, 1); fwritemsg(rfp, msg, msglen); (void) fflush(rfp); exit(0); --- 1357,1363 ---- if (fdstat != -1) (void) fcntl(qsp->s_rfd, F_SETFL, fdstat & ~FNDELAY); fwritemsg(rfp, msg, msglen); ! doaxfr(np, rfp, 1, np); fwritemsg(rfp, msg, msglen); (void) fflush(rfp); exit(0); -- Don "Truck" Lewis Harris Semiconductor Internet: del@mlb.semi.harris.com PO Box 883 MS 62A-028 Phone: (407) 729-5205 Melbourne, FL 32901
asp@UUNET.UU.NET (Andrew Partan) (02/06/91)
I just fixed the other side of the problem - I hacked named-xfer to not accept any unneeded glue records in any incoming zone transfers. We were getting all sorts of bad addresses for our nameservers here - we even had an address of 85.85.85.85 (U.U.U.U) for uunet. My patch follows. --asp@uunet.uu.net (Andrew Partan) *** named-xfer.c.orig Wed Aug 15 12:14:48 1990 --- named-xfer.c Sun Feb 3 21:57:45 1991 *************** *** 883,888 **** --- 889,896 ---- u_char *temp_ptr; /* used to get ttl for RR */ char *cdata, *origin, *proto, dname[MAXDNAME]; extern char *inet_ntoa(), *protocolname(), *servicename(); + char * ignore = ""; + int lend, lenn; cp = rrp; if ((n = dn_expand(msg, msg + msglen, cp, (u_char *) dname, *************** *** 1016,1027 **** else got_soa++; } /* * If the origin has changed, print the new origin */ if (strcasecmp(prev_origin, origin)) { (void) strcpy(prev_origin, origin); ! (void) fprintf(dbfp, "$ORIGIN %s.\n", origin); } tab = 0; --- 1024,1068 ---- else got_soa++; } + /* + * If they are trying to tell us info about something that is + * not in the zone that we are transfering, then ignore it! + * They don't have the authority to tell us this info. + * + * We have to do a bit of checking here - the name that we are + * checking vs is fully qualified & may be in a subdomain of the + * zone in question. We also need to ignore any final dots. + */ + lend = strlen(domain); + if (domain[lend-1] == '.') + lend--; + lenn = strlen(dname); + if (dname[lenn-1] == '.') + lenn--; + if ((lend <= 0) || (lend > lenn) || strncasecmp(domain, &dname[lenn-lend], lend)) { + #ifdef not-yet + #ifdef DEBUG + if (debug) { + (void) fprintf(dbfp, "; Ignoring info about %s, not in zone %s.\n", + dname, domain); + ignore = "; "; + } else + #endif + return result; + #else + (void) fprintf(dbfp, "; Ignoring info about %s, not in zone %s.\n", + dname, domain); + ignore = "; "; + #endif + } + + /* * If the origin has changed, print the new origin */ if (strcasecmp(prev_origin, origin)) { (void) strcpy(prev_origin, origin); ! (void) fprintf(dbfp, "%s$ORIGIN %s.\n", ignore, origin); } tab = 0; *************** *** 1038,1052 **** if (dname[0] == 0) { if (origin[0] == 0) ! (void) fprintf(dbfp, ".\t"); else ! (void) fprintf(dbfp, ".%s.\t", origin); /* ??? */ } else ! (void) fprintf(dbfp, "%s\t", dname); if (strlen(dname) < 8) tab = 1; } else { ! (void) putc('\t', dbfp); tab = 1; } --- 1079,1094 ---- if (dname[0] == 0) { if (origin[0] == 0) ! (void) fprintf(dbfp, "%s.\t", ignore); else ! (void) fprintf(dbfp, "%s.%s.\t", ! ignore, origin); /* ??? */ } else ! (void) fprintf(dbfp, "%s%s\t", ignore, dname); if (strlen(dname) < 8) tab = 1; } else { ! (void) fprintf(dbfp, "%s\t", ignore); tab = 1; } *************** *** 1115,1121 **** (void) fprintf(dbfp, " %s. (\n", cp); cp += strlen((char *) cp) + 1; GETLONG(n, cp); ! (void) fprintf(dbfp, "\t\t%lu", n); GETLONG(n, cp); (void) fprintf(dbfp, " %lu", n); GETLONG(n, cp); --- 1157,1163 ---- (void) fprintf(dbfp, " %s. (\n", cp); cp += strlen((char *) cp) + 1; GETLONG(n, cp); ! (void) fprintf(dbfp, "%s\t\t%lu", ignore, n); GETLONG(n, cp); (void) fprintf(dbfp, " %lu", n); GETLONG(n, cp);
he@spurv.runit.sintef.no (Havard Eidnes) (02/07/91)
In article <1991Feb5.064846.5711@mlb.semi.harris.com>, del@algol.mlb.semi.harris.com (Don Lewis) writes: |> I made a similar change a while ago. My patch is a bit fancier |> (and much more complicated). It has the advantage that it only includes |> the extra A records if they reside below an NS RR delegating authority |> to a subzone. I think my fix also does that, because doaxfr is called recursively for all RRs in a zone (or did I read the code wrong?). Anyway, it appears to exhibit this desireable behaviour with my fix. - Havard
del@algol.mlb.semi.harris.com (Don Lewis) (02/08/91)
In article <1991Feb6.185422.8886@ugle.unit.no> he@idt.unit.no (Havard Eidnes) writes: >In article <1991Feb5.064846.5711@mlb.semi.harris.com>, del@algol.mlb.semi.harris.com (Don Lewis) writes: >|> I made a similar change a while ago. My patch is a bit fancier >|> (and much more complicated). It has the advantage that it only includes >|> the extra A records if they reside below an NS RR delegating authority >|> to a subzone. > >I think my fix also does that, because doaxfr is called recursively for all >RRs in a zone (or did I read the code wrong?). Anyway, it appears to >exhibit this desireable behaviour with my fix. Your fix only includes the A records if they only if they reside below the NS. It does not include the A records if they reside below another NS in the same zone. Example zone file for foo.com. ; Stock BIND and your version will include the A RR for hosta in the zone xfer ; (redundant) IN NS hosta ; Stock BIND may include the A RR for xyzzy.edu IN NS xyzzy.edu ; All versions include the A RR hosta hosta IN A x.x.x.x ; All versions include the A RR for host1.sub1 sub1 IN NS host1.sub1 ; Stock BIND and my version include the A RR for host3.sub2 (required glue) IN NS host3.sub2 ; Outside the zone, not included in the zone xfer host1.sub1 IN A x.x.x.x ; Outside the zone, not included in the zone xfer host3.sub2 IN A x.x.x.x ; Stock BIND and my version include the A RR for hosta (redundant) sub2 IN NS hosta ; Stock BIND and my version include the A RR for host1.sub1 (redundant) sub1 IN NS host1.sub1 ; All versions include the A RR for host2.sub2 IN NS host2.sub2 ; Outside the zone, not included in the zone xfer host2.sub2 IN A x.x.x.x The main problem with your version is that it does not transfer the A RR for host3.sub2. The only real defect in my version of the fix is the inclusion of a few redundant A RRs. Also, if the a host acts as server for both foo.com and sub1.foo.com, and there is an MX (or other such RR) for sub1.foo.com (which is part of the sub1.foo.com zone), stock BIND includes this RR in zone transfers of the foo.com zone. My patch takes care of this problem as well. -- Don "Truck" Lewis Harris Semiconductor Internet: del@mlb.semi.harris.com PO Box 883 MS 62A-028 Phone: (407) 729-5205 Melbourne, FL 32901