diff -u pdns-recursor-3.1.7/debian/changelog pdns-recursor-3.1.7/debian/changelog --- pdns-recursor-3.1.7/debian/changelog +++ pdns-recursor-3.1.7/debian/changelog @@ -1,3 +1,14 @@ +pdns-recursor (3.1.7-1ubuntu0.1) intrepid-security; urgency=low + + * SECURITY UPDATE: first issue is remotely exploitable, likely + leads to full compromise; second issue allows an attacker to + spoof domain data for domain names he does not own (LP: #502987) + - debian/patches/CVE-2009-4009-4010.dpatch: fixes the two + problems + - CVE-2009-4009, CVE-2009-4010 + + -- Imre Gergely Thu, 07 Jan 2010 01:35:01 +0200 + pdns-recursor (3.1.7-1) unstable; urgency=low * New upstream version (Closes: #490069) (Closes: #477130) diff -u pdns-recursor-3.1.7/debian/patches/00list pdns-recursor-3.1.7/debian/patches/00list --- pdns-recursor-3.1.7/debian/patches/00list +++ pdns-recursor-3.1.7/debian/patches/00list @@ -3,0 +4 @@ +CVE-2009-4009-40010 only in patch2: unchanged: --- pdns-recursor-3.1.7.orig/debian/patches/CVE-2009-4009-40010.dpatch +++ pdns-recursor-3.1.7/debian/patches/CVE-2009-4009-40010.dpatch @@ -0,0 +1,471 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## CVE-2009-4009-40010.dpatch by Imre Gergely +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: security patch for undisclosed exploits (CVE-2009-4009, CVE-2009-4010) +## DP: http://mailman.powerdns.com/pipermail/pdns-users/2010-January/006410.html +## DP: closes LP: #502987 + +@DPATCH@ +diff -urNad pdns-recursor-3.1.7~/dnsparser.cc pdns-recursor-3.1.7/dnsparser.cc +--- pdns-recursor-3.1.7~/dnsparser.cc 2010-01-07 00:11:58.000000000 +0200 ++++ pdns-recursor-3.1.7/dnsparser.cc 2010-01-07 00:20:14.000000000 +0200 +@@ -75,20 +75,38 @@ + vector d_record; + }; + +-static const string EncodeDNSLabel(const string& input) ++static const string EncodeDNSLabel(const string& input) + { +- typedef vector parts_t; +- parts_t parts; +- stringtok(parts,input,"."); +- string ret; +- for(parts_t::const_iterator i=parts.begin(); i!=parts.end(); ++i) { +- ret.append(1,(char)i->length()); +- ret.append(*i); +- } +- ret.append(1,(char)0); +- return ret; ++ if(input.length() == 1 && input[0]=='.') // otherwise we encode .. (long story) ++ return string (1, 0); ++ ++ labelparts_t parts; ++ bool unescapedSomething = labeltokUnescape(parts, input); ++ string ret; ++ ++ if(!unescapedSomething) { ++ for(labelparts_t::const_iterator i=parts.begin(); i!=parts.end(); ++i) { ++ ret.append(1, i->second - i->first); ++ ret.append(input.c_str() + i->first, i->second - i->first); ++ } ++ ++ } else { ++ for(labelparts_t::const_iterator i=parts.begin(); i!=parts.end(); ++i) { ++ string part(input.c_str() + i->first, i->second - i->first); ++ boost::replace_all(part, "\\\\", "\\"); ++ boost::replace_all(part, "\\.", "."); ++ ++ ret.append(1, part.length()); ++ ret.append(part); ++ } ++ } ++ ret.append(1, 0); ++ // cerr<<"Asked to encode '"< DNSRecordContent::unserialize(const string& qname, uint16_t qtype, const string& serialized) + { + dnsheader dnsheader; +@@ -437,7 +455,7 @@ + // XXX FIXME THIS MIGHT BE VERY SLOW! + ret.reserve(ret.size() + labellen + 2); + for(string::size_type n = 0 ; n < labellen; ++n, frompos++) { +- if(content.at(frompos)=='.') ++ if(content.at(frompos)=='.' || content.at(frompos)=='\\') + ret.append(1, '\\'); + ret.append(1, content[frompos]); + } +diff -urNad pdns-recursor-3.1.7~/dnsparser.hh pdns-recursor-3.1.7/dnsparser.hh +--- pdns-recursor-3.1.7~/dnsparser.hh 2008-07-09 22:42:48.000000000 +0300 ++++ pdns-recursor-3.1.7/dnsparser.hh 2010-01-07 00:20:14.000000000 +0200 +@@ -194,7 +194,7 @@ + static uint16_t TypeToNumber(const string& name) + { + for(namemap_t::const_iterator i=getNamemap().begin(); i!=getNamemap().end();++i) +- if(!Utility::strcasecmp(i->second.c_str(), name.c_str())) ++ if(pdns_iequals(i->second,name)) + return i->first.second; + + throw runtime_error("Unknown DNS type '"+name+"'"); +diff -urNad pdns-recursor-3.1.7~/dnswriter.cc pdns-recursor-3.1.7/dnswriter.cc +--- pdns-recursor-3.1.7~/dnswriter.cc 2008-07-09 22:42:48.000000000 +0300 ++++ pdns-recursor-3.1.7/dnswriter.cc 2010-01-07 00:20:14.000000000 +0200 +@@ -67,7 +67,7 @@ + d_stuff = 0; + d_rollbackmarker=d_content.size(); + +- if(iequals(d_qname,d_recordqname)) { // don't do the whole label compression thing if we *know* we can get away with "see question" ++ if(pdns_iequals(d_qname,d_recordqname)) { // don't do the whole label compression thing if we *know* we can get away with "see question" + static char marker[2]={0xc0, 0x0c}; + d_content.insert(d_content.end(), &marker[0], &marker[2]); + } +@@ -153,14 +153,12 @@ + { + DNSPacketWriter::lmap_t::iterator ret; + for(ret=lmap.begin(); ret != lmap.end(); ++ret) +- if(iequals(ret->first,label)) ++ if(pdns_iequals(ret->first,label)) + break; + return ret; + } + +-typedef vector > parts_t; +- +-bool labeltokUnescape(parts_t& parts, const string& label) ++bool labeltokUnescape(labelparts_t& parts, const string& label) + { + string::size_type epos = label.size(), lpos(0), pos; + bool unescapedSomething = false; +@@ -188,7 +186,7 @@ + // this is the absolute hottest function in the pdns recursor + void DNSPacketWriter::xfrLabel(const string& label, bool compress) + { +- parts_t parts; ++ labelparts_t parts; + + if(label.size()==1 && label[0]=='.') { // otherwise we encode '..' + d_record.push_back(0); +@@ -200,7 +198,7 @@ + // d_stuff is amount of stuff that is yet to be written out - the dnsrecordheader for example + unsigned int pos=d_content.size() + d_record.size() + d_stuff; + string chopped; +- for(parts_t::const_iterator i=parts.begin(); i!=parts.end(); ++i) { ++ for(labelparts_t::const_iterator i=parts.begin(); i!=parts.end(); ++i) { + chopped.assign(label.c_str() + i->first); + lmap_t::iterator li=d_labelmap.end(); + // see if we've written out this domain before +@@ -218,6 +216,10 @@ + if(unescaped) { + string part(label.c_str() + i -> first, i->second - i->first); + replace_all(part, "\\.", "."); ++ replace_all(part, "\\\\", "\\"); ++ if(part.size() > 255) ++ throw MOADNSException("Attempting to express a label of invalid size in xfrLabel"); ++ + d_record.push_back(part.size()); + unsigned int len=d_record.size(); + d_record.resize(len + part.size()); +diff -urNad pdns-recursor-3.1.7~/dnswriter.hh pdns-recursor-3.1.7/dnswriter.hh +--- pdns-recursor-3.1.7~/dnswriter.hh 2008-07-09 22:42:48.000000000 +0300 ++++ pdns-recursor-3.1.7/dnswriter.hh 2010-01-07 00:20:14.000000000 +0200 +@@ -107,4 +107,8 @@ + uint16_t d_rollbackmarker; // start of last complete packet, for rollback + Place d_recordplace; + }; ++ ++typedef vector > labelparts_t; ++ ++bool labeltokUnescape(labelparts_t& parts, const std::string& label); + #endif +diff -urNad pdns-recursor-3.1.7~/lwres.cc pdns-recursor-3.1.7/lwres.cc +--- pdns-recursor-3.1.7~/lwres.cc 2010-01-07 00:11:58.000000000 +0200 ++++ pdns-recursor-3.1.7/lwres.cc 2010-01-07 00:20:14.000000000 +0200 +@@ -155,7 +155,7 @@ + lwr->d_tcbit=mdp.d_header.tc; + lwr->d_rcode=mdp.d_header.rcode; + +- if(Utility::strcasecmp(domain.c_str(), mdp.d_qname.c_str())) { ++ if(!pdns_iequals(domain,mdp.d_qname)) { + if(domain.find((char)0)==string::npos) {// embedded nulls are too noisy + L< ++ ++inline bool pdns_ilexicographical_compare(const std::string& a, const std::string& b) __attribute__((pure)); ++inline bool pdns_ilexicographical_compare(const std::string& a, const std::string& b) + { +- bool operator()(const string& a, const string& b) const +- { +- const unsigned char *p1 = (const unsigned char *) a.c_str(); +- const unsigned char *p2 = (const unsigned char *) b.c_str(); +- int result; +- +- if (p1 == p2) +- return 0; +- +- while ((result = dns_tolower (*p1) - dns_tolower (*p2++)) == 0) +- if (*p1++ == '\0') +- break; +- +- return result < 0; ++ string::size_type aLen = a.length(), bLen = b.length(), n; ++ const char *aPtr = a.c_str(), *bPtr = b.c_str(); ++ int result; ++ ++ for(n = 0 ; n < aLen && n < bLen ; ++n) { ++ if((result = dns_tolower(*aPtr++) - dns_tolower(*bPtr++))) { ++ return result < 0; ++ } + } ++ if(n == aLen && n == bLen) // strings are equal (in length) ++ return 0; ++ if(n == aLen) // first string was shorter ++ return true; ++ return false; ++} + +- bool operator()(const string& a, const char* b) const +- { +- const unsigned char *p1 = (const unsigned char *) a.c_str(); +- const unsigned char *p2 = (const unsigned char *) b; +- int result; +- +- if (p1 == p2) +- return 0; +- +- while ((result = dns_tolower (*p1) - dns_tolower (*p2++)) == 0) +- if (*p1++ == '\0') +- break; +- +- return result < 0; ++inline bool pdns_iequals(const std::string& a, const std::string& b) __attribute__((pure)); ++ ++inline bool pdns_iequals(const std::string& a, const std::string& b) ++{ ++ string::size_type aLen = a.length(), bLen = b.length(), n; ++ const char *aPtr = a.c_str(), *bPtr = b.c_str(); ++ ++ for(n = 0 ; n < aLen && n < bLen ; ++n) { ++ if(dns_tolower(*aPtr++) != dns_tolower(*bPtr++)) ++ return false; + } ++ return aLen == bLen; // strings are equal (in length) ++} + +- bool operator()(const char* a, const string& b) const ++struct CIStringCompare: public binary_function ++{ ++ bool operator()(const string& a, const string& b) const + { +- const unsigned char *p1 = (const unsigned char *) a; +- const unsigned char *p2 = (const unsigned char *) b.c_str(); +- int result; +- +- if (p1 == p2) +- return 0; +- +- while ((result = dns_tolower (*p1) - dns_tolower (*p2++)) == 0) +- if (*p1++ == '\0') +- break; +- +- return result < 0; ++ return pdns_ilexicographical_compare(a, b); + } + }; + +diff -urNad pdns-recursor-3.1.7~/pdns_recursor.cc pdns-recursor-3.1.7/pdns_recursor.cc +--- pdns-recursor-3.1.7~/pdns_recursor.cc 2010-01-07 00:11:58.000000000 +0200 ++++ pdns-recursor-3.1.7/pdns_recursor.cc 2010-01-07 00:20:14.000000000 +0200 +@@ -806,43 +806,54 @@ + } + } + +-void questionExpand(const char* packet, uint16_t len, char* qname, int maxlen, uint16_t& type) ++ ++static void appendEscapedLabel(string& ret, const char* begin, unsigned char labellen) ++{ ++ unsigned char n = 0; ++ for(n = 0 ; n < labellen; ++n) ++ if(begin[n] == '.' || begin[n] == '\\') ++ break; ++ ++ if( n == labellen) { ++ ret.append(begin, labellen); ++ return; ++ } ++ string label(begin, labellen); ++ boost::replace_all(label, "\\", "\\\\"); ++ boost::replace_all(label, ".", "\\."); ++ ret.append(label); ++} ++ ++string questionExpand(const char* packet, uint16_t len, uint16_t& type) + { + type=0; +- const unsigned char* end=(const unsigned char*)packet+len; +- unsigned char* lbegin=(unsigned char*)packet+12; +- unsigned char* pos=lbegin; ++ string ret; ++ if(len < 12) ++ throw runtime_error("Error parsing question in incoming packet: packet too short"); ++ ++ const unsigned char* end = (const unsigned char*)packet+len; ++ const unsigned char* pos = (const unsigned char*)packet+12; + unsigned char labellen; +- +- // 3www4ds9a2nl0 +- char *dst=qname; +- char* lend=dst + maxlen; + + if(!*pos) +- *dst++='.'; +- ++ ret.assign(1, '.'); ++ + while((labellen=*pos++) && pos < end) { // "scan and copy" +- if(dst >= lend) +- throw runtime_error("Label length exceeded destination length"); +- for(;labellen;--labellen) +- *dst++ = *pos++; +- *dst++='.'; ++ if(pos + labellen > end) ++ throw runtime_error("Error parsing question in incoming packet: label extends beyond packet"); ++ ++ appendEscapedLabel(ret, (const char*) pos, labellen); ++ ++ ret.append(1, '.'); ++ pos += labellen; + } +- *dst=0; + +- if(pos + labellen + 2 <= end) // is this correct XXX FIXME? ++ if(pos + labellen + 2 <= end) + type=(*pos)*256 + *(pos+1); +- +- +- // cerr<<"Returning: '"<< string(tmp+1, pos) <<"'\n"; ++ // cerr << "returning: '"<d_waiters.begin(); mthread!=MT->d_waiters.end(); ++mthread) { + if(pident.fd==mthread->key.fd && mthread->key.remote==pident.remote && mthread->key.type == pident.type && +- !Utility::strcasecmp(pident.domain.c_str(), mthread->key.domain.c_str())) { ++ pdns_iequals(pident.domain,mthread->key.domain)) { + mthread->key.nearMisses++; + } + } +diff -urNad pdns-recursor-3.1.7~/recursor_cache.cc pdns-recursor-3.1.7/recursor_cache.cc +--- pdns-recursor-3.1.7~/recursor_cache.cc 2010-01-07 00:11:58.000000000 +0200 ++++ pdns-recursor-3.1.7/recursor_cache.cc 2010-01-07 00:17:38.000000000 +0200 +@@ -180,7 +180,7 @@ + + // cerr<<"looking up "<< qname+"|"+qt.getName()<<"\n"; + +- if(!d_cachecachevalid || Utility::strcasecmp(d_cachedqname.c_str(), qname.c_str())) { ++ if(!d_cachecachevalid || !pdns_iequals(d_cachedqname,qname)) { + // cerr<<"had cache cache miss"<&ret) + { + s_queries++; +- if( (qtype.getCode()==QType::PTR && !Utility::strcasecmp(qname.c_str(), "1.0.0.127.in-addr.arpa.")) || +- (qtype.getCode()==QType::A && qname.length()==10 && !Utility::strcasecmp(qname.c_str(), "localhost."))) { ++ if( (qtype.getCode()==QType::PTR && pdns_iequals(qname, "1.0.0.127.in-addr.arpa.")) || ++ (qtype.getCode()==QType::A && qname.length()==10 && pdns_iequals(qname, "localhost."))) { + ret.clear(); + DNSResourceRecord rr; + rr.qname=qname; +@@ -79,7 +79,7 @@ + } + + if(qclass==3 && qtype.getCode()==QType::TXT && +- (!Utility::strcasecmp(qname.c_str(), "version.bind.") || !Utility::strcasecmp(qname.c_str(), "id.server.") || !Utility::strcasecmp(qname.c_str(), "version.pdns.") ) ++ (pdns_iequals(qname, "version.bind.") || pdns_iequals(qname, "id.server.") || pdns_iequals(qname, "version.pdns.") ) + ) { + ret.clear(); + DNSResourceRecord rr; +@@ -87,7 +87,7 @@ + rr.qtype=qtype; + rr.qclass=qclass; + rr.ttl=86400; +- if(!Utility::strcasecmp(qname.c_str(),"version.bind.") || !Utility::strcasecmp(qname.c_str(),"version.pdns.")) ++ if(pdns_iequals(qname, "version.bind.") || pdns_iequals(qname, "version.pdns.")) + rr.content="\""+::arg()["version-string"]+"\""; + else + rr.content="\""+s_serverID+"\""; +@@ -571,10 +571,9 @@ + { + bool operator()(const pair& a, const pair& b) const + { +- int cmp=Utility::strcasecmp(a.first.c_str(), b.first.c_str()); +- if(cmp < 0) ++ if(pdns_ilexicographical_compare(a.first, b.first)) + return true; +- if(cmp > 0) ++ if(pdns_ilexicographical_compare(b.first, a.first)) + return false; + + return a.second < b.second; +@@ -829,7 +828,7 @@ + newtarget=i->content; + } + // for ANY answers we *must* have an authoritive answer +- else if(i->d_place==DNSResourceRecord::ANSWER && !Utility::strcasecmp(i->qname.c_str(),qname.c_str()) && ++ else if(i->d_place==DNSResourceRecord::ANSWER && pdns_iequals(i->qname,qname) && + ( + i->qtype==qtype || (lwr.d_aabit && (qtype==QType(QType::ANY) || magicAddrMatch(qtype, i->qtype) ) ) + ) +diff -urNad pdns-recursor-3.1.7~/syncres.hh pdns-recursor-3.1.7/syncres.hh +--- pdns-recursor-3.1.7~/syncres.hh 2008-07-09 22:42:48.000000000 +0300 ++++ pdns-recursor-3.1.7/syncres.hh 2010-01-07 00:20:14.000000000 +0200 +@@ -434,12 +434,11 @@ + if( tie(remote, ourSock, type) > tie(b.remote, bSock, b.type)) + return false; + +- int cmp=Utility::strcasecmp(domain.c_str(), b.domain.c_str()); +- if(cmp < 0) ++ if(pdns_ilexicographical_compare(domain,b.domain)) + return true; +- if(cmp > 0) +- return false; + ++ if(pdns_ilexicographical_compare(b.domain,domain)) ++ return false; + return tie(fd, id) < tie(b.fd, b.id); + } + }; +@@ -455,8 +454,7 @@ + if( tie(a.remote, ourSock, a.type) > tie(b.remote, bSock, b.type)) + return false; + +- int cmp=Utility::strcasecmp(a.domain.c_str(), b.domain.c_str()); +- return cmp < 0; ++ return pdns_ilexicographical_compare(a.domain,b.domain); + } + }; + extern MemRecursorCache RC; +diff -urNad pdns-recursor-3.1.7~/zoneparser-tng.cc pdns-recursor-3.1.7/zoneparser-tng.cc +--- pdns-recursor-3.1.7~/zoneparser-tng.cc 2008-07-09 22:42:48.000000000 +0300 ++++ pdns-recursor-3.1.7/zoneparser-tng.cc 2010-01-07 00:20:14.000000000 +0200 +@@ -303,7 +303,7 @@ + + // cout<<"Next part: '"<