diff --git a/.github/workflows/genesis-openembedded.yml b/.github/workflows/genesis-openembedded.yml index af91a32..326c776 100644 --- a/.github/workflows/genesis-openembedded.yml +++ b/.github/workflows/genesis-openembedded.yml @@ -69,6 +69,7 @@ jobs: prove -v t/goconserver_cross_build.t prove -v t/ipxe_xcat_payload.t prove -v t/mockbuild-all.t + prove -v t/net_dns_rr_types.t prove -v t/repo-lock-race.t prove -v t/nfslock.t prove -v -It/lib t/genesis_openembedded_release.t diff --git a/BUILD.md b/BUILD.md index 078e373..bc917b0 100644 --- a/BUILD.md +++ b/BUILD.md @@ -489,10 +489,14 @@ Deliberately not built for riscv64: Test::XML) are EPEL-only as well, so it can neither be built nor installed without EPEL; xCAT uses it for HP blade and VirtualBox support only. -Known differences from the EPEL-fed x86_64 repo: perl-Net-DNS is the 0.80 release of -`perl-Net-DNS/Net-DNS.spec`, built pure-perl (`--noxs`) as noarch, where EPEL 10 ships -1.47 -- its spec BuildRequires perl(Net::LibIDN2), which is EPEL-only too. A newer, -XS-free perl-Net-DNS without that BuildRequires is a follow-up. The riscv64 goconserver +perl-Net-DNS is built from `perl-Net-DNS/Net-DNS.spec` at 1.57. xCAT needs the KEY record, +which release 0.80 left to the separate Net::DNS::SEC distribution. 1.57 is newer than the +1.47 that EPEL 10 ships, because 1.56 and 1.57 fix CVE-2026-64193, CVE-2026-64194 and an +unbounded recursion on TSIG. This spec +does not take the EPEL-only perl(Net::LibIDN2) BuildRequires of the EPEL package: +Net::DNS treats Net::LibIDN2 as optional and uses it for internationalised names only. + +Known difference from the EPEL-fed x86_64 repo: the riscv64 goconserver binaries are stripped by the Go linker (`-ldflags "-s -w"`, cross build only) because the host's brp-strip cannot strip a riscv64 ELF. diff --git a/packages-manifest.conf b/packages-manifest.conf index 74d3d9a..cc38cbb 100644 --- a/packages-manifest.conf +++ b/packages-manifest.conf @@ -180,7 +180,7 @@ perl-Crypt-Rijndael=1.13 perl-Digest-SHA1=2.13 perl-Expect=1.35 perl-Mail-Sender=0.903 -perl-Net-DNS=0.80 +perl-Net-DNS=1.57 perl-Net-IP=1.26 perl-Path-Class=0.37 diff --git a/perl-Net-DNS/Buildnote b/perl-Net-DNS/Buildnote index d3d7cb6..4f632ee 100644 --- a/perl-Net-DNS/Buildnote +++ b/perl-Net-DNS/Buildnote @@ -1,6 +1,7 @@ How to build the perl-Net-DNS -1. cp the Net-DNS-0.80.tar.gz to the rpmbuild/SOURCES/ +1. cp the Net-DNS-1.57.tar.gz to the rpmbuild/SOURCES/ 2. cp the Net-DNS.spec to the rpmbuild/SPECS/ 3. cd rpmbuild/SPECS/ -4. rpmbuild -bb Net-DNS.spec (The default buildarch in Net-DNS.spec is x86_64, needs to modify buildarch if the build arch is not x86_64) +4. rpmbuild -bb Net-DNS.spec (Net::DNS is pure perl, so the rpm is noarch and one + build serves every architecture.) diff --git a/perl-Net-DNS/Net-DNS-0.80.tar.gz b/perl-Net-DNS/Net-DNS-0.80.tar.gz deleted file mode 100644 index f1e3b5d..0000000 Binary files a/perl-Net-DNS/Net-DNS-0.80.tar.gz and /dev/null differ diff --git a/perl-Net-DNS/Net-DNS-1.57.tar.gz b/perl-Net-DNS/Net-DNS-1.57.tar.gz new file mode 100644 index 0000000..561e63e Binary files /dev/null and b/perl-Net-DNS/Net-DNS-1.57.tar.gz differ diff --git a/perl-Net-DNS/Net-DNS.spec b/perl-Net-DNS/Net-DNS.spec index 56d184e..e123bb0 100644 --- a/perl-Net-DNS/Net-DNS.spec +++ b/perl-Net-DNS/Net-DNS.spec @@ -2,7 +2,7 @@ # - Net::DNS - # This spec file was automatically generated by cpan2rpm [ver: 2.028] # The following arguments were used: -# ./Net-DNS-0.80.tar.gz +# ./Net-DNS-1.57.tar.gz # For more information on cpan2rpm please visit: http://perl.arix.com/ # @@ -13,8 +13,8 @@ name: perl-Net-DNS summary: Net-DNS - Perl DNS resolver module -version: 0.80 -release: 2 +version: 1.57 +release: 1 vendor: Olaf Kolkman packager: Arix International license: Artistic @@ -23,7 +23,7 @@ url: http://www.cpan.org buildroot: %{_tmppath}/%{name}-%{version}-%(id -u -n) buildarch: noarch prefix: %(echo %{_prefix}) -source: Net-DNS-0.80.tar.gz +source: Net-DNS-1.57.tar.gz # cpan2rpm specs carry no BuildRequires; an EL10 buildroot has neither perl nor make, and # perl-generators is what makes rpm compute the perl(...) Requires. @@ -34,9 +34,15 @@ BuildRequires: perl(ExtUtils::MakeMaker) BuildRequires: perl(Digest::HMAC) BuildRequires: perl(Digest::MD5) BuildRequires: perl(Digest::SHA) +BuildRequires: perl(IO::Socket::IP) BuildRequires: perl(MIME::Base64) BuildRequires: perl(Test::More) +# The perl dependency generator reads "use base OS_CONF" in Net::DNS::Resolver::Base as a module +# name. OS_CONF is a constant that names the platform resolver class at run time, so no package +# provides perl(OS_CONF) and the generated dependency stops dnf from installing the rpm. +%global __requires_exclude ^perl\\(OS_CONF\\)$ + %description Net::DNS is a collection of Perl modules that act as a Domain Name System (DNS) resolver. It allows the programmer to perform DNS queries that are @@ -61,8 +67,9 @@ grep -rsl '^#!.*perl' . | grep -v '.bak$' |xargs --no-run-if-empty \ %__perl -MExtUtils::MakeMaker -e 'MY->fixin(@ARGV)' CFLAGS="$RPM_OPT_FLAGS" -# --noxs: pure-perl Net::DNS (no compiled dn_expand), so one noarch rpm serves every arch -%{__perl} Makefile.PL --noxs `%{__perl} -MExtUtils::MakeMaker -e ' print qq|PREFIX=%{buildroot}%{_prefix}| if \$ExtUtils::MakeMaker::VERSION =~ /5\.9[1-6]|6\.0[0-5]/ '` +# Net::DNS is pure perl from 1.01 on, so one noarch rpm serves every arch. The mock chroot has +# no network: --noonline-tests keeps the resolver tests from waiting for one. +%{__perl} Makefile.PL --noonline-tests `%{__perl} -MExtUtils::MakeMaker -e ' print qq|PREFIX=%{buildroot}%{_prefix}| if \$ExtUtils::MakeMaker::VERSION =~ /5\.9[1-6]|6\.0[0-5]/ '` %{__make} %if %maketest %{__make} test @@ -99,7 +106,7 @@ find %{buildroot}%{_prefix} \ %{__perl} -MFile::Find -le ' find({ wanted => \&wanted, no_chdir => 1}, "%{buildroot}"); - print "%doc Changes README TODO contrib demo"; + print "%doc Changes LICENSE README contrib demo"; for my $x (sort @dirs, @files) { push @ret, $x unless indirs($x); } @@ -139,6 +146,19 @@ find %{buildroot}%{_prefix} \ %defattr(-,root,root) %changelog +* Tue Sep 29 2026 xCAT build - 1.57-1 +- Net::DNS 1.57. 1.47 carries CVE-2026-64193 (code injection via EDNS + EXTENDED-ERROR) and CVE-2026-64194 (deep compression pointer chains), + fixed in 1.56, and unbounded recursion on a misplaced TSIG, fixed in 1.57. + +* Sat Sep 05 2026 xCAT build - 1.47-1 +- Net::DNS 1.47. Release 0.80 leaves the DNSSEC records to Net::DNS::SEC, so + Net::DNS::RR->new(". IN KEY ...") dies and xCAT makedns fails. 1.47 is + also the release EPEL 10 ships, so every architecture now gets the same one. +- Makefile.PL of 1.47 rejects --noxs (the module carries no XS); pass + --noonline-tests instead, because the mock chroot has no network. +- BuildRequires perl(IO::Socket::IP), which Makefile.PL needs to configure. + * Thu Aug 20 2026 xCAT build - 0.80-2 - Build the pure-perl module (--noxs) as noarch instead of an XS x86_64 rpm. - BuildRequires for an EL10 buildroot (make, perl-interpreter, perl-generators, diff --git a/t/net_dns_rr_types.t b/t/net_dns_rr_types.t new file mode 100644 index 0000000..bf9a8e3 --- /dev/null +++ b/t/net_dns_rr_types.t @@ -0,0 +1,174 @@ +#!/usr/bin/perl +# The xCAT ddns plugin signs a DDNS update with a key record that it builds itself: +# Net::DNS::RR->new(". IN KEY 512 3 ") +# Net::DNS 0.80 leaves the DNSSEC records, KEY included, to the separate Net::DNS::SEC +# distribution, so that call dies with "zone file representation not defined for KEY" and +# makedns returns non-zero. The x86_64 and ppc64le repositories take Net::DNS from EPEL and +# never showed the gap; riscv64 has no EPEL and builds this one, so only that architecture +# shipped a Net::DNS without KEY. +# +# The test drives the Net::DNS that the shipped source tarball contains. It does not read the +# module text: it extracts the tarball, puts its lib first on @INC, loads Net::DNS::RR from +# there, and constructs the records xCAT constructs. +use strict; +use warnings; +use Test::More; +use FindBin qw($RealBin); +use lib "$RealBin/..", "$RealBin/../lib"; +use File::Temp qw(tempdir); +use Archive::Tar; +use version; +use MockBuildUtils qw(read_manifest version_matches); +use XCAT::BuildUtils qw(read_lines); + +# The records xCAT builds, and the class Net::DNS must return for each. The numbers are the +# algorithm codes of xCAT::DHCP::OmapiPolicy (157 hmac-md5, 163 hmac-sha256, 165 hmac-sha512). +my $SECRET = 'c2VjcmV0'; +my @RECORDS = ( + { rr => "xcat_key. IN KEY 512 3 157 $SECRET", type => 'KEY' }, + { rr => "xcat_key. IN KEY 512 3 163 $SECRET", type => 'KEY' }, + { rr => "xcat_key. IN KEY 512 3 165 $SECRET", type => 'KEY' }, +); + +my $root = "$RealBin/.."; + +# The spec is the artifact here: it names the version built and the tarball it is built from. +# read_lines dies when the spec is gone. +my @spec = read_lines("$root/perl-Net-DNS/Net-DNS.spec"); +my ($version) = map { /^version:\s*(\S+)/i ? $1 : () } @spec; +my ($source) = map { /^source:\s*(\S+)/i ? $1 : () } @spec; +die "the Net::DNS spec declares no version and source; this test covers nothing\n" + unless $version && $source; + +# Net::DNS moved the DNSSEC records, KEY included, into the core distribution at release 1.01. +# Below that release the KEY record lives in the separate Net::DNS::SEC distribution, which +# xcat-dep does not build. +my $KEY_FLOOR = '1.01'; + +# Net::DNS pads the minor field (0.80, 1.01, 1.47), so the decimal form of version.pm orders +# the releases correctly. +sub at_least_floor { return version->parse($_[0]) >= version->parse($KEY_FLOOR) ? 1 : 0 } + +ok(at_least_floor($version), + "the spec builds Net::DNS $KEY_FLOOR or newer ($version), so KEY is in the core distribution"); + +# Every target whose manifest section lists perl-Net-DNS builds it from this one spec, so the +# records must work for all of them. A target that takes Net::DNS from EPEL is not listed. +my %manifest = read_manifest("$root/packages-manifest.conf"); +my @targets = grep { exists $manifest{$_}{'perl-Net-DNS'} } sort keys %manifest; +die 'no manifest target builds perl-Net-DNS; this test covers nothing' unless @targets; + +# The pin is the second place the version is written down, and mockbuild-all.pl fails the run +# when the built rpm does not match it. A pin below $KEY_FLOOR puts a Net::DNS without KEY back +# into the repositories a service node reads, which have no EPEL copy to outrank it. An operator +# pin (">= 0.80") accepts such a build too, so only an exact version is allowed here. +for my $target (@targets) { + my $pin = $manifest{$target}{'perl-Net-DNS'}; + my $exact = $pin =~ /\A\d+(?:\.\d+)+\z/ ? 1 : 0; + ok($exact, "[$target] the perl-Net-DNS pin ($pin) names one exact version"); + ok($exact && at_least_floor($pin), + "[$target] the perl-Net-DNS pin ($pin) is $KEY_FLOOR or newer"); + ok(version_matches($version, $pin), + "[$target] the perl-Net-DNS pin ($pin) accepts the version the spec builds ($version)"); +} + +my $tarball = "$root/perl-Net-DNS/$source"; +die "$tarball is missing, so the spec cannot build" unless -f $tarball; + +# The tarball, the spec and the Buildnote name one release. A second tarball beside the spec is a +# release that nothing builds, and a Buildnote that names it sends a manual build to the wrong one. +my @tarballs = map { s{.*/}{}r } glob("$root/perl-Net-DNS/Net-DNS-*.tar.gz"); +is_deeply(\@tarballs, [$source], "perl-Net-DNS/ holds only the tarball the spec builds ($source)"); +my @buildnote = read_lines("$root/perl-Net-DNS/Buildnote"); +my @named = map { /(Net-DNS-[\d.]+\.tar\.gz)/ ? $1 : () } @buildnote; +is_deeply(\@named, [$source], "the Buildnote names the tarball the spec builds ($source)"); + +my $tmp = tempdir(CLEANUP => 1); +{ + my $tar = Archive::Tar->new; + $tar->read($tarball) or die "Cannot read $tarball: " . Archive::Tar->error; + $tar->setcwd($tmp); + $tar->extract or die "Cannot extract $tarball: " . Archive::Tar->error; +} +my ($libdir) = grep { -d } glob("$tmp/*/lib"); +die "$tarball holds no lib/ directory" unless defined $libdir; + +# The extracted copy goes first on @INC, so it, and not a Net::DNS installed on the build host, +# answers the calls. The test checks which file each module came from. +unshift @INC, $libdir; +require Net::DNS::RR; + +my $loaded = $INC{'Net/DNS/RR.pm'} // '(nothing)'; +is(index($loaded, $tmp), 0, 'Net::DNS::RR is loaded from the shipped tarball, not from the host') + or diag("loaded: $loaded"); + +for my $want (@RECORDS) { + my $rr = eval { Net::DNS::RR->new($want->{rr}) }; + ok($rr, "Net::DNS builds '$want->{rr}'") or diag($@); + next unless $rr; + is($rr->type, $want->{type}, "... as a $want->{type} record"); + is(ref($rr), "Net::DNS::RR::$want->{type}", "... of class Net::DNS::RR::$want->{type}"); + is($rr->key, $SECRET, '... carrying the key material given to it'); +} + +my $key_module = $INC{'Net/DNS/RR/KEY.pm'} // '(nothing)'; +is(index($key_module, $tmp), 0, 'the KEY record class also comes from the shipped tarball') + or diag("loaded: $key_module"); + +# CVE-2026-64194: a reply whose owner name is a long chain of compression pointers makes the +# decoder recurse once per pointer. Net::DNS 1.56 stops the chain; 1.47 decodes all of it. +# The reply below holds a NULL record whose rdata is 200 pointers, each to the one before it, +# and an A record whose owner name points at the last one. +{ + my $reply = pack('n6', 1, 0x8100, 1, 2, 0, 0) . "\x01a\x00" . pack('nn', 1, 1); + my $rdata_at = length($reply) + 12; + my ($chain, $prev) = ('', 12); + for (1 .. 200) { + my $here = $rdata_at + length $chain; + $chain .= pack 'n', 0xC000 | $prev; + $prev = $here; + } + $reply .= pack('nnnNn', 0xC00C, 10, 1, 0, length $chain) . $chain; + $reply .= pack('nnnNn', 0xC000 | $prev, 1, 1, 0, 4) . "\x7f\0\0\1"; + + require Net::DNS::Packet; + local $SIG{__WARN__} = sub { warn @_ unless $_[0] =~ /^Deep recursion/ }; + # Packet->new reports a decode error in $@ and returns, as the resolver expects. + Net::DNS::Packet->new(\$reply); + like($@, qr/deep compression recursion/, + 'Net::DNS rejects a reply that chains 200 compression pointers (CVE-2026-64194)'); +} + +# rt.cpan.org #181125, fixed in Net::DNS 1.57: a reply with a TSIG record in the answer section, +# followed by another record, makes the re-encode of that reply recurse without bound. 1.56 and +# 1.47 recurse. The wrapper stops the recursion at 20 levels and records that it did: Net::DNS +# catches the die inside the TSIG encoder, so the re-encode itself still returns. +{ + my $reply = pack('n6', 1, 0x8100, 1, 2, 0, 0) . "\x07example\x00" . pack('nn', 1, 1); + my $rdata = "\x0bhmac-sha256\x00" . pack('nNn nnnn', 0, 0, 300, 0, 1, 0, 0); + $reply .= "\x03key\x00" . pack('nnNn', 250, 255, 0, length $rdata) . $rdata; + $reply .= "\x00" . pack('nnNn', 1, 1, 0, 4) . "\x7f\0\0\1"; + + my $encode = \&Net::DNS::Packet::encode; + my ($depth, $stopped) = (0, 0); + no warnings 'redefine'; + local *Net::DNS::Packet::encode = sub { + if (++$depth > 20) { + $stopped = 1; + die "Net::DNS::Packet::encode recursed more than 20 levels deep\n"; + } + my $wire = eval { $encode->(@_) }; + my $error = $@; + $depth--; + die $error if $error; + return $wire; + }; + local $SIG{__WARN__} = sub { warn @_ unless $_[0] =~ /misplaced or corrupt TSIG/ }; + my $packet = Net::DNS::Packet->new(\$reply); + ok($packet, 'Net::DNS decodes a reply with a misplaced TSIG record') or diag($@); + my $data = $packet && eval { $packet->data }; + ok(defined $data, 'Net::DNS re-encodes that reply') or diag($@); + ok(!$stopped, 'the re-encode does not recurse without bound (rt.cpan.org #181125)'); +} + +done_testing;