rustsec-2026-0106
Vulnerability from osv_rustsec
The Hickory DNS project's experimental hickory-recursor crate's record cache
(DnsLru) stores records from DNS responses keyed by each record's own (name,
type), not by the query that triggered the response. cache_response() in
crates/recursor/src/lib.rs chains ANSWER, AUTHORITY, and ADDITIONAL
sections into one record iterator before insertion. The bailiwick filter it
applies uses the zone context of the NS pool that serviced the lookup, not the
zone being queried.
This creates a cross-zone poisoning path. When Hickory builds the NS pool for
attacker.poc. it uses the parent poc. NS pool (ns.zone() = "poc."). If
the poc. nameserver under the attacker's control includes in its response's
AUTHORITY section a record for a sibling zone like victim.poc. NS
ns.evil.poc., the bailiwick check is_subzone("poc.", "victim.poc.") passes
(victim.poc. is a subdomain of poc.). The record is stored under
(victim.poc., NS) in the shared cache.
Subsequently, any client querying a name in victim.poc. causes Hickory to
build its NS pool from the poisoned cache entry, routing queries to the
attacker's nameserver (ns.evil.poc.) rather than to the legitimate nameserver
for victim.poc.. The legitimate NS for that zone receives zero queries.
This issue is fixed in hickory-resolver 0.26.0 with the recursor feature
through an architectural change to response-level caching: responses are stored
keyed by the originating query (name, type). A response to (attacker.poc.
NS) is stored only under that key and cannot affect the (victim.poc., NS)
cache entry.
We believe this issue has been present in all published versions of the
experimental hickory-recursor crate, which has now been folded into the
hickory-resolver crate under the non-default recursor feature flag. The
hickory-recursor crate will not receive any updates going forward and all
users should migrate to hickory-resolver with the recursor feature.
| URL | Type | |
|---|---|---|
{
"affected": [
{
"database_specific": {
"categories": [
"privilege-escalation"
],
"cvss": null,
"informational": null
},
"ecosystem_specific": {
"affected_functions": null,
"affects": {
"arch": [],
"functions": [],
"os": []
}
},
"package": {
"ecosystem": "crates.io",
"name": "hickory-recursor",
"purl": "pkg:cargo/hickory-recursor"
},
"ranges": [
{
"events": [
{
"introduced": "0.0.0-0"
}
],
"type": "SEMVER"
}
],
"versions": []
}
],
"aliases": [],
"database_specific": {
"license": "CC-BY-4.0"
},
"details": "The Hickory DNS project\u0027s experimental `hickory-recursor` crate\u0027s record cache\n(`DnsLru`) stores records from DNS responses keyed by each record\u0027s own (name,\ntype), not by the query that triggered the response. `cache_response()` in\n`crates/recursor/src/lib.rs` chains `ANSWER`, `AUTHORITY`, and `ADDITIONAL`\nsections into one record iterator before insertion. The bailiwick filter it\napplies uses the zone context of the NS pool that serviced the lookup, not the\nzone being queried.\n\nThis creates a cross-zone poisoning path. When Hickory builds the NS pool for\n`attacker.poc.` it uses the parent `poc.` `NS` pool (`ns.zone() = \"poc.\"`). If\nthe `poc.` nameserver under the attacker\u0027s control includes in its response\u0027s\n`AUTHORITY` section a record for a sibling zone like `victim.poc. NS\nns.evil.poc.`, the bailiwick check `is_subzone(\"poc.\", \"victim.poc.\")` passes\n(`victim.poc.` is a subdomain of `poc.`). The record is stored under\n`(victim.poc., NS)` in the shared cache.\n\nSubsequently, any client querying a name in `victim.poc`. causes Hickory to\nbuild its NS pool from the poisoned cache entry, routing queries to the\nattacker\u0027s nameserver (`ns.evil.poc.`) rather than to the legitimate nameserver\nfor `victim.poc.`. The legitimate `NS` for that zone receives zero queries.\n\nThis issue is fixed in `hickory-resolver` 0.26.0 with the `recursor` feature\nthrough an architectural change to response-level caching: responses are stored\nkeyed by the originating query `(name, type)`. A response to `(attacker.poc.\nNS)` is stored only under that key and cannot affect the `(victim.poc., NS)`\ncache entry.\n\nWe believe this issue has been present in all published versions of the\nexperimental `hickory-recursor` crate, which has now been folded into the\n`hickory-resolver` crate under the non-default `recursor` feature flag. The\n`hickory-recursor` crate will not receive any updates going forward and all\nusers should migrate to `hickory-resolver` with the `recursor` feature.",
"id": "RUSTSEC-2026-0106",
"modified": "2026-04-22T19:53:31Z",
"published": "2026-04-22T12:00:00Z",
"references": [
{
"type": "PACKAGE",
"url": "https://crates.io/crates/hickory-recursor"
},
{
"type": "ADVISORY",
"url": "https://rustsec.org/advisories/RUSTSEC-2026-0106.html"
},
{
"type": "ADVISORY",
"url": "https://github.com/hickory-dns/hickory-dns/security/advisories/GHSA-83hf-93m4-rgwq"
}
],
"related": [],
"severity": [],
"summary": "Record cache accepts AUTHORITY section NS from sibling zone via parent-pool zone-context elevation"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.