1 //
2 //	dnssec_v2.c
3 //	mDNSResponder
4 //
5 //	Copyright (c) 2020 Apple Inc. All rights reserved.
6 //
7 
8 #include <AssertMacros.h>		// for require_* macro
9 #include <os/feature_private.h> // for feature flag
10 #include "mDNSEmbeddedAPI.h"
11 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
12 #include "uds_daemon.h"
13 #include "DNSCommon.h"
14 #include "dnssec_v2.h"
15 #include "dnssec_v2_helper.h"
16 #include "dnssec_v2_validation.h"
17 #include "dnssec_v2_trust_anchor.h"
18 #include "dnssec_v2_client.h"
19 
20 // MARK: - Macros
21 
22 #define DNSSEC_OK_BIT 0x8000
23 
24 // MARK: - External Functions
25 
26 mDNSexport mDNSBool
enables_dnssec_validation(const DNSQuestion * _Nonnull q)27 enables_dnssec_validation(const DNSQuestion * _Nonnull q) {
28 	return q->DNSSECStatus.enable_dnssec;
29 }
30 
31 //======================================================================================================================
32 
33 // Check if the question could be validated with DNSSEC.
34 mDNSexport mDNSBool
is_eligible_for_dnssec(const domainname * const _Nonnull name,mDNSu16 question_type)35 is_eligible_for_dnssec(const domainname * const _Nonnull name, mDNSu16 question_type) {
36 	mDNSBool is_eligible = mDNSfalse;
37 
38 	require_quiet(!IsLocalDomain(name), exit);
39 	require_quiet(question_type != kDNSServiceType_RRSIG, exit);
40 	require_quiet(question_type != kDNSServiceType_ANY, exit);
41 
42 	is_eligible = mDNStrue;
43 exit:
44 	return is_eligible;
45 }
46 
47 //======================================================================================================================
48 
49 mDNSexport void
get_denial_records_from_negative_cache_to_dnssec_context(const mDNSBool enable_dnssec,dnssec_context_t * const _Nonnull context,CacheRecord * const _Nonnull rr)50 get_denial_records_from_negative_cache_to_dnssec_context(
51 	const mDNSBool							enable_dnssec,
52 	dnssec_context_t * const	_Nonnull	context,
53 	CacheRecord * const			_Nonnull	rr) {
54 
55 	if (enable_dnssec) {
56 		context->denial_of_existence_records = rr->denial_of_existence_records;
57 	}
58 }
59 
60 //======================================================================================================================
61 
62 mDNSexport void
set_denial_records_in_cache_record(CacheRecord * const _Nonnull cache_record,denial_of_existence_records_t * _Nullable * _Nonnull denial_records_ptr)63 set_denial_records_in_cache_record(
64 	CacheRecord * const 				_Nonnull				cache_record,
65 	denial_of_existence_records_t * 	_Nullable *	_Nonnull	denial_records_ptr) {
66 
67 	cache_record->denial_of_existence_records = *denial_records_ptr;
68 	*denial_records_ptr = mDNSNULL;
69 }
70 
71 //======================================================================================================================
72 
73 mDNSexport void
release_denial_records_in_cache_record(CacheRecord * const _Nonnull cache_record)74 release_denial_records_in_cache_record(CacheRecord * const _Nonnull cache_record) {
75 	if (cache_record->denial_of_existence_records != mDNSNULL) {
76 		destroy_denial_of_existence_records_t(cache_record->denial_of_existence_records);
77 		cache_record->denial_of_existence_records = mDNSNULL;
78 	}
79 }
80 
81 //======================================================================================================================
82 
83 mDNSexport void
update_denial_records_in_cache_record(CacheRecord * const _Nonnull cache_record,denial_of_existence_records_t * _Nullable * _Nonnull denial_records_ptr)84 update_denial_records_in_cache_record(
85 	CacheRecord * const 				_Nonnull 				cache_record,
86 	denial_of_existence_records_t * 	_Nullable *	_Nonnull	denial_records_ptr) {
87 
88 	if (cache_record->denial_of_existence_records != mDNSNULL) {
89 		destroy_denial_of_existence_records_t(cache_record->denial_of_existence_records);
90 	}
91 	cache_record->denial_of_existence_records = *denial_records_ptr;
92 	*denial_records_ptr = mDNSNULL;
93 }
94 
95 //======================================================================================================================
96 
97 mDNSexport mDNSBool
adds_denial_records_in_cache_record(const ResourceRecord * _Nonnull const rr,const mDNSBool enable_dnssec,denial_of_existence_records_t * _Nullable * _Nonnull denials_ptr)98 adds_denial_records_in_cache_record(
99 	const ResourceRecord * 			_Nonnull const			rr,
100 	const mDNSBool 											enable_dnssec,
101 	denial_of_existence_records_t *	_Nullable * _Nonnull	denials_ptr) {
102 
103 	mDNSBool not_answer_but_required_for_dnssec = mDNSfalse;
104 	mStatus error = mStatus_NoError;
105 
106 	require_quiet(enable_dnssec, exit);
107 	require_quiet(record_denies_existence_of_dnssec_question(rr), exit);
108 
109 	if (*denials_ptr == mDNSNULL) {
110 		*denials_ptr = create_denial_of_existence_records_t();
111 		require_quiet(*denials_ptr != mDNSNULL, exit);
112 	}
113 
114 	error = add_to_denial_of_existence_records_t(*denials_ptr, rr);
115 	require_quiet(error == mStatus_NoError, exit);
116 	not_answer_but_required_for_dnssec = mDNStrue;
117 
118 exit:
119 	if (error != mStatus_NoError) {
120 		if (*denials_ptr != mDNSNULL) destroy_denial_of_existence_records_t(*denials_ptr);
121 		*denials_ptr = mDNSNULL;
122 	}
123 	return not_answer_but_required_for_dnssec;
124 }
125 
126 //======================================================================================================================
127 
128 mDNSexport mDNSBool
are_records_in_the_same_cache_set_for_dnssec(const ResourceRecord * const _Nonnull left,const ResourceRecord * const _Nonnull right)129 are_records_in_the_same_cache_set_for_dnssec(
130 	const ResourceRecord * const _Nonnull left,
131 	const ResourceRecord * const _Nonnull right) {
132 
133 	if (left->rrtype != kDNSType_RRSIG) {
134 		return mDNStrue;
135 	}
136 
137 	return rrsig_records_cover_the_same_record_type(left, right);
138 }
139 
140 //======================================================================================================================
141 
142 // Check if the current record type belongs to the question that enables DNSSEC.
143 mDNSexport mDNSBool
record_type_answers_dnssec_question(const ResourceRecord * const _Nonnull record,const mDNSu16 qtype)144 record_type_answers_dnssec_question(const ResourceRecord * const _Nonnull record, const mDNSu16 qtype) {
145 	mDNSBool result = mDNSfalse;
146 
147 	switch (record->rrtype) {
148 		case kDNSType_CNAME:
149 			result		= mDNStrue;
150 			break;
151 		case kDNSType_RRSIG: {
152 			mDNSu16 type_covered	= get_covered_type_of_dns_type_rrsig_t(record->rdata->u.data);
153 
154 			if (qtype == kDNSType_RRSIG
155 				|| type_covered == qtype
156 				|| type_covered == kDNSType_CNAME) { // Returned RRSIG covers a CNAME for the current question.
157 				// RRSIG that covers NSEC/NSEC3 also answers question, but it provides non-existence proof.
158 				result = mDNStrue;
159 			}
160 		}
161 			break;
162 		// kDNSType_DS and kDNSType_DNSKEY also applies to the default case here.
163 		default:
164 			if (record->rrtype == qtype) {
165 				result = mDNStrue;
166 			}
167 			// NSEC/NSEC3 or RRSIG that covers NSEC/NSEC3 also answers question, but they provides non-existence proof,
168 			// so they do not answer the question positively, and the function should return false.
169 			break;
170 	}
171 
172 	return result;
173 }
174 
175 //======================================================================================================================
176 
177 mDNSexport mDNSBool
rrsig_records_cover_the_same_record_type(const ResourceRecord * const _Nonnull left,const ResourceRecord * const _Nonnull right)178 rrsig_records_cover_the_same_record_type(const ResourceRecord * const _Nonnull left, const ResourceRecord * const _Nonnull right) {
179 	mDNSu16 type_covered_left	= get_covered_type_of_dns_type_rrsig_t(left->rdata->u.data);
180 	mDNSu16 type_covered_right	= get_covered_type_of_dns_type_rrsig_t(right->rdata->u.data);
181 
182 	return type_covered_left == type_covered_right;
183 }
184 
185 //======================================================================================================================
186 
187 // Used by mDNSCoreReceiveResponse, to check if the current NSEC/NSEC3 record belongs to the question that enables DNSSEC
188 mDNSexport mDNSBool
record_denies_existence_of_dnssec_question(const ResourceRecord * const _Nonnull record)189 record_denies_existence_of_dnssec_question(const ResourceRecord * const _Nonnull record) {
190 	const mDNSu16	rr_type							= record->rrtype;
191 	mDNSu16			type_covered;
192 	mDNSBool		acceptable_denial_of_existence	= mDNSfalse;
193 
194 	// Temporarily disbale NSEC validation, it should also check if it is NSEC(or the corresponding RRSIG covers NSEC)
195 	if (rr_type == kDNSType_NSEC3) {
196 		acceptable_denial_of_existence = mDNStrue;
197 	} else if (rr_type == kDNSType_RRSIG) {
198 		type_covered = get_covered_type_of_dns_type_rrsig_t(record->rdata->u.data);
199 		// Same here, temporarily disbale NSEC validation.
200 		if (type_covered == kDNSType_NSEC3) {
201 			acceptable_denial_of_existence = mDNStrue;
202 		}
203 	}
204 
205 	return acceptable_denial_of_existence;
206 }
207 
208 //======================================================================================================================
209 
210 // The main DNSSEC callback function, it replaces the original user callback function, and becomes a middle layer
211 // between the mDNSCore and user callback function, all the DNSSEC related operation happens here:
212 // 1. Records retrieval
213 // 2. Records validation
214 mDNSexport void
query_record_result_reply_with_dnssec(mDNS * const _Null_unspecified m,DNSQuestion * _Null_unspecified question,const ResourceRecord * const _Null_unspecified const_answer,QC_result add_record,DNSServiceErrorType dns_result_error,void * _Null_unspecified context)215 query_record_result_reply_with_dnssec(
216 	mDNS *const						_Null_unspecified	m,
217 	DNSQuestion *					_Null_unspecified	question,
218 	const ResourceRecord * const	_Null_unspecified	const_answer,
219 	QC_result											add_record,
220 	DNSServiceErrorType									dns_result_error,
221 	void *							_Null_unspecified	context) {
222 
223 	dnssec_context_t *				dnssec_context		= (dnssec_context_t *)context;
224 	QueryRecordClientRequest *		primary_request		= GET_PRIMARY_REQUEST(dnssec_context);
225 	ResourceRecord * const			answer				= (ResourceRecord *)const_answer;
226 	mDNSBool						anchor_reached		= mDNSfalse;
227 	mDNSBool						stop_process		= mDNSfalse;
228 	dnssec_retrieval_result_t		retrieval_result	= dnssec_retrieval_no_error;
229 	dnssec_validation_result_t		validation_result;
230 	mDNSu32							request_id			= primary_request->op.reqID;
231 	returned_answers_t * const		returned_answers	= &dnssec_context->returned_answers;
232 
233 	switch (add_record) {
234 		case QC_add:
235 		case QC_rmv:
236 		case QC_suppressed:
237 			break;
238 		// QC_addnocache and QC_forceresponse are all cases where the returned resource record is not in the cache.
239 		// We temporarily ignore those two cases.
240 		case QC_addnocache:
241 		case QC_forceresponse:
242 		default:
243 			log_error("[R%u] QC_result other than add, remove, suppressed is returned; add_record=%d",
244 				request_id, add_record);
245 			return;
246 	}
247 
248 	if (dns_result_error == kDNSServiceErr_NoError) {
249 		retrieval_result = add_no_error_records(m, question, answer, add_record, dns_result_error, dnssec_context);
250 	} else {
251 		retrieval_result = add_denial_of_existence_records(m, question, answer, add_record, dns_result_error, dnssec_context);
252 	}
253 
254 	do {
255 		// handle any error case when addign records
256 		stop_process = handle_retrieval_result(question, context, retrieval_result, dns_result_error, m);
257 		// WARNING: If stop_process is set to true here, we should not touch anything including dnssec_context, because
258 		// we might free the object related to the current dnssec request, and we would get memory fault if using it.
259 
260 		// If we have error when adding record, then we should not continue.
261 		if (stop_process) {
262 			break;
263 		}
264 
265 		// check if we could reach the trust anchor with the records we have currently
266 		anchor_reached = trust_anchor_can_be_reached(dnssec_context);
267 		if (anchor_reached) {
268 			// if so, validate the from the leaf to the root(trust anchor)
269 			validation_result = validate_dnssec(dnssec_context);
270 
271 			// handle the validation result such as returning DNSSEC-secure answer to user, return error code to user
272 			stop_process = handle_validation_result(question, context, validation_result, dns_result_error, m);
273 
274 			// If we already returned the answer/error to user, there is no more to do.
275 			if (stop_process) {
276 				break;
277 			}
278 		} else if (returned_answers->error != kDNSServiceErr_Invalid) {
279 			// previous verified record set cannot establish trust chain, deliver rmv event for all returned records
280 			if (returned_answers->type != cname_response) { // Do not deliver RMV for CNAME records.
281 				// Since here we are returning the records on the behave of the primary request, the question being
282 				// returned should be the question from the primary request instead of the possible CNAME question that
283 				// is started by the DNSSEC handler itself.
284 				stop_process = deliver_remove_to_callback_with_all_returned_answers(dnssec_context, returned_answers, m,
285 					GET_PRIMARY_QUESTION(dnssec_context), question);
286 				require_quiet(!stop_process, exit);
287 			}
288 			uninitialize_returned_answers_t(returned_answers);
289 			initialize_returned_answers_t(returned_answers, dnssec_indeterminate, kDNSServiceErr_Invalid);
290 		}
291 
292 		// If the records we have currently is not enough to form a chian of trust, keep querying for more records until
293 		// the trust anchor
294 		retrieval_result = fetch_necessary_dnssec_records(dnssec_context, anchor_reached);
295 
296 		// handle the result of fetch_necessary_dnssec_records such as returning error to user if some error occurs when
297 		// querying for more records
298 		stop_process =	handle_retrieval_result(question, context, retrieval_result, dns_result_error, m);
299 		if (stop_process) {
300 			break;
301 		}
302 	} while (retrieval_result == dnssec_retrieval_validate_again);
303 
304 exit:
305 	return;
306 }
307 
308 //======================================================================================================================
309 
310 mDNSexport void
stop_dnssec_if_enable_dnssec(QueryRecordClientRequest * const _Nonnull request)311 stop_dnssec_if_enable_dnssec(QueryRecordClientRequest * const _Nonnull request) {
312 	DNSQuestion * const q = &request->op.q;
313 	if (!q->DNSSECStatus.enable_dnssec) {
314 		return;
315 	}
316 	stop_dnssec(request);
317 }
318 
319 
320 //======================================================================================================================
321 
322 mDNSexport void
stop_dnssec(QueryRecordClientRequest * const _Nonnull request)323 stop_dnssec(QueryRecordClientRequest * const _Nonnull request) {
324 	DNSQuestion * const			q				= &request->op.q;
325 	mDNSu32						request_id		= request->op.reqID;
326 	if (!q->DNSSECStatus.enable_dnssec) {
327 		goto exit;
328 	}
329 
330 	dnssec_context_t * const	dnssec_context	= (dnssec_context_t *)q->DNSSECStatus.context;
331 	list_t * const				zones			= &dnssec_context->zone_chain;
332 	original_t * const			original		= &dnssec_context->original;
333 	const original_request_parameters_t * const param = &original->original_parameters;
334 
335 	log_default("[R%u] Stopping " PUB_S "DNSSEC request -- hostname: " PRI_DM_NAME ", type: " PUB_S, request_id,
336 		dnssec_context->primary_dnssec_context == mDNSNULL ? "primary " : "sub-",
337 		DM_NAME_PARAM(&param->question_name), DNSTypeName(param->question_type));
338 
339 	// stop and clean zone, dnssec_zone_t node will be deleted in destroy_dnssec_context_t
340 	for (list_node_t * zone_node = list_get_first(zones); !list_has_ended(zones, zone_node); zone_node = list_next(zone_node)) {
341 		dnssec_zone_t * zone = (dnssec_zone_t *)zone_node->data;
342 		stop_and_clean_dnssec_zone_t(zone);
343 	}
344 
345 	// Stop the sub CNAME request.
346 	if (dnssec_context->subtask_dnssec_context != mDNSNULL) {
347 		// Since we will not deliver RMV so there is no need to check if we should stop the request immediately because
348 		// the client cancels the request in the callback.
349 		stop_sub_cname_request_and_dnssec(q, dnssec_context, mDNSfalse, mDNSNULL);
350 	}
351 
352 	// leave original->original_request to be released by QueryRecordClientRequestStop
353 	uninitialize_originals_with_rrsig_t(&original->original_result_with_rrsig);
354 
355 	// undo create_dnssec_context_t
356 	destroy_dnssec_context_t(dnssec_context);
357 
358 exit:
359 	return;
360 }
361 
362 //======================================================================================================================
363 
364 mDNSexport mDNSBool
stop_sub_cname_request_and_dnssec(DNSQuestion * const question,dnssec_context_t * const _Nonnull dnssec_context,const mDNSBool deliver_remove,mDNS * const _Nullable m)365 stop_sub_cname_request_and_dnssec(DNSQuestion * const question, dnssec_context_t * const _Nonnull dnssec_context,
366 	const mDNSBool deliver_remove, mDNS * const _Nullable m) {
367 
368 	dnssec_context_t * const cname_dnssec_context = dnssec_context->subtask_dnssec_context;
369 	DNSQuestion * const primary_question = GET_PRIMARY_QUESTION(dnssec_context);
370 	mDNSBool stop_immediately = mDNSfalse;
371 	require_quiet(cname_dnssec_context != mDNSNULL, exit);
372 
373 	// If we call this function because the CNAME reference chain has changed, all the answers that
374 	// are returned to the client needs to be removed first.
375 	if (deliver_remove) {
376 		for (dnssec_context_t * context_i = cname_dnssec_context; context_i != mDNSNULL; context_i = context_i->subtask_dnssec_context) {
377 			// if it is not kDNSServiceErr_Invalid, it means that we have returned something.
378 			if (context_i->returned_answers.error == kDNSServiceErr_Invalid) {
379 				continue;
380 			}
381 			// Do not deliver RMV event for CNAME answer, since mDNSResponder never rewinds the CNAME chain, DNSSEC API
382 			// needs to follow the same behavior.
383 			if (context_i->returned_answers.type == cname_response) {
384 				continue;
385 			}
386 			stop_immediately = deliver_remove_to_callback_with_all_returned_answers(context_i,
387 				&context_i->returned_answers, m, primary_question, question);
388 			require_quiet(!stop_immediately, exit);
389 		}
390 	}
391 
392 	QueryRecordClientRequest * cname_request = cname_dnssec_context->me;
393 	require_action_quiet(cname_request == &dnssec_context->request_to_follow_cname, exit, stop_immediately = mDNStrue;
394 		log_debug("cname request does not points back to the request_to_follow_cname"));
395 	QueryRecordOpStopForClientRequest(&cname_request->op);
396 	stop_dnssec(cname_request);
397 	dnssec_context->subtask_dnssec_context = mDNSNULL;
398 
399 exit:
400 	return stop_immediately;
401 }
402 
403 #endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
404