Coverage Report

Created: 2025-11-24 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/dns/nsec.c
Line
Count
Source
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3
 *
4
 * SPDX-License-Identifier: MPL-2.0
5
 *
6
 * This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9
 *
10
 * See the COPYRIGHT file distributed with this work for additional
11
 * information regarding copyright ownership.
12
 */
13
14
/*! \file */
15
16
#include <stdbool.h>
17
18
#include <isc/log.h>
19
#include <isc/result.h>
20
#include <isc/string.h>
21
#include <isc/util.h>
22
23
#include <dns/db.h>
24
#include <dns/nsec.h>
25
#include <dns/rdata.h>
26
#include <dns/rdatalist.h>
27
#include <dns/rdataset.h>
28
#include <dns/rdatasetiter.h>
29
#include <dns/rdatastruct.h>
30
31
#include <dst/dst.h>
32
33
void
34
0
dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit) {
35
0
  unsigned int shift, mask;
36
37
0
  shift = 7 - (type % 8);
38
0
  mask = 1 << shift;
39
40
0
  if (bit != 0) {
41
0
    array[type / 8] |= mask;
42
0
  } else {
43
0
    array[type / 8] &= (~mask & 0xFF);
44
0
  }
45
0
}
46
47
bool
48
0
dns_nsec_isset(const unsigned char *array, unsigned int type) {
49
0
  unsigned int byte, shift, mask;
50
51
0
  byte = array[type / 8];
52
0
  shift = 7 - (type % 8);
53
0
  mask = 1 << shift;
54
55
0
  return (byte & mask) != 0;
56
0
}
57
58
unsigned int
59
dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw,
60
0
      unsigned int max_type) {
61
0
  unsigned char *start = map;
62
0
  unsigned int window;
63
0
  int octet;
64
65
0
  if (raw == NULL) {
66
0
    return 0;
67
0
  }
68
69
0
  for (window = 0; window < 256; window++) {
70
0
    if (window * 256 > max_type) {
71
0
      break;
72
0
    }
73
0
    for (octet = 31; octet >= 0; octet--) {
74
0
      if (*(raw + octet) != 0) {
75
0
        break;
76
0
      }
77
0
    }
78
0
    if (octet < 0) {
79
0
      raw += 32;
80
0
      continue;
81
0
    }
82
0
    *map++ = window;
83
0
    *map++ = octet + 1;
84
    /*
85
     * Note: potential overlapping move.
86
     */
87
0
    memmove(map, raw, octet + 1);
88
0
    map += octet + 1;
89
0
    raw += 32;
90
0
  }
91
0
  return (unsigned int)(map - start);
92
0
}
93
94
isc_result_t
95
dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
96
        const dns_name_t *target, unsigned char *buffer,
97
0
        dns_rdata_t *rdata) {
98
0
  isc_result_t result;
99
0
  isc_region_t r;
100
0
  unsigned int i;
101
0
  unsigned char *nsec_bits, *bm;
102
0
  unsigned int max_type;
103
0
  dns_rdatasetiter_t *rdsiter;
104
105
0
  REQUIRE(target != NULL);
106
107
0
  memset(buffer, 0, DNS_NSEC_BUFFERSIZE);
108
0
  dns_name_toregion(target, &r);
109
0
  memmove(buffer, r.base, r.length);
110
0
  r.base = buffer;
111
  /*
112
   * Use the end of the space for a raw bitmap leaving enough
113
   * space for the window identifiers and length octets.
114
   */
115
0
  bm = r.base + r.length + 512;
116
0
  nsec_bits = r.base + r.length;
117
0
  dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1);
118
0
  dns_nsec_setbit(bm, dns_rdatatype_nsec, 1);
119
0
  max_type = dns_rdatatype_nsec;
120
0
  rdsiter = NULL;
121
0
  result = dns_db_allrdatasets(db, node, version, 0, 0, &rdsiter);
122
0
  if (result != ISC_R_SUCCESS) {
123
0
    return result;
124
0
  }
125
0
  DNS_RDATASETITER_FOREACH(rdsiter) {
126
0
    dns_rdataset_t rdataset = DNS_RDATASET_INIT;
127
0
    dns_rdatasetiter_current(rdsiter, &rdataset);
128
0
    if (!dns_rdatatype_isnsec(rdataset.type) &&
129
0
        rdataset.type != dns_rdatatype_rrsig)
130
0
    {
131
0
      if (rdataset.type > max_type) {
132
0
        max_type = rdataset.type;
133
0
      }
134
0
      dns_nsec_setbit(bm, rdataset.type, 1);
135
0
    }
136
0
    dns_rdataset_disassociate(&rdataset);
137
0
  }
138
0
  dns_rdatasetiter_destroy(&rdsiter);
139
140
  /*
141
   * At zone cuts, deny the existence of glue in the parent zone.
142
   */
143
0
  if (dns_nsec_isset(bm, dns_rdatatype_ns) &&
144
0
      !dns_nsec_isset(bm, dns_rdatatype_soa))
145
0
  {
146
0
    for (i = 0; i <= max_type; i++) {
147
0
      if (dns_nsec_isset(bm, i) &&
148
0
          !dns_rdatatype_iszonecutauth((dns_rdatatype_t)i))
149
0
      {
150
0
        dns_nsec_setbit(bm, i, 0);
151
0
      }
152
0
    }
153
0
  }
154
155
0
  nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type);
156
157
0
  r.length = (unsigned int)(nsec_bits - r.base);
158
0
  INSIST(r.length <= DNS_NSEC_BUFFERSIZE);
159
0
  dns_rdata_fromregion(rdata, dns_db_class(db), dns_rdatatype_nsec, &r);
160
161
0
  return ISC_R_SUCCESS;
162
0
}
163
164
isc_result_t
165
dns_nsec_build(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node,
166
0
         const dns_name_t *target, dns_ttl_t ttl) {
167
0
  isc_result_t result;
168
0
  dns_rdata_t rdata = DNS_RDATA_INIT;
169
0
  unsigned char data[DNS_NSEC_BUFFERSIZE];
170
0
  dns_rdatalist_t rdatalist;
171
0
  dns_rdataset_t rdataset;
172
173
0
  dns_rdataset_init(&rdataset);
174
0
  dns_rdata_init(&rdata);
175
176
0
  result = dns_nsec_buildrdata(db, version, node, target, data, &rdata);
177
0
  if (result != ISC_R_SUCCESS) {
178
0
    goto failure;
179
0
  }
180
181
0
  dns_rdatalist_init(&rdatalist);
182
0
  rdatalist.rdclass = dns_db_class(db);
183
0
  rdatalist.type = dns_rdatatype_nsec;
184
0
  rdatalist.ttl = ttl;
185
0
  ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
186
0
  dns_rdatalist_tordataset(&rdatalist, &rdataset);
187
0
  result = dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL);
188
0
  if (result == DNS_R_UNCHANGED) {
189
0
    result = ISC_R_SUCCESS;
190
0
  }
191
192
0
failure:
193
0
  if (dns_rdataset_isassociated(&rdataset)) {
194
0
    dns_rdataset_disassociate(&rdataset);
195
0
  }
196
0
  return result;
197
0
}
198
199
bool
200
0
dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type) {
201
0
  dns_rdata_nsec_t nsecstruct;
202
0
  isc_result_t result;
203
0
  bool present;
204
0
  unsigned int i, len, window;
205
206
0
  REQUIRE(nsec != NULL);
207
0
  REQUIRE(nsec->type == dns_rdatatype_nsec);
208
209
  /* This should never fail */
210
0
  result = dns_rdata_tostruct(nsec, &nsecstruct, NULL);
211
0
  INSIST(result == ISC_R_SUCCESS);
212
213
0
  present = false;
214
0
  for (i = 0; i < nsecstruct.len; i += len) {
215
0
    INSIST(i + 2 <= nsecstruct.len);
216
0
    window = nsecstruct.typebits[i];
217
0
    len = nsecstruct.typebits[i + 1];
218
0
    INSIST(len > 0 && len <= 32);
219
0
    i += 2;
220
0
    INSIST(i + len <= nsecstruct.len);
221
0
    if (window * 256 > type) {
222
0
      break;
223
0
    }
224
0
    if ((window + 1) * 256 <= type) {
225
0
      continue;
226
0
    }
227
0
    if (type < (window * 256) + len * 8) {
228
0
      present = dns_nsec_isset(&nsecstruct.typebits[i],
229
0
             type % 256);
230
0
    }
231
0
    break;
232
0
  }
233
0
  dns_rdata_freestruct(&nsecstruct);
234
0
  return present;
235
0
}
236
237
isc_result_t
238
dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff,
239
0
      bool *answer) {
240
0
  dns_dbnode_t *node = NULL;
241
0
  dns_rdataset_t rdataset;
242
0
  dns_rdata_dnskey_t dnskey;
243
0
  isc_result_t result;
244
245
0
  REQUIRE(answer != NULL);
246
247
0
  dns_rdataset_init(&rdataset);
248
249
0
  result = dns_db_getoriginnode(db, &node);
250
0
  if (result != ISC_R_SUCCESS) {
251
0
    return result;
252
0
  }
253
254
0
  result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, 0,
255
0
             0, &rdataset, NULL);
256
0
  dns_db_detachnode(&node);
257
258
0
  if (result == ISC_R_NOTFOUND) {
259
0
    *answer = false;
260
0
  }
261
0
  if (result != ISC_R_SUCCESS) {
262
0
    return result;
263
0
  }
264
0
  bool matched = false;
265
0
  DNS_RDATASET_FOREACH(&rdataset) {
266
0
    dns_rdata_t rdata = DNS_RDATA_INIT;
267
268
0
    dns_rdataset_current(&rdataset, &rdata);
269
0
    result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
270
0
    RUNTIME_CHECK(result == ISC_R_SUCCESS);
271
272
0
    if (dnskey.algorithm == DST_ALG_RSAMD5 ||
273
0
        dnskey.algorithm == DST_ALG_DSA ||
274
0
        dnskey.algorithm == DST_ALG_RSASHA1)
275
0
    {
276
0
      bool deleted = false;
277
0
      if (diff != NULL) {
278
0
        ISC_LIST_FOREACH(diff->tuples, tuple, link) {
279
0
          if (tuple->rdata.type !=
280
0
                dns_rdatatype_dnskey ||
281
0
              tuple->op != DNS_DIFFOP_DEL)
282
0
          {
283
0
            continue;
284
0
          }
285
286
0
          if (dns_rdata_compare(
287
0
                &rdata, &tuple->rdata) == 0)
288
0
          {
289
0
            deleted = true;
290
0
            break;
291
0
          }
292
0
        }
293
0
      }
294
295
0
      if (!deleted) {
296
0
        matched = true;
297
0
        break;
298
0
      }
299
0
    }
300
0
  }
301
0
  dns_rdataset_disassociate(&rdataset);
302
0
  *answer = matched;
303
0
  return ISC_R_SUCCESS;
304
0
}
305
306
/*%
307
 * Return ISC_R_SUCCESS if we can determine that the name doesn't exist
308
 * or we can determine whether there is data or not at the name.
309
 * If the name does not exist return the wildcard name.
310
 *
311
 * Return ISC_R_IGNORE when the NSEC is not the appropriate one.
312
 */
313
isc_result_t
314
dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
315
           const dns_name_t *nsecname, dns_rdataset_t *nsecset,
316
           bool *exists, bool *data, dns_name_t *wild,
317
0
           dns_nseclog_t logit, void *arg) {
318
0
  int order;
319
0
  dns_rdata_t rdata = DNS_RDATA_INIT;
320
0
  isc_result_t result;
321
0
  dns_namereln_t relation;
322
0
  unsigned int olabels, nlabels, labels;
323
0
  dns_rdata_nsec_t nsec;
324
0
  bool atparent;
325
0
  bool ns;
326
0
  bool soa;
327
328
0
  REQUIRE(exists != NULL);
329
0
  REQUIRE(data != NULL);
330
0
  REQUIRE(nsecset != NULL && nsecset->type == dns_rdatatype_nsec);
331
332
0
  result = dns_rdataset_first(nsecset);
333
0
  if (result != ISC_R_SUCCESS) {
334
0
    (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC set");
335
0
    return result;
336
0
  }
337
0
  dns_rdataset_current(nsecset, &rdata);
338
339
#ifdef notyet
340
  if (!dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) ||
341
      !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec))
342
  {
343
    (*logit)(arg, ISC_LOG_DEBUG(3),
344
       "NSEC missing RRSIG and/or NSEC from type map");
345
    return ISC_R_IGNORE;
346
  }
347
#endif
348
349
0
  (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC");
350
0
  relation = dns_name_fullcompare(name, nsecname, &order, &olabels);
351
352
0
  if (order < 0) {
353
    /*
354
     * The name is not within the NSEC range.
355
     */
356
0
    (*logit)(arg, ISC_LOG_DEBUG(3),
357
0
       "NSEC does not cover name, before NSEC");
358
0
    return ISC_R_IGNORE;
359
0
  }
360
361
0
  if (order == 0) {
362
    /*
363
     * The names are the same.   If we are validating "."
364
     * then atparent should not be set as there is no parent.
365
     */
366
0
    atparent = (olabels != 1) && dns_rdatatype_atparent(type);
367
0
    ns = dns_nsec_typepresent(&rdata, dns_rdatatype_ns);
368
0
    soa = dns_nsec_typepresent(&rdata, dns_rdatatype_soa);
369
0
    if (ns && !soa) {
370
0
      if (!atparent) {
371
        /*
372
         * This NSEC record is from somewhere higher in
373
         * the DNS, and at the parent of a delegation.
374
         * It can not be legitimately used here.
375
         */
376
0
        (*logit)(arg, ISC_LOG_DEBUG(3),
377
0
           "ignoring parent nsec");
378
0
        return ISC_R_IGNORE;
379
0
      }
380
0
    } else if (atparent && ns && soa) {
381
      /*
382
       * This NSEC record is from the child.
383
       * It can not be legitimately used here.
384
       */
385
0
      (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring child nsec");
386
0
      return ISC_R_IGNORE;
387
0
    }
388
0
    if (type == dns_rdatatype_cname || type == dns_rdatatype_nxt ||
389
0
        type == dns_rdatatype_nsec || type == dns_rdatatype_key ||
390
0
        !dns_nsec_typepresent(&rdata, dns_rdatatype_cname))
391
0
    {
392
0
      *exists = true;
393
0
      *data = dns_nsec_typepresent(&rdata, type);
394
0
      (*logit)(arg, ISC_LOG_DEBUG(3),
395
0
         "nsec proves name exists (owner) data=%d",
396
0
         *data);
397
0
      return ISC_R_SUCCESS;
398
0
    }
399
0
    (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC proves CNAME exists");
400
0
    return ISC_R_IGNORE;
401
0
  }
402
403
0
  if (relation == dns_namereln_subdomain &&
404
0
      dns_nsec_typepresent(&rdata, dns_rdatatype_ns) &&
405
0
      !dns_nsec_typepresent(&rdata, dns_rdatatype_soa))
406
0
  {
407
    /*
408
     * This NSEC record is from somewhere higher in
409
     * the DNS, and at the parent of a delegation or
410
     * at a DNAME.
411
     * It can not be legitimately used here.
412
     */
413
0
    (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring parent nsec");
414
0
    return ISC_R_IGNORE;
415
0
  }
416
417
0
  if (relation == dns_namereln_subdomain &&
418
0
      dns_nsec_typepresent(&rdata, dns_rdatatype_dname))
419
0
  {
420
0
    (*logit)(arg, ISC_LOG_DEBUG(3), "nsec proves covered by dname");
421
0
    *exists = false;
422
0
    return DNS_R_DNAME;
423
0
  }
424
425
0
  result = dns_rdata_tostruct(&rdata, &nsec, NULL);
426
0
  if (result != ISC_R_SUCCESS) {
427
0
    return result;
428
0
  }
429
0
  relation = dns_name_fullcompare(&nsec.next, name, &order, &nlabels);
430
0
  if (order == 0) {
431
0
    dns_rdata_freestruct(&nsec);
432
0
    (*logit)(arg, ISC_LOG_DEBUG(3),
433
0
       "ignoring nsec matches next name");
434
0
    return ISC_R_IGNORE;
435
0
  }
436
437
0
  if (order < 0 && !dns_name_issubdomain(nsecname, &nsec.next)) {
438
    /*
439
     * The name is not within the NSEC range.
440
     */
441
0
    dns_rdata_freestruct(&nsec);
442
0
    (*logit)(arg, ISC_LOG_DEBUG(3),
443
0
       "ignoring nsec because name is past end of range");
444
0
    return ISC_R_IGNORE;
445
0
  }
446
447
0
  if (order > 0 && relation == dns_namereln_subdomain) {
448
0
    (*logit)(arg, ISC_LOG_DEBUG(3),
449
0
       "nsec proves name exist (empty)");
450
0
    dns_rdata_freestruct(&nsec);
451
0
    *exists = true;
452
0
    *data = false;
453
0
    return ISC_R_SUCCESS;
454
0
  }
455
0
  if (wild != NULL) {
456
0
    dns_name_t common;
457
0
    dns_name_init(&common);
458
0
    if (olabels > nlabels) {
459
0
      labels = dns_name_countlabels(nsecname);
460
0
      dns_name_getlabelsequence(nsecname, labels - olabels,
461
0
              olabels, &common);
462
0
    } else {
463
0
      labels = dns_name_countlabels(&nsec.next);
464
0
      dns_name_getlabelsequence(&nsec.next, labels - nlabels,
465
0
              nlabels, &common);
466
0
    }
467
0
    result = dns_name_concatenate(dns_wildcardname, &common, wild);
468
0
    if (result != ISC_R_SUCCESS) {
469
0
      dns_rdata_freestruct(&nsec);
470
0
      (*logit)(arg, ISC_LOG_DEBUG(3),
471
0
         "failure generating wildcard name");
472
0
      return result;
473
0
    }
474
0
  }
475
0
  dns_rdata_freestruct(&nsec);
476
0
  (*logit)(arg, ISC_LOG_DEBUG(3), "nsec range ok");
477
0
  *exists = false;
478
0
  return ISC_R_SUCCESS;
479
0
}
480
481
bool
482
0
dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
483
0
  dns_rdataset_t rdataset = DNS_RDATASET_INIT;
484
0
  bool found = false;
485
486
0
  REQUIRE(DNS_RDATASET_VALID(nsecset));
487
0
  REQUIRE(nsecset->type == dns_rdatatype_nsec);
488
489
0
  dns_rdataset_clone(nsecset, &rdataset);
490
491
0
  DNS_RDATASET_FOREACH(&rdataset) {
492
0
    dns_rdata_t rdata = DNS_RDATA_INIT;
493
0
    dns_rdataset_current(&rdataset, &rdata);
494
0
    if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) ||
495
0
        !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig))
496
0
    {
497
0
      dns_rdataset_disassociate(&rdataset);
498
0
      return false;
499
0
    }
500
0
    found = true;
501
0
  }
502
0
  dns_rdataset_disassociate(&rdataset);
503
0
  return found;
504
0
}