From f14b78532e210f22e94f2ab9697591d2c7bef039 Mon Sep 17 00:00:00 2001 From: jiabowen Date: Wed, 3 Jun 2026 08:30:12 +0000 Subject: [PATCH] fix(dnssec): validate short RSA DNSKEY blobs Reject malformed RSA DNSKEY data before reading the extended exponent header, and add a regression test. Co-developed-by: GitHub Copilot (GPT 5.5) Changes: - Add debian/patches/fix-dnssec-validate-short-rsa-dnskey.patch - Modify debian/patches/series - Modify debian/changelog Upstream: https://github.com/systemd/systemd/commit/004401fd061b222f4ee374263f5fa65d08609fc4 Generated-By: glm-5-turbo Co-Authored-By: jiabowen --- debian/changelog | 6 + ...fix-dnssec-validate-short-rsa-dnskey.patch | 108 ++++++++++++++++++ debian/patches/series | 1 + 3 files changed, 115 insertions(+) create mode 100644 debian/patches/fix-dnssec-validate-short-rsa-dnskey.patch diff --git a/debian/changelog b/debian/changelog index 5c960842..f327b085 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +systemd (255.2-4deepin31) unstable; urgency=medium + + * Fix DNSSEC: validate short RSA DNSKEY blobs to prevent out-of-bounds read + + -- jiabowen Wed, 03 Jun 2026 14:22:00 +0800 + systemd (255.2-4deepin30) unstable; urgency=medium * Fix tmpfiles x11 socket age-based cleanup causing unexpected removal diff --git a/debian/patches/fix-dnssec-validate-short-rsa-dnskey.patch b/debian/patches/fix-dnssec-validate-short-rsa-dnskey.patch new file mode 100644 index 00000000..645d6ddf --- /dev/null +++ b/debian/patches/fix-dnssec-validate-short-rsa-dnskey.patch @@ -0,0 +1,108 @@ +diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c +index 017c370b0e..f33bba28ff 100644 +--- a/src/resolve/resolved-dns-dnssec.c ++++ b/src/resolve/resolved-dns-dnssec.c +@@ -141,10 +141,15 @@ static int dnssec_rsa_verify( + assert(rrsig); + assert(dnskey); + ++ if (dnskey->dnskey.key_size < 1) ++ return -EINVAL; ++ + if (*(uint8_t*) dnskey->dnskey.key == 0) { + /* exponent is > 255 bytes long */ + +- exponent = (uint8_t*) dnskey->dnskey.key + 3; ++ if (dnskey->dnskey.key_size < 3) ++ return -EINVAL; ++ + exponent_size = + ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | + ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); +@@ -155,13 +160,12 @@ static int dnssec_rsa_verify( + if (3 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + ++ exponent = (uint8_t*) dnskey->dnskey.key + 3; + modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; + + } else { + /* exponent is <= 255 bytes long */ +- +- exponent = (uint8_t*) dnskey->dnskey.key + 1; + exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; + + if (exponent_size <= 0) +@@ -170,6 +174,7 @@ static int dnssec_rsa_verify( + if (1 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + ++ exponent = (uint8_t*) dnskey->dnskey.key + 1; + modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + } +diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c +index eaa4ef9959..f6ea6214f4 100644 +--- a/src/resolve/test-dnssec.c ++++ b/src/resolve/test-dnssec.c +@@ -521,6 +521,59 @@ TEST(dnssec_verify_rrset) { + assert_se(result == DNSSEC_VALIDATED); + } + ++TEST(dnssec_verify_rrset_invalid_rsa_dnskey) { ++ static const uint8_t signature_blob[] = { 0x00 }; ++ static const uint8_t dnskey_blob[] = { 0x00 }; ++ ++ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; ++ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; ++ DnssecResult result; ++ ++ ASSERT_NOT_NULL(a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "example.com.")); ++ ++ a->a.in_addr.s_addr = inet_addr("192.0.2.1"); ++ ++ ASSERT_NOT_NULL(dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "example.com.")); ++ ++ dnskey->dnskey.flags = DNSKEY_FLAG_ZONE_KEY; ++ dnskey->dnskey.protocol = 3; ++ dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; ++ dnskey->dnskey.key_size = sizeof(dnskey_blob); ++ ASSERT_NOT_NULL(dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob))); ++ ++ ASSERT_NOT_NULL(rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "example.com.")); ++ ++ rrsig->rrsig.type_covered = DNS_TYPE_A; ++ rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; ++ rrsig->rrsig.labels = 2; ++ rrsig->rrsig.original_ttl = 3600; ++ rrsig->rrsig.expiration = 2000000000; ++ rrsig->rrsig.inception = 1000000000; ++ rrsig->rrsig.key_tag = dnssec_keytag(dnskey, false); ++ ASSERT_NOT_NULL(rrsig->rrsig.signer = strdup("example.com.")); ++ rrsig->rrsig.signature_size = sizeof(signature_blob); ++ ASSERT_NOT_NULL(rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size)); ++ ++ ASSERT_GT(dnssec_key_match_rrsig(a->key, rrsig), 0); ++ ASSERT_GT(dnssec_rrsig_match_dnskey(rrsig, dnskey, false), 0); ++ ++ ASSERT_NOT_NULL(answer = dns_answer_new(1)); ++ ASSERT_OK(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED, NULL)); ++ ++ ASSERT_ERROR(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, ++ rrsig->rrsig.inception * USEC_PER_SEC, &result), ++ EINVAL); ++ ++ dnskey->dnskey.key = mfree(dnskey->dnskey.key); ++ dnskey->dnskey.key_size = 0; ++ rrsig->rrsig.key_tag = dnssec_keytag(dnskey, false); ++ ++ ASSERT_GT(dnssec_rrsig_match_dnskey(rrsig, dnskey, false), 0); ++ ASSERT_ERROR(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, ++ rrsig->rrsig.inception * USEC_PER_SEC, &result), ++ EINVAL); ++} ++ + TEST(dnssec_verify_rrset2) { + static const uint8_t signature_blob[] = { + 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, diff --git a/debian/patches/series b/debian/patches/series index 4b002b6d..0346d5ab 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -42,3 +42,4 @@ hwdb-reject-oob-fnmatch.patch exec-invoke-chdir-after-chroot.patch uniontech-skip-clock-restore-for-timesyncd.patch fix-tmpfiles-x11-cleanup.patch +fix-dnssec-validate-short-rsa-dnskey.patch