new file mode 100644
@@ -0,0 +1,151 @@
+From 97245ce2852162fbf19cc23b016ee73fe2aec63c Mon Sep 17 00:00:00 2001
+From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
+Date: Wed, 22 Jul 2026 10:07:52 +0200
+Subject: [PATCH] - Fix CVE-2026-46582, A wildcard replay, as another piece of
+ data, triggers poisoning in the serve expired reply path. Thanks to Qifan
+ Zhang, Palo Alto Networks, for the report.
+
+(cherry picked from commit fea0ff550bb6193417c9b17ffff409eb6736f90d)
+
+CVE: CVE-2026-46582
+Upstream-Status: Backport [https://github.com/NLnetLabs/unbound/commit/fea0ff550bb6193417c9b17ffff409eb6736f90d]
+
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ validator/val_utils.c | 15 ++++++++++++---
+ validator/validator.c | 31 ++++++++++++++++++++++++++++++-
+ 2 files changed, 42 insertions(+), 4 deletions(-)
+
+diff --git a/validator/val_utils.c b/validator/val_utils.c
+index 4495695ac..87c5a034a 100644
+--- a/validator/val_utils.c
++++ b/validator/val_utils.c
+@@ -439,10 +439,15 @@ val_verify_rrset(struct module_env* env, struct val_env* ve,
+ * only improves security status
+ * and bogus is set only once, even if we rechecked the status */
+ if(sec > d->security) {
++ int wc_expanded = 0;
+ d->security = sec;
+- if(sec == sec_status_secure)
++ if(sec == sec_status_secure) {
++ uint8_t* wc = NULL;
++ size_t wclen = 0;
+ d->trust = rrset_trust_validated;
+- else if(sec == sec_status_bogus) {
++ if(val_rrset_wildcard(rrset, &wc, &wclen) && wc)
++ wc_expanded = 1;
++ } else if(sec == sec_status_bogus) {
+ size_t i;
+ /* update ttl for rrset to fixed value. */
+ d->ttl = ve->bogus_ttl;
+@@ -455,7 +460,11 @@ val_verify_rrset(struct module_env* env, struct val_env* ve,
+ lock_basic_unlock(&ve->bogus_lock);
+ }
+ /* if status updated - store in cache for reuse */
+- rrset_update_sec_status(env->rrset_cache, rrset, *env->now);
++ /* For a wildcard rrset, that is secure, do not store this
++ * into the cache, because it changes proofs around the
++ * item. */
++ if(!wc_expanded)
++ rrset_update_sec_status(env->rrset_cache, rrset, *env->now);
+ }
+
+ return sec;
+diff --git a/validator/validator.c b/validator/validator.c
+index 5817fc808..68c4bf643 100644
+--- a/validator/validator.c
++++ b/validator/validator.c
+@@ -1013,6 +1013,9 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
+ size_t wl;
+ int wc_cached = 0;
+ int wc_NSEC_ok = 0;
++ /* This is used to update the RRset cache, with the combination
++ * of the dname expansion and this wildcard, for security status. */
++ struct ub_packed_rrset_key* wc_rrset = NULL;
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
+@@ -1031,6 +1034,9 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
+ ntohs(s->rk.type), ntohs(s->rk.rrset_class));
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ if(wc_rrset)
++ ((struct packed_rrset_data*)wc_rrset->
++ entry.data)->security = sec_status_bogus;
+ return;
+ }
+ if(wc && !wc_cached && env->cfg->aggressive_nsec) {
+@@ -1038,7 +1044,7 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
+ env->alloc, *env->now);
+ wc_cached = 1;
+ }
+-
++ if(wc) wc_rrset = s;
+ }
+
+ /* validate the AUTHORITY section as well - this will generally be
+@@ -1095,6 +1101,9 @@ validate_positive_response(struct module_env* env, struct val_env* ve,
+ "did not exist");
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ if(wc_rrset)
++ ((struct packed_rrset_data*)wc_rrset->
++ entry.data)->security = sec_status_bogus;
+ return;
+ }
+
+@@ -1496,6 +1505,16 @@ validate_any_response(struct module_env* env, struct val_env* ve,
+ "did not exist");
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ /* Make the expanded name and wildcard RRSIG rrsets bogus */
++ for(i=0; i<chase_reply->an_numrrsets; i++) {
++ uint8_t* cwc = NULL;
++ size_t cwl = 0;
++ s = chase_reply->rrsets[i];
++ if(val_rrset_wildcard(s, &cwc, &cwl) && cwc) {
++ ((struct packed_rrset_data*)s->
++ entry.data)->security = sec_status_bogus;
++ }
++ }
+ return;
+ }
+
+@@ -1533,6 +1552,9 @@ validate_cname_response(struct module_env* env, struct val_env* ve,
+ uint8_t* wc = NULL;
+ size_t wl;
+ int wc_NSEC_ok = 0;
++ /* This is used to update the RRset cache, with the combination
++ * of the dname expansion and this wildcard, for security status. */
++ struct ub_packed_rrset_key* wc_rrset = NULL;
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
+@@ -1553,6 +1575,7 @@ validate_cname_response(struct module_env* env, struct val_env* ve,
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ return;
+ }
++ if(wc) wc_rrset = s;
+
+ /* Refuse wildcarded DNAMEs rfc 4597.
+ * Do not follow a wildcarded DNAME because
+@@ -1564,6 +1587,9 @@ validate_cname_response(struct module_env* env, struct val_env* ve,
+ ntohs(s->rk.type), ntohs(s->rk.rrset_class));
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ if(wc_rrset)
++ ((struct packed_rrset_data*)wc_rrset->
++ entry.data)->security = sec_status_bogus;
+ return;
+ }
+
+@@ -1628,6 +1654,9 @@ validate_cname_response(struct module_env* env, struct val_env* ve,
+ "did not exist");
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ if(wc_rrset)
++ ((struct packed_rrset_data*)wc_rrset->
++ entry.data)->security = sec_status_bogus;
+ return;
+ }
+
@@ -23,6 +23,7 @@ SRC_URI = "git://github.com/NLnetLabs/unbound.git;protocol=https;branch=master;t
file://CVE-2026-42960.patch \
file://CVE-2026-44390.patch \
file://CVE-2026-44608.patch \
+ file://CVE-2026-46582.patch \
"
SRCREV = "f6269baa605d31859f28770e01a24e3677e5f82c"