2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-31 14:35:26 +00:00

[9.20] fix: usr: Recently expired records could be returned with timestamp in future

Under rare circumstances, the RRSet that expired at the time of
the query could be returned with TTL far in the future.  This
has been fixed.

As a side-effect, the expiration time of expired RRSets are no
longer printed out in the cache dump.

Closes #5094

Backport of MR !10048

Merge branch 'backport-5094-fix-timestamp-in-ttl-9.20' into 'bind-9.20'

See merge request isc-projects/bind9!10059
This commit is contained in:
Ondřej Surý
2025-02-03 15:05:48 +00:00
6 changed files with 34 additions and 50 deletions

View File

@@ -1649,7 +1649,7 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
# Check that expired records are not dumped.
ret=0
grep "; expired since .* (awaiting cleanup)" ns5/named_dump.db.test$n && ret=1
grep "; expired (awaiting cleanup)" ns5/named_dump.db.test$n && ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@@ -1665,13 +1665,13 @@ status=$((status + ret))
echo_i "check rndc dump expired data.example ($n)"
ret=0
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired since .* (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
| grep "; expired (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired since .* (awaiting cleanup) nodata\.example\." >/dev/null 2>&1 || ret=1
| grep "; expired (awaiting cleanup) nodata\.example\." >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired since .* (awaiting cleanup) nxdomain\.example\." >/dev/null 2>&1 || ret=1
| grep "; expired (awaiting cleanup) nxdomain\.example\." >/dev/null 2>&1 || ret=1
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; expired since .* (awaiting cleanup) othertype\.example\." >/dev/null 2>&1 || ret=1
| grep "; expired (awaiting cleanup) othertype\.example\." >/dev/null 2>&1 || ret=1
# Also make sure the not expired data does not have an expired comment.
awk '/; authanswer/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
| grep "; authanswer longttl\.example.*A text record with a 600 second ttl" >/dev/null 2>&1 || ret=1

View File

@@ -1164,15 +1164,7 @@ again:
if (STALE(rds)) {
fprintf(f, "; stale\n");
} else if (ANCIENT(rds)) {
isc_buffer_t b;
char buf[sizeof("YYYYMMDDHHMMSS")];
memset(buf, 0, sizeof(buf));
isc_buffer_init(&b, buf, sizeof(buf) - 1);
dns_time64_totext((uint64_t)rds->ttl, &b);
fprintf(f,
"; expired since %s "
"(awaiting cleanup)\n",
buf);
fprintf(f, "; expired (awaiting cleanup)\n");
}
result = dump_rdataset(mctx, name, rds, ctx, buffer, f);
if (result != ISC_R_SUCCESS) {

View File

@@ -954,15 +954,20 @@ setttl(dns_slabheader_t *header, dns_ttl_t newttl) {
}
}
static void
mark_ancient(dns_slabheader_t *header) {
setttl(header, 0);
mark(header, DNS_SLABHEADERATTR_ANCIENT);
HEADERNODE(header)->dirty = 1;
}
/*
* Caller must hold the node (write) lock.
*/
static void
expireheader(dns_slabheader_t *header, isc_rwlocktype_t *nlocktypep,
isc_rwlocktype_t *tlocktypep, dns_expire_t reason DNS__DB_FLARG) {
setttl(header, 0);
mark(header, DNS_SLABHEADERATTR_ANCIENT);
HEADERNODE(header)->dirty = 1;
mark_ancient(header);
if (isc_refcount_current(&HEADERNODE(header)->erefs) == 0) {
qpcache_t *qpdb = (qpcache_t *)header->db;
@@ -1058,7 +1063,7 @@ bindrdataset(qpcache_t *qpdb, qpcnode_t *node, dns_slabheader_t *header,
* (these records should not be cached anyway).
*/
if (KEEPSTALE(qpdb) && stale_ttl > now) {
if (!ZEROTTL(header) && KEEPSTALE(qpdb) && stale_ttl > now) {
stale = true;
} else {
/*
@@ -1073,6 +1078,7 @@ bindrdataset(qpcache_t *qpdb, qpcnode_t *node, dns_slabheader_t *header,
rdataset->rdclass = qpdb->common.rdclass;
rdataset->type = DNS_TYPEPAIR_TYPE(header->type);
rdataset->covers = DNS_TYPEPAIR_COVERS(header->type);
rdataset->ttl = !ZEROTTL(header) ? header->ttl - now : 0;
rdataset->ttl = header->ttl - now;
rdataset->trust = header->trust;
rdataset->resign = 0;
@@ -1103,7 +1109,7 @@ bindrdataset(qpcache_t *qpdb, qpcnode_t *node, dns_slabheader_t *header,
rdataset->attributes |= DNS_RDATASETATTR_STALE;
} else if (!ACTIVE(header, now)) {
rdataset->attributes |= DNS_RDATASETATTR_ANCIENT;
rdataset->ttl = header->ttl;
rdataset->ttl = 0;
}
rdataset->count = atomic_fetch_add_relaxed(&header->count, 1);
@@ -1270,8 +1276,7 @@ check_stale_header(qpcnode_t *node, dns_slabheader_t *header,
}
dns_slabheader_destroy(&header);
} else {
mark(header, DNS_SLABHEADERATTR_ANCIENT);
HEADERNODE(header)->dirty = 1;
mark_ancient(header);
*header_prev = header;
}
} else {
@@ -2233,8 +2238,7 @@ findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
* non-zero. This is so because 'node' is an
* argument to the function.
*/
mark(header, DNS_SLABHEADERATTR_ANCIENT);
HEADERNODE(header)->dirty = 1;
mark_ancient(header);
}
} else if (EXISTS(header) && !ANCIENT(header)) {
if (header->type == matchtype) {
@@ -2585,13 +2589,6 @@ qpdb_destroy(dns_db_t *arg) {
qpcache_detach(&qpdb);
}
static void
mark_ancient(dns_slabheader_t *header) {
setttl(header, 0);
mark(header, DNS_SLABHEADERATTR_ANCIENT);
HEADERNODE(header)->dirty = 1;
}
/*%
* Clean up dead nodes. These are nodes which have no references, and
* have no data. They are dead but we could not or chose not to delete
@@ -3162,7 +3159,6 @@ find_header:
newheader->next = topheader->next;
newheader->down = topheader;
topheader->next = newheader;
qpnode->dirty = 1;
mark_ancient(header);
if (sigheader != NULL) {
mark_ancient(sigheader);

View File

@@ -417,9 +417,7 @@ check_stale_header(dns_rbtnode_t *node, dns_slabheader_t *header,
}
dns_slabheader_destroy(&header);
} else {
dns__rbtdb_mark(header,
DNS_SLABHEADERATTR_ANCIENT);
RBTDB_HEADERNODE(header)->dirty = 1;
dns__rbtdb_mark_ancient(header);
*header_prev = header;
}
} else {
@@ -1401,9 +1399,7 @@ cache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
* non-zero. This is so because 'node' is an
* argument to the function.
*/
dns__rbtdb_mark(header,
DNS_SLABHEADERATTR_ANCIENT);
RBTDB_HEADERNODE(header)->dirty = 1;
dns__rbtdb_mark_ancient(header);
}
} else if (EXISTS(header) && !ANCIENT(header)) {
if (header->type == matchtype) {
@@ -1589,9 +1585,7 @@ void
dns__cacherbt_expireheader(dns_slabheader_t *header,
isc_rwlocktype_t *tlocktypep,
dns_expire_t reason DNS__DB_FLARG) {
dns__rbtdb_setttl(header, 0);
dns__rbtdb_mark(header, DNS_SLABHEADERATTR_ANCIENT);
RBTDB_HEADERNODE(header)->dirty = 1;
dns__rbtdb_mark_ancient(header);
if (isc_refcount_current(&RBTDB_HEADERNODE(header)->references) == 0) {
isc_rwlocktype_t nlocktype = isc_rwlocktype_write;

View File

@@ -857,8 +857,8 @@ dns__rbtdb_mark(dns_slabheader_t *header, uint_least16_t flag) {
}
}
static void
mark_ancient(dns_slabheader_t *header) {
void
dns__rbtdb_mark_ancient(dns_slabheader_t *header) {
dns__rbtdb_setttl(header, 0);
dns__rbtdb_mark(header, DNS_SLABHEADERATTR_ANCIENT);
RBTDB_HEADERNODE(header)->dirty = 1;
@@ -2150,7 +2150,7 @@ dns__rbtdb_bindrdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
* (these records should not be cached anyway).
*/
if (KEEPSTALE(rbtdb) && stale_ttl > now) {
if (!ZEROTTL(header) && KEEPSTALE(rbtdb) && stale_ttl > now) {
stale = true;
} else {
/*
@@ -2165,7 +2165,7 @@ dns__rbtdb_bindrdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
rdataset->rdclass = rbtdb->common.rdclass;
rdataset->type = DNS_TYPEPAIR_TYPE(header->type);
rdataset->covers = DNS_TYPEPAIR_COVERS(header->type);
rdataset->ttl = header->ttl - now;
rdataset->ttl = !ZEROTTL(header) ? header->ttl - now : 0;
rdataset->trust = header->trust;
if (NEGATIVE(header)) {
@@ -2194,7 +2194,7 @@ dns__rbtdb_bindrdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
rdataset->attributes |= DNS_RDATASETATTR_STALE;
} else if (IS_CACHE(rbtdb) && !ACTIVE(header, now)) {
rdataset->attributes |= DNS_RDATASETATTR_ANCIENT;
rdataset->ttl = header->ttl;
rdataset->ttl = 0;
}
rdataset->count = atomic_fetch_add_relaxed(&header->count, 1);
@@ -2604,7 +2604,7 @@ dns__rbtdb_add(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode,
topheader != NULL;
topheader = topheader->next)
{
mark_ancient(topheader);
dns__rbtdb_mark_ancient(topheader);
}
goto find_header;
}
@@ -2667,7 +2667,7 @@ dns__rbtdb_add(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode,
* The new rdataset is better. Expire the
* ncache entry.
*/
mark_ancient(topheader);
dns__rbtdb_mark_ancient(topheader);
topheader = NULL;
goto find_header;
}
@@ -3013,9 +3013,9 @@ find_header:
changed->dirty = true;
}
if (rbtversion == NULL) {
mark_ancient(header);
dns__rbtdb_mark_ancient(header);
if (sigheader != NULL) {
mark_ancient(sigheader);
dns__rbtdb_mark_ancient(sigheader);
}
}
if (rbtversion != NULL && !header_nx) {
@@ -3119,7 +3119,7 @@ find_header:
expireheader = newheader;
}
mark_ancient(expireheader);
dns__rbtdb_mark_ancient(expireheader);
/*
* FIXME: In theory, we should mark the RRSIG
* and the header at the same time, but there is

View File

@@ -486,6 +486,8 @@ dns__zonerbt_addwildcards(dns_rbtdb_t *rbtdb, const dns_name_t *name,
* Cache-specific functions that are called from rbtdb.c
*/
void
dns__rbtdb_mark_ancient(dns_slabheader_t *header);
void
dns__cacherbt_expireheader(dns_slabheader_t *header,
isc_rwlocktype_t *tlocktypep,
dns_expire_t reason DNS__DB_FLARG);