Commit message: Fixed out of bounds memory access from malformed DNS queries that have an invalid length label. Introduced in a6a07de0daa353bcd29056a4535a9c4784c113c8.
CVE: CVE-2012-6696
CWE: CWE-476
Vulnerability description: inspircd in Debian before 2.0.7 does not properly handle unsigned integers.  NOTE: This vulnerability exists because of an incomplete fix to CVE-2012-1836.

Project: inspircd
File: dns.cpp

int DNSRequest::SendRequests(const DNSHeader *header, const int length, QueryType qt)
{
	ServerInstance->Logs->Log("RESOLVER", DEBUG,"DNSRequest::SendRequests");

	unsigned char payload[sizeof(DNSHeader)];

	this->rr_class = 1;
	this->type = qt;

	DNS::EmptyHeader(payload,header,length);

	if (ServerInstance->SE->SendTo(dnsobj, payload, length + 12, 0, &(dnsobj->myserver.sa), sa_size(dnsobj->myserver)) != length+12)
		return -1;

	ServerInstance->Logs->Log("RESOLVER",DEBUG,"Sent OK");
	return 0;
}

Project: inspircd
File: dns.cpp

int DNS::GetIP6(const char *name)
{
	DNSHeader h;
	int id;
	int length;

	if ((length = this->MakePayload(name, DNS_QUERY_AAAA, 1, (unsigned char*)&h.payload)) == -1)
		return -1;

	DNSRequest* req = this->AddQuery(&h, id, name);

	if ((!req) || (req->SendRequests(&h, length, DNS_QUERY_AAAA) == -1))
		return -1;

	return id;
}

Project: inspircd
File: dns.cpp

int DNS::GetCName(const char *alias)
{
	DNSHeader h;
	int id;
	int length;

	if ((length = this->MakePayload(alias, DNS_QUERY_CNAME, 1, (unsigned char*)&h.payload)) == -1)
		return -1;

	DNSRequest* req = this->AddQuery(&h, id, alias);

	if ((!req) || (req->SendRequests(&h, length, DNS_QUERY_CNAME) == -1))
		return -1;

	return id;
}

Project: inspircd
File: dns.cpp

inline void DNS::EmptyHeader(unsigned char *output, const DNSHeader *header, const int length)
{
	output[0] = header->id[0];
	output[1] = header->id[1];
	output[2] = header->flags1;
	output[3] = header->flags2;
	output[4] = header->qdcount >> 8;
	output[5] = header->qdcount & 0xFF;
	output[6] = header->ancount >> 8;
	output[7] = header->ancount & 0xFF;
	output[8] = header->nscount >> 8;
	output[9] = header->nscount & 0xFF;
	output[10] = header->arcount >> 8;
	output[11] = header->arcount & 0xFF;
	memcpy(&output[12],header->payload,length);
}

Project: inspircd
File: dns.cpp

DNSRequest::~DNSRequest()
{
	delete[] res;
}

Project: inspircd
File: dns.cpp

Module* Resolver::GetCreator()
{
	return this->Creator;
}

Project: inspircd
File: dns.cpp

DNSResult DNS::GetResult()
{
	/* Fetch dns query response and decide where it belongs */
	DNSHeader header;
	DNSRequest *req;
	unsigned char buffer[sizeof(DNSHeader)];
	irc::sockets::sockaddrs from;
	memset(&from, 0, sizeof(from));
	socklen_t x = sizeof(from);

	int length = ServerInstance->SE->RecvFrom(this, (char*)buffer, sizeof(DNSHeader), 0, &from.sa, &x);

	/* Did we get the whole header? */
	if (length < 12)
	{
		ServerInstance->Logs->Log("RESOLVER",DEBUG,"GetResult didn't get a full packet (len=%d)", length);
		/* Nope - something screwed up. */
		return DNSResult(-1,"",0,"");
	}

	/* Check wether the reply came from a different DNS
	 * server to the one we sent it to, or the source-port
	 * is not 53.
	 * A user could in theory still spoof dns packets anyway
	 * but this is less trivial than just sending garbage
	 * to the server, which is possible without this check.
	 *
	 * -- Thanks jilles for pointing this one out.
	 */
	if (from != myserver)
	{
		ServerInstance->Logs->Log("RESOLVER",DEBUG,"Got a result from the wrong server! Bad NAT or DNS forging attempt? '%s' != '%s'",
			from.str().c_str(), myserver.str().c_str());
		return DNSResult(-1,"",0,"");
	}

	/* Put the read header info into a header class */
	DNS::FillHeader(&header,buffer,length - 12);

	/* Get the id of this request.
	 * Its a 16 bit value stored in two char's,
	 * so we use logic shifts to create the value.
	 */
	unsigned long this_id = header.id[1] + (header.id[0] << 8);

	/* Do we have a pending request matching this id? */
	if (!requests[this_id])
	{
		/* Somehow we got a DNS response for a request we never made... */
		ServerInstance->Logs->Log("RESOLVER",DEBUG,"Hmm, got a result that we didn't ask for (id=%lx). Ignoring.", this_id);
		return DNSResult(-1,"",0,"");
	}
	else
	{
		/* Remove the query from the list of pending queries */
		req = requests[this_id];
		requests[this_id] = NULL;
	}

	/* Inform the DNSRequest class that it has a result to be read.
	 * When its finished it will return a DNSInfo which is a pair of
	 * unsigned char* resource record data, and an error message.
	 */
	DNSInfo data = req->ResultIsReady(header, length);
	std::string resultstr;

	/* Check if we got a result, if we didnt, its an error */
	if (data.first == NULL)
	{
		/* An error.
		 * Mask the ID with the value of ERROR_MASK, so that
		 * the dns_deal_with_classes() function knows that its
		 * an error response and needs to be treated uniquely.
		 * Put the error message in the second field.
		 */
		std::string ro = req->orig;
		delete req;
		return DNSResult(this_id | ERROR_MASK, data.second, 0, ro);
	}
	else
	{
		unsigned long ttl = req->ttl;
		char formatted[128];

		/* Forward lookups come back as binary data. We must format them into ascii */
		switch (req->type)
		{
			case DNS_QUERY_A:
				snprintf(formatted,16,"%u.%u.%u.%u",data.first[0],data.first[1],data.first[2],data.first[3]);
				resultstr = formatted;
			break;

			case DNS_QUERY_AAAA:
			{
				inet_ntop(AF_INET6, data.first, formatted, sizeof(formatted));
				char* c = strstr(formatted,":0:");
				if (c != NULL)
				{
					memmove(c+1,c+2,strlen(c+2) + 1);
					c += 2;
					while (memcmp(c,"0:",2) == 0)
						memmove(c,c+2,strlen(c+2) + 1);
					if (memcmp(c,"0",2) == 0)
						*c = 0;
					if (memcmp(formatted,"0::",3) == 0)
						memmove(formatted,formatted + 1, strlen(formatted + 1) + 1);
				}
				resultstr = formatted;

				/* Special case. Sending ::1 around between servers
				 * and to clients is dangerous, because the : on the
				 * start makes the client or server interpret the IP
				 * as the last parameter on the line with a value ":1".
				 */
				if (*formatted == ':')
					resultstr.insert(0, "0");
			}
			break;

			case DNS_QUERY_CNAME:
				/* Identical handling to PTR */

			case DNS_QUERY_PTR:
				/* Reverse lookups just come back as char* */
				resultstr = std::string((const char*)data.first);
			break;

			default:
			break;
		}

		/* Build the reply with the id and hostname/ip in it */
		std::string ro = req->orig;
		delete req;
		return DNSResult(this_id,resultstr,ttl,ro);
	}
}

Project: inspircd
File: dns.cpp

inline void DNS::FillHeader(DNSHeader *header, const unsigned char *input, const int length)
{
	header->id[0] = input[0];
	header->id[1] = input[1];
	header->flags1 = input[2];
	header->flags2 = input[3];
	header->qdcount = (input[4] << 8) + input[5];
	header->ancount = (input[6] << 8) + input[7];
	header->nscount = (input[8] << 8) + input[9];
	header->arcount = (input[10] << 8) + input[11];
	memcpy(header->payload,&input[12],length);
}

Project: inspircd
File: dns.cpp

int DNS::MakePayload(const char * const name, const QueryType rr, const unsigned short rr_class, unsigned char * const payload)
{
	short payloadpos = 0;
	const char* tempchr, *tempchr2 = name;
	unsigned short length;

	/* split name up into labels, create query */
	while ((tempchr = strchr(tempchr2,'.')) != NULL)
	{
		length = tempchr - tempchr2;
		if (payloadpos + length + 1 > 507)
			return -1;
		payload[payloadpos++] = length;
		memcpy(&payload[payloadpos],tempchr2,length);
		payloadpos += length;
		tempchr2 = &tempchr[1];
	}
	length = strlen(tempchr2);
	if (length)
	{
		if (payloadpos + length + 2 > 507)
			return -1;
		payload[payloadpos++] = length;
		memcpy(&payload[payloadpos],tempchr2,length);
		payloadpos += length;
		payload[payloadpos++] = 0;
	}
	if (payloadpos > 508)
		return -1;
	length = htons(rr);
	memcpy(&payload[payloadpos],&length,2);
	length = htons(rr_class);
	memcpy(&payload[payloadpos + 2],&length,2);
	return payloadpos + 4;
}

Project: inspircd
File: dns.cpp

int DNS::GetNameForce(const char *ip, ForceProtocol fp)
{
	char query[128];
	DNSHeader h;
	int id;
	int length;

	if (fp == PROTOCOL_IPV6)
	{
		in6_addr i;
		if (inet_pton(AF_INET6, ip, &i) > 0)
		{
			DNS::MakeIP6Int(query, &i);
		}
		else
		{
			ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce IPv6 bad format for '%s'", ip);
			/* Invalid IP address */
			return -1;
		}
	}
	else
	{
		in_addr i;
		if (inet_aton(ip, &i))
		{
			unsigned char* c = (unsigned char*)&i.s_addr;
			sprintf(query,"%d.%d.%d.%d.in-addr.arpa",c[3],c[2],c[1],c[0]);
		}
		else
		{
			ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce IPv4 bad format for '%s'", ip);
			/* Invalid IP address */
			return -1;
		}
	}

	length = this->MakePayload(query, DNS_QUERY_PTR, 1, (unsigned char*)&h.payload);
	if (length == -1)
	{
		ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce can't query '%s' using '%s' because it's too long", ip, query);
		return -1;
	}

	DNSRequest* req = this->AddQuery(&h, id, ip);

	if (!req)
	{
		ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce can't add query (resolver down?)");
		return -1;
	}

	if (req->SendRequests(&h, length, DNS_QUERY_PTR) == -1)
	{
		ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce can't send (firewall?)");
		return -1;
	}

	return id;
}

Project: inspircd
File: dns.cpp

void DNS::DelCache(const std::string &source)
{
	cache->erase(source.c_str());
}

Project: inspircd
File: dns.cpp

void DNS::HandleEvent(EventType, int)
{
	/* Fetch the id and result of the next available packet */
	DNSResult res(0,"",0,"");
	res.id = 0;
	ServerInstance->Logs->Log("RESOLVER",DEBUG,"Handle DNS event");

	res = this->GetResult();

	ServerInstance->Logs->Log("RESOLVER",DEBUG,"Result id %d", res.id);

	/* Is there a usable request id? */
	if (res.id != -1)
	{
		/* Its an error reply */
		if (res.id & ERROR_MASK)
		{
			/* Mask off the error bit */
			res.id -= ERROR_MASK;
			/* Marshall the error to the correct class */
			if (Classes[res.id])
			{
				if (ServerInstance && ServerInstance->stats)
					ServerInstance->stats->statsDnsBad++;
				Classes[res.id]->OnError(RESOLVER_NXDOMAIN, res.result);
				delete Classes[res.id];
				Classes[res.id] = NULL;
			}
			return;
		}
		else
		{
			/* It is a non-error result, marshall the result to the correct class */
			if (Classes[res.id])
			{
				if (ServerInstance && ServerInstance->stats)
					ServerInstance->stats->statsDnsGood++;

				if (!this->GetCache(res.original.c_str()))
					this->cache->insert(std::make_pair(res.original.c_str(), CachedQuery(res.result, res.ttl)));

				Classes[res.id]->OnLookupComplete(res.result, res.ttl, false);
				delete Classes[res.id];
				Classes[res.id] = NULL;
			}
		}

		if (ServerInstance && ServerInstance->stats)
			ServerInstance->stats->statsDns++;
	}
}

Project: inspircd
File: dns.cpp

void DNS::MakeIP6Int(char* query, const in6_addr *ip)
{
	const char* hex = "0123456789abcdef";
	for (int index = 31; index >= 0; index--) /* for() loop steps twice per byte */
	{
		if (index % 2)
			/* low nibble */
			*query++ = hex[ip->s6_addr[index / 2] & 0x0F];
		else
			/* high nibble */
			*query++ = hex[(ip->s6_addr[index / 2] & 0xF0) >> 4];
		*query++ = '.'; /* Seperator */
	}
	strcpy(query,"ip6.arpa"); /* Suffix the string */
}

Project: inspircd
File: dns.cpp

CachedQuery::CachedQuery(const std::string &res, unsigned int ttl) : data(res)
{
	expires = ServerInstance->Time() + ttl;
}

Project: inspircd
File: dns.cpp

void DNS::CleanResolvers(Module* module)
{
	for (int i = 0; i < MAX_REQUEST_ID; i++)
	{
		if (Classes[i])
		{
			if (Classes[i]->GetCreator() == module)
			{
				Classes[i]->OnError(RESOLVER_FORCEUNLOAD, "Parent module is unloading");
				delete Classes[i];
				Classes[i] = NULL;
			}
		}
	}
}

Project: inspircd
File: dns.cpp

	void Tick(time_t)
	{
		if (ServerInstance->Res->requests[watchid] == watch)
		{
			/* Still exists, whack it */
			if (ServerInstance->Res->Classes[watchid])
			{
				ServerInstance->Res->Classes[watchid]->OnError(RESOLVER_TIMEOUT, "Request timed out");
				delete ServerInstance->Res->Classes[watchid];
				ServerInstance->Res->Classes[watchid] = NULL;
			}
			ServerInstance->Res->requests[watchid] = NULL;
			delete watch;
		}
	}

Project: inspircd
File: dns.cpp

void Resolver::TriggerCachedResult()
{
	if (CQ)
		OnLookupComplete(CQ->data, time_left, true);
}

Project: inspircd
File: dns.cpp

void Resolver::OnError(ResolverError, const std::string&)
{
	/* Nothing in here */
}

Project: inspircd
File: dns.cpp

DNS::DNS()
{
	ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::DNS");
	/* Clear the Resolver class table */
	memset(Classes,0,sizeof(Classes));

	/* Clear the requests class table */
	memset(requests,0,sizeof(requests));

	/* Set the id of the next request to 0
	 */
	currid = 0;

	/* DNS::Rehash() sets this to a valid ptr
	 */
	this->cache = NULL;

	/* Again, DNS::Rehash() sets this to a
	 * valid value
	 */
	this->SetFd(-1);

	/* Actually read the settings
	 */
	this->Rehash();

	this->PruneTimer = new CacheTimer(this);

	ServerInstance->Timers->AddTimer(this->PruneTimer);
}

Project: inspircd
File: dns.cpp

DNSRequest::DNSRequest(DNS* dns, int rid, const std::string &original) : dnsobj(dns)
{
	/* hardening against overflow here:  make our work buffer twice the theoretical
	 * maximum size so that hostile input doesn't screw us over.
	 */
	res = new unsigned char[sizeof(DNSHeader) * 2];
	*res = 0;
	orig = original;
	RequestTimeout* RT = new RequestTimeout(ServerInstance->Config->dns_timeout ? ServerInstance->Config->dns_timeout : 5, this, rid);
	ServerInstance->Timers->AddTimer(RT); /* The timer manager frees this */
}

Project: inspircd
File: dns.cpp

DNS::~DNS()
{
	ServerInstance->SE->Shutdown(this, 2);
	ServerInstance->SE->Close(this);
	ServerInstance->Timers->DelTimer(this->PruneTimer);
	if (cache)
		delete cache;
}

Project: inspircd
File: dns.cpp

bool DNS::AddResolverClass(Resolver* r)
{
	ServerInstance->Logs->Log("RESOLVER",DEBUG,"AddResolverClass 0x%08lx", (unsigned long)r);
	/* Check the pointers validity and the id's validity */
	if ((r) && (r->GetId() > -1))
	{
		/* Check the slot isnt already occupied -
		 * This should NEVER happen unless we have
		 * a severely broken DNS server somewhere
		 */
		if (!Classes[r->GetId()])
		{
			/* Set up the pointer to the class */
			Classes[r->GetId()] = r;
			return true;
		}
		else
			/* Duplicate id */
			return false;
	}
	else
	{
		/* Pointer or id not valid.
		 * Free the item and return
		 */
		if (r)
			delete r;

		return false;
	}
}

Project: inspircd
File: dns.cpp

int DNS::PruneCache()
{
	int n = 0;
	dnscache* newcache = new dnscache();
	for (dnscache::iterator i = this->cache->begin(); i != this->cache->end(); i++)
		/* Dont include expired items (theres no point) */
		if (i->second.CalcTTLRemaining())
			newcache->insert(*i);
		else
			n++;

	delete this->cache;
	this->cache = newcache;
	return n;
}

Project: inspircd
File: dns.cpp

void DNS::Rehash()
{
	if (this->GetFd() > -1)
	{
		ServerInstance->SE->DelFd(this);
		ServerInstance->SE->Shutdown(this, 2);
		ServerInstance->SE->Close(this);
		this->SetFd(-1);

		/* Rehash the cache */
		this->PruneCache();
	}
	else
	{
		/* Create initial dns cache */
		this->cache = new dnscache();
	}

	irc::sockets::aptosa(ServerInstance->Config->DNSServer, DNS::QUERY_PORT, myserver);

	/* Initialize mastersocket */
	int s = socket(myserver.sa.sa_family, SOCK_DGRAM, 0);
	this->SetFd(s);

	/* Have we got a socket and is it nonblocking? */
	if (this->GetFd() != -1)
	{
		ServerInstance->SE->SetReuse(s);
		ServerInstance->SE->NonBlocking(s);
		irc::sockets::sockaddrs bindto;
		memset(&bindto, 0, sizeof(bindto));
		bindto.sa.sa_family = myserver.sa.sa_family;
		if (ServerInstance->SE->Bind(this->GetFd(), bindto) < 0)
		{
			/* Failed to bind */
			ServerInstance->Logs->Log("RESOLVER",SPARSE,"Error binding dns socket - hostnames will NOT resolve");
			ServerInstance->SE->Shutdown(this, 2);
			ServerInstance->SE->Close(this);
			this->SetFd(-1);
		}
		else if (!ServerInstance->SE->AddFd(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE))
		{
			ServerInstance->Logs->Log("RESOLVER",SPARSE,"Internal error starting DNS - hostnames will NOT resolve.");
			ServerInstance->SE->Shutdown(this, 2);
			ServerInstance->SE->Close(this);
			this->SetFd(-1);
		}
	}
	else
	{
		ServerInstance->Logs->Log("RESOLVER",SPARSE,"Error creating DNS socket - hostnames will NOT resolve");
	}
}

Project: inspircd
File: dns.cpp

CachedQuery* DNS::GetCache(const std::string &source)
{
	dnscache::iterator x = cache->find(source.c_str());
	if (x != cache->end())
		return &(x->second);
	else
		return NULL;
}

Project: inspircd
File: dns.cpp

DNSRequest* DNS::AddQuery(DNSHeader *header, int &id, const char* original)
{
	/* Is the DNS connection down? */
	if (this->GetFd() == -1)
		return NULL;

	/* Create an id */
	do {
		id = ServerInstance->GenRandomInt(DNS::MAX_REQUEST_ID);
	} while (requests[id]);

	DNSRequest* req = new DNSRequest(this, id, original);

	header->id[0] = req->id[0] = id >> 8;
	header->id[1] = req->id[1] = id & 0xFF;
	header->flags1 = FLAGS_MASK_RD;
	header->flags2 = 0;
	header->qdcount = 1;
	header->ancount = 0;
	header->nscount = 0;
	header->arcount = 0;

	/* At this point we already know the id doesnt exist,
	 * so there needs to be no second check for the ::end()
	 */
	requests[id] = req;

	/* According to the C++ spec, new never returns NULL. */
	return req;
}

Project: inspircd
File: dns.cpp

int DNS::ClearCache()
{
	/* This ensures the buckets are reset to sane levels */
	int rv = this->cache->size();
	delete this->cache;
	this->cache = new dnscache();
	return rv;
}

Project: inspircd
File: dns.cpp

Resolver::~Resolver()
{
	/* Nothing here (yet) either */
}

Project: inspircd
File: dns.cpp

int Resolver::GetId()
{
	return this->myid;
}

Project: inspircd
File: dns.cpp

inline void DNS::FillResourceRecord(ResourceRecord* rr, const unsigned char *input)
{
	rr->type = (QueryType)((input[0] << 8) + input[1]);
	rr->rr_class = (input[2] << 8) + input[3];
	rr->ttl = (input[4] << 24) + (input[5] << 16) + (input[6] << 8) + input[7];
	rr->rdlength = (input[8] << 8) + input[9];
}

Project: inspircd
File: dns.cpp

int DNS::GetIP(const char *name)
{
	DNSHeader h;
	int id;
	int length;

	if ((length = this->MakePayload(name, DNS_QUERY_A, 1, (unsigned char*)&h.payload)) == -1)
		return -1;

	DNSRequest* req = this->AddQuery(&h, id, name);

	if ((!req) || (req->SendRequests(&h, length, DNS_QUERY_A) == -1))
		return -1;

	return id;
}

Project: inspircd
File: dns.cpp

	virtual void Tick(time_t)
	{
		dns->PruneCache();
	}

Project: inspircd
File: dns.cpp

Resolver::Resolver(const std::string &source, QueryType qt, bool &cached, Module* creator) : Creator(creator), input(source), querytype(qt)
{
	ServerInstance->Logs->Log("RESOLVER",DEBUG,"Resolver::Resolver");
	cached = false;

	CQ = ServerInstance->Res->GetCache(source);
	if (CQ)
	{
		time_left = CQ->CalcTTLRemaining();
		if (!time_left)
		{
			ServerInstance->Res->DelCache(source);
		}
		else
		{
			cached = true;
			return;
		}
	}

	switch (querytype)
	{
		case DNS_QUERY_A:
			this->myid = ServerInstance->Res->GetIP(source.c_str());
		break;

		case DNS_QUERY_PTR4:
			querytype = DNS_QUERY_PTR;
			this->myid = ServerInstance->Res->GetNameForce(source.c_str(), PROTOCOL_IPV4);
		break;

		case DNS_QUERY_PTR6:
			querytype = DNS_QUERY_PTR;
			this->myid = ServerInstance->Res->GetNameForce(source.c_str(), PROTOCOL_IPV6);
		break;

		case DNS_QUERY_AAAA:
			this->myid = ServerInstance->Res->GetIP6(source.c_str());
		break;

		case DNS_QUERY_CNAME:
			this->myid = ServerInstance->Res->GetCName(source.c_str());
		break;

		default:
			ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS request with unknown query type %d", querytype);
			this->myid = -1;
		break;
	}
	if (this->myid == -1)
	{
		throw ModuleException("Resolver: Couldn't get an id to make a request");
	}
	else
	{
		ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS request id %d", this->myid);
	}
}

Project: inspircd
File: dns.cpp

int CachedQuery::CalcTTLRemaining()
{
	int n = expires - ServerInstance->Time();
	return (n < 0 ? 0 : n);
}

Project: inspircd
File: dns.cpp

	CacheTimer(DNS* thisdns)
		: Timer(3600, ServerInstance->Time(), true), dns(thisdns) { }

Project: inspircd
File: dns.cpp

	RequestTimeout(unsigned long n, DNSRequest* watching, int id) : Timer(n, ServerInstance->Time()), watch(watching), watchid(id)
	{
	}

Project: inspircd
File: dns.cpp

	~RequestTimeout()
	{
		if (ServerInstance->Res)
			Tick(0);
	}