Coverage Report

Created: 2026-10-28 06:55

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