Commit message: bug #10609: CVE-2014-0239 Don't reply to replies

Due to insufficient input checking, the DNS server will reply to a packet that
has the "reply" bit set. Over UDP, this allows to send a packet with a spoofed
sender address and have two servers DOS each other with circular replies.

This patch fixes bug #10609 and adds a test to make sure we don't regress.
CVE-2014-2039 has been assigned to this issue.
Bug: https://bugzilla.samba.org/show_bug.cgi?id=10609

Signed-off-by: Kai Blin <kai@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>

Autobuild-User(master): Kai Blin <kai@samba.org>
Autobuild-Date(master): Tue May 20 04:15:44 CEST 2014 on sn-devel-104
CVE: CVE-2014-0239
CWE: CWE-20
Vulnerability description: The internal DNS server in Samba 4.x before 4.0.18 does not check the QR field in the header section of an incoming DNS message before sending a response, which allows remote attackers to cause a denial of service (CPU and bandwidth consumption) via a forged response packet that triggers a communication loop, a related issue to CVE-1999-0103.

Project: samba

static struct tevent_req *dns_process_send(TALLOC_CTX *mem_ctx,
					   struct tevent_context *ev,
					   struct dns_server *dns,
					   DATA_BLOB *in)
{
	struct tevent_req *req, *subreq;
	struct dns_process_state *state;
	enum ndr_err_code ndr_err;
	WERROR ret;
	const char *forwarder = lpcfg_dns_forwarder(dns->task->lp_ctx);
	req = tevent_req_create(mem_ctx, &state, struct dns_process_state);
	if (req == NULL) {
		return NULL;
	}
	state->in = in;

	state->dns = dns;

	if (in->length < 12) {
		tevent_req_werror(req, WERR_INVALID_PARAM);
		return tevent_req_post(req, ev);
	}
	dump_data_dbgc(DBGC_DNS, 8, in->data, in->length);

	ndr_err = ndr_pull_struct_blob(
		in, state, &state->in_packet,
		(ndr_pull_flags_fn_t)ndr_pull_dns_name_packet);

	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		state->dns_err = DNS_RCODE_FORMERR;
		tevent_req_done(req);
		return tevent_req_post(req, ev);
	}
	if (DEBUGLVLC(DBGC_DNS, 8)) {
		NDR_PRINT_DEBUGC(DBGC_DNS, dns_name_packet, &state->in_packet);
	}

	ret = dns_verify_tsig(dns, state, &state->state, &state->in_packet, in);
	if (!W_ERROR_IS_OK(ret)) {
		DEBUG(1, ("Failed to verify TSIG!\n"));
		state->dns_err = werr_to_dns_err(ret);
		tevent_req_done(req);
		return tevent_req_post(req, ev);
	}

	state->state.flags = state->in_packet.operation;
	state->state.flags |= DNS_FLAG_REPLY;

	
	if (forwarder && *forwarder) {
		state->state.flags |= DNS_FLAG_RECURSION_AVAIL;
	}

	state->out_packet = state->in_packet;

	switch (state->in_packet.operation & DNS_OPCODE) {
	case DNS_OPCODE_QUERY:
		subreq = dns_server_process_query_send(
			state, ev, dns, &state->state, &state->in_packet);
		if (tevent_req_nomem(subreq, req)) {
			return tevent_req_post(req, ev);
		}
		tevent_req_set_callback(subreq, dns_process_done, req);
		return req;
	case DNS_OPCODE_UPDATE:
		ret = dns_server_process_update(
			dns, &state->state, state, &state->in_packet,
			&state->out_packet.answers, &state->out_packet.ancount,
			&state->out_packet.nsrecs,  &state->out_packet.nscount,
			&state->out_packet.additional,
			&state->out_packet.arcount);
		break;
	default:
		ret = WERR_DNS_ERROR_RCODE_NOT_IMPLEMENTED;
	}
	if (!W_ERROR_IS_OK(ret)) {
		state->dns_err = werr_to_dns_err(ret);
	}
	tevent_req_done(req);
	return tevent_req_post(req, ev);
}