From 558ffa99646532b98c542003f1e83113ce985d52 Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Tue, 19 May 2026 15:06:55 -0400 Subject: [PATCH] import CS dnf-4.20.0-22.el10 --- ...-email_to-in-command_email-emitter-t.patch | 98 ++++++++++++++++++ ...ntrusted-signatures-if-there-is-trus.patch | 99 +++++++++++++++++++ ...and-skip-dangling-protected-dependen.patch | 43 ++++++++ ...ootc-unlock-only-if-usr-is-read-only.patch | 57 +++++++++++ ...writable-when-DeploymentUnlockedStat.patch | 37 +++++++ dnf.spec | 23 ++++- 6 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 0037-automatic-Expand-email_to-in-command_email-emitter-t.patch create mode 100644 0038-rpmkeys-Ignore-untrusted-signatures-if-there-is-trus.patch create mode 100644 0039-autoremove-warn-and-skip-dangling-protected-dependen.patch create mode 100644 0040-bootc-unlock-only-if-usr-is-read-only.patch create mode 100644 0041-bootc-Call-make_writable-when-DeploymentUnlockedStat.patch diff --git a/0037-automatic-Expand-email_to-in-command_email-emitter-t.patch b/0037-automatic-Expand-email_to-in-command_email-emitter-t.patch new file mode 100644 index 0000000..e6fa92b --- /dev/null +++ b/0037-automatic-Expand-email_to-in-command_email-emitter-t.patch @@ -0,0 +1,98 @@ +From a73cc59429846abe44b19f92dc1d05ebdcab4c7b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +Date: Thu, 19 Jun 2025 13:09:35 +0200 +Subject: [PATCH] automatic: Expand email_to in command_email emitter to + individual arguments + +Upstream commit: aa1ba2d1566198127518d0ccb38eaf5481b4649e + +If /etc/dnf/automatic.conf has: + + [emitters] + emit_via = command_email + [command_email] + email_to = root,test + +a command incompatible with s-nail-14.9.25, a "mail" command +implementation, was executed: + + execve("/usr/bin/mail", ["mail", "-Ssendwait", "-s", "Updates available on 'fedora-43'.", "-r", "root", "root test"], ...) = 0 + +The cause was that s-nail does not support multiple recipients in +a single argument. + +This patch changes how "{email_to}" expands in command_format +formatting string. Now it expands into multiple, space separated arguments. +The new syntax is also accepted by mailx tool. + +Implementation detail: A list of email_to recipients passed in +CommandEmitterMixIn does not inherit from "list" Python class or any +other iterator. It's libdnf.module.VectorString class generated by +Swig without. There the custom ShellQuotedLists formatting dictionary +needs to refer to libdnf.module.VectorString type and include "libdnf" +Python module explicitly. + +A test will be added to ci-dnf-stack repository. + +Resolve: #2241 +Resolve: https://issues.redhat.com/browse/RHEL-94331 +--- + dnf/automatic/emitter.py | 21 +++++++++++++++++---- + 1 file changed, 17 insertions(+), 4 deletions(-) + +diff --git a/dnf/automatic/emitter.py b/dnf/automatic/emitter.py +index 1c8ff6bf8..ee1954296 100644 +--- a/dnf/automatic/emitter.py ++++ b/dnf/automatic/emitter.py +@@ -22,6 +22,7 @@ from __future__ import absolute_import + from __future__ import print_function + from __future__ import unicode_literals + from dnf.i18n import _ ++import libdnf + import logging + import dnf.pycomp + import smtplib +@@ -132,6 +133,20 @@ class EmailEmitter(Emitter): + logger.error(msg) + + ++class ShellQuotedLists(dict): ++ """ ++ Dictionary which returns values quoted with dnf.pycomp.shlex_quote(). ++ If a looked-up value is a list or libdnf.module.VectorString, it will ++ quote the list members and then concatenate them with a space and return ++ the resulting string. ++ """ ++ def __getitem__(self, key): ++ value = super(ShellQuotedLists, self).__getitem__(key) ++ if isinstance(value, (list, libdnf.module.VectorString)): ++ return ' '.join(dnf.pycomp.shlex_quote(item) for item in value) ++ else: ++ return dnf.pycomp.shlex_quote(value) ++ + class CommandEmitterMixIn(object): + """ + Executes a desired command, and pushes data into its stdin. +@@ -147,9 +162,7 @@ class CommandEmitterMixIn(object): + msg = self._prepare_msg() + # all strings passed to shell should be quoted to avoid accidental code + # execution +- quoted_msg = dict((key, dnf.pycomp.shlex_quote(val)) +- for key, val in msg.items()) +- command = command_fmt.format(**quoted_msg) ++ command = command_fmt.format_map(ShellQuotedLists(msg)) + stdin_feed = stdin_fmt.format(**msg).encode('utf-8') + + # Execute the command +@@ -177,7 +190,7 @@ class CommandEmailEmitter(CommandEmitterMixIn, EmailEmitter): + return {'subject': subject, + 'body': body, + 'email_from': self._conf.email_from, +- 'email_to': ' '.join(self._conf.email_to)} ++ 'email_to': self._conf.email_to} + + + class StdIoEmitter(Emitter): +-- +2.52.0 + diff --git a/0038-rpmkeys-Ignore-untrusted-signatures-if-there-is-trus.patch b/0038-rpmkeys-Ignore-untrusted-signatures-if-there-is-trus.patch new file mode 100644 index 0000000..e29a774 --- /dev/null +++ b/0038-rpmkeys-Ignore-untrusted-signatures-if-there-is-trus.patch @@ -0,0 +1,99 @@ +From c930603f8a62c53862ffc6fee8800610f79d6a1b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +Date: Wed, 21 Jan 2026 17:38:02 +0100 +Subject: [PATCH] rpmkeys: Ignore untrusted signatures if there is trusted one + +Upstream commit: 00fef9ad0d761eccf8d86580e031f442af9cd8ef + +With RPMv6 signatures, there can be multiple signatures attached to +a single package. If some signatures are made with an algorithm +disabled in a system-wide crypto policy (e.g. rsa4096 = "never" in +/etc/crypto-policies/back-ends/rpm-sequoia.config), but other +signatures are valid and trusted, so that the package is overall +correctly signed: + + # rpmkeys -v -K ./foo-0-1.fc43.noarch.rpm; echo $? + ./foo-0-1.fc43.noarch.rpm: + Header OpenPGP V4 EdDSA/SHA512 signature, key fingerprint: 111e11e164e61b51a1f62abe496099dbe2b145f3: OK + Header OpenPGP V4 RSA/SHA512 signature, key fingerprint: 9b02d881fe4185e9ea52e78888cd83a4b5e56945: NOTTRUSTED + Header SHA256 digest: OK + Payload SHA256 digest: OK + 0 + +DNF failed like this: + + [...] + Is this ok [y/N]: y + Downloading Packages: + rsa-edsa 633 kB/s | 648 B 00:00 + Importing GPG key 0xE2B145F3: + Userid : "test2 " + Fingerprint: 111E 11E1 64E6 1B51 A1F6 2ABE 4960 99DB E2B1 45F3 + From : /root/repos/rsa-edsa/edsa.pub + Is this ok [y/N]: y + Key imported successfully + rsa-edsa 1.6 MB/s | 1.6 kB 00:00 + Importing GPG key 0xB5E56945: + Userid : "" + Fingerprint: 9B02 D881 FE41 85E9 EA52 E788 88CD 83A4 B5E5 6945 + From : /root/repos/rsa-edsa/rsa.pub + Is this ok [y/N]: y + error: Certificate 88CD83A4B5E56945: + Policy rejects 88CD83A4B5E56945: Policy rejected asymmetric algorithm + Key import failed (code 2). Failing package is: foo-0-1.fc43.noarch + GPG Keys are configured as: file:///root/repos/rsa-edsa/edsa.pub, file:///root/repos/rsa-edsa/rsa.pub + Error: GPG check FAILED + +The cause was that an output of "rpmkeys -v -K" tool executed indirectly by +dnf.rpm.miscutils.checkSig() was incorrectly parsed in _process_rpm_output() +function. That function assumed that only one signature can exist and +reported on any NOTTRUSTED record that the package is not trustfully +signed. + +As a result, DNF attempted to (re)import all the signing keys. But +importing a key with the disabled algorithm failed and DNF errored. + +This patch fixes parsing the rpmkeys output to ignore all untrusted +signatures if there is at least one signature trusted. + +Resolve: https://issues.redhat.com/browse/RHEL-112730 +--- + dnf/rpm/miscutils.py | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/dnf/rpm/miscutils.py b/dnf/rpm/miscutils.py +index 1b85301da..61b0c2da5 100644 +--- a/dnf/rpm/miscutils.py ++++ b/dnf/rpm/miscutils.py +@@ -39,7 +39,7 @@ def _process_rpm_output(data): + # last newline. + if len(data) < 3 or data[0] != b'-:' or data[-1]: + return 2 +- seen_sig, missing_key, not_trusted, not_signed = False, False, False, False ++ trusted_sig, missing_key, not_trusted, not_signed = False, False, False, False + for i in data[1:-1]: + if b': BAD' in i: + return 2 +@@ -49,12 +49,16 @@ def _process_rpm_output(data): + not_trusted = True + elif i.endswith(b': NOTFOUND'): + not_signed = True ++ # Some rpmkeys versions print Signature, some signature, accept both. ++ elif i.endswith(b': OK') and b'ignature,' in i: ++ trusted_sig = True + elif not i.endswith(b': OK'): + return 2 +- if not_trusted: +- return 3 +- elif missing_key: ++ if missing_key: + return 1 ++ elif not trusted_sig and not_trusted: ++ # Do not report untrusted signatures if there is a trusted one ++ return 3 + elif not_signed: + return 4 + # we still check return code, so this is safe +-- +2.53.0 + diff --git a/0039-autoremove-warn-and-skip-dangling-protected-dependen.patch b/0039-autoremove-warn-and-skip-dangling-protected-dependen.patch new file mode 100644 index 0000000..93cb1ee --- /dev/null +++ b/0039-autoremove-warn-and-skip-dangling-protected-dependen.patch @@ -0,0 +1,43 @@ +From 93a1bdc09ad6ef345cf8cecc059b649ee2ccbf40 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Mon, 26 Jan 2026 12:29:11 +0100 +Subject: [PATCH 1/2] autoremove: warn and skip dangling protected dependencies + +Requires new libdnf 0.76.0 API. +--- + dnf/base.py | 18 ++++++++++++++++-- + 1 file changed, 16 insertions(+), 2 deletions(-) + +diff --git a/dnf/base.py b/dnf/base.py +index 7117dbe3..1d6ae6e2 100644 +--- a/dnf/base.py ++++ b/dnf/base.py +@@ -2311,9 +2311,23 @@ class Base(object): + logger.warning(_('No packages marked for removal.')) + + else: +- pkgs = self.sack.query()._unneeded(self.history.swdb, ++ unneeded_pkgs = self.sack.query()._unneeded(self.history.swdb, + debug_solver=self.conf.debug_solver) +- for pkg in pkgs: ++ ++ protected = self.sack.query().installed().filterm(name=self.conf.protected_packages) ++ protected_found = False ++ for pkg in protected: ++ if pkg in unneeded_pkgs: ++ msg = _('Unneeded protected package: %s (and its dependencies) cannot be removed, ' ++ 'either mark it as user-installed or change protected_packages configuration option.') ++ logger.warning(msg, pkg) ++ protected_found = True ++ ++ if protected_found: ++ unneeded_pkgs = self.sack.query()._unneeded_extra_userinstalled(self.history.swdb, protected, ++ debug_solver=self.conf.debug_solver) ++ ++ for pkg in unneeded_pkgs: + self.package_remove(pkg) + + def remove(self, pkg_spec, reponame=None, forms=None): +-- +2.53.0 + diff --git a/0040-bootc-unlock-only-if-usr-is-read-only.patch b/0040-bootc-unlock-only-if-usr-is-read-only.patch new file mode 100644 index 0000000..85092bc --- /dev/null +++ b/0040-bootc-unlock-only-if-usr-is-read-only.patch @@ -0,0 +1,57 @@ +From c1c66613c04a476a3b85582ae5214a3f9301d485 Mon Sep 17 00:00:00 2001 +From: Evan Goode +Date: Mon, 1 Dec 2025 13:40:26 -0500 +Subject: [PATCH 1/2] bootc: unlock only if /usr is read-only + +DNF should only run `ostree admin unlock --transient` if `/usr` is +actually read-only. `/usr` may be writable via OSTree's `root.transient = +true` even if the `ostree admin status` is not transient. + +Resolves: https://redhat.atlassian.net/browse/RHEL-145780 +--- + dnf/cli/cli.py | 4 +++- + dnf/util.py | 2 +- + 2 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py +index d4bf811c1..07cc62cb1 100644 +--- a/dnf/cli/cli.py ++++ b/dnf/cli/cli.py +@@ -225,6 +225,7 @@ class BaseCli(dnf.Base): + # Handle bootc transactions. `--transient` must be specified if + # /usr is not already writeable. + bootc_system = None ++ bootc_system_needs_unlock = False + if is_bootc_transaction: + if self.conf.persistence == "persist": + logger.info(_("Persistent transactions aren't supported on bootc systems.")) +@@ -246,6 +247,7 @@ class BaseCli(dnf.Base): + logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. " + "Keep in mind that changes to /etc and /var will still persist, and packages " + "commonly modify these directories.")) ++ bootc_system_needs_unlock = True + self._persistence = libdnf.transaction.TransactionPersistence_TRANSIENT + + # Check whether the transaction modifies usr_drift_protected_paths +@@ -276,7 +278,7 @@ class BaseCli(dnf.Base): + if self.conf.assumeno or not self.output.userconfirm(): + raise CliError(_("Operation aborted.")) + +- if bootc_system: ++ if bootc_system and bootc_system_needs_unlock: + bootc_system.make_writable() + else: + logger.info(_('Nothing to do.')) +diff --git a/dnf/util.py b/dnf/util.py +index eb987bb8a..e058bc187 100644 +--- a/dnf/util.py ++++ b/dnf/util.py +@@ -749,4 +749,4 @@ class _BootcSystem: + # read-only. Set up a mount namespace for DNF. + self._set_up_mountns() + +- assert os.access(self.usr, os.W_OK) ++ assert self.is_writable() +-- +2.53.0 + diff --git a/0041-bootc-Call-make_writable-when-DeploymentUnlockedStat.patch b/0041-bootc-Call-make_writable-when-DeploymentUnlockedStat.patch new file mode 100644 index 0000000..cc6c512 --- /dev/null +++ b/0041-bootc-Call-make_writable-when-DeploymentUnlockedStat.patch @@ -0,0 +1,37 @@ +From 0b6ed2e107d1fe8744f45a771729cb0290413d67 Mon Sep 17 00:00:00 2001 +From: Evan Goode +Date: Fri, 20 Mar 2026 16:28:14 -0400 +Subject: [PATCH 2/2] bootc: Call make_writable when + DeploymentUnlockedState.TRANSIENT + +Fixes a bug in 1afe4328334f27b45b5c4599b6f1e8ac69d465e4. +bootc_system.make_writable should still be called even when the system +is already in DeploymentUnlockedState.TRANSIENT, since the DNF mount +namespace needs to be set up either way. +--- + dnf/cli/cli.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py +index 07cc62cb1..17cf9165e 100644 +--- a/dnf/cli/cli.py ++++ b/dnf/cli/cli.py +@@ -235,6 +235,7 @@ class BaseCli(dnf.Base): + bootc_system = dnf.util._BootcSystem() + + if not bootc_system.is_writable(): ++ bootc_system_needs_unlock = True + if self.conf.persistence == "auto": + logger.info(_("This bootc system is configured to be read-only. Pass --transient to " + "perform this transaction in a transient overlay which will reset when " +@@ -247,7 +248,6 @@ class BaseCli(dnf.Base): + logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. " + "Keep in mind that changes to /etc and /var will still persist, and packages " + "commonly modify these directories.")) +- bootc_system_needs_unlock = True + self._persistence = libdnf.transaction.TransactionPersistence_TRANSIENT + + # Check whether the transaction modifies usr_drift_protected_paths +-- +2.53.0 + diff --git a/dnf.spec b/dnf.spec index 86dc46f..6e45b3a 100644 --- a/dnf.spec +++ b/dnf.spec @@ -24,7 +24,7 @@ %endif %if 0%{?rhel} == 10 - %global hawkey_version 0.73.1-11 + %global hawkey_version 0.73.1-14 %endif # override dependencies for fedora 26 @@ -72,7 +72,7 @@ It supports RPMs, modules and comps groups & environments. Name: dnf Version: 4.20.0 -Release: 18%{?dist} +Release: 22%{?dist} Summary: %{pkg_summary} # For a breakdown of the licensing, see PACKAGE-LICENSING License: GPL-2.0-or-later AND GPL-1.0-only @@ -114,6 +114,11 @@ Patch33: 0033-Obsolete-RHEL-9-only-multisig-DNF-plugin.patch Patch34: 0034-Add-deprecation-warning-for-module-commands.patch Patch35: 0035-Add-modularity-deprecation-warning-to-doc-pages.patch Patch36: 0036-automatic-Fix-detecting-releasever_minor.patch +Patch37: 0037-automatic-Expand-email_to-in-command_email-emitter-t.patch +Patch38: 0038-rpmkeys-Ignore-untrusted-signatures-if-there-is-trus.patch +Patch39: 0039-autoremove-warn-and-skip-dangling-protected-dependen.patch +Patch40: 0040-bootc-unlock-only-if-usr-is-read-only.patch +Patch41: 0041-bootc-Call-make_writable-when-DeploymentUnlockedStat.patch BuildArch: noarch BuildRequires: cmake @@ -475,6 +480,20 @@ popd # bootc subpackage does not include any files %changelog +* Wed Mar 25 2026 Evan Goode - 4.20.0-22 +- bootc: unlock only if /usr is read-only (RHEL-145780) + +* Mon Feb 16 2026 Ales Matej - 4.20.0-21 +- autoremove: when a dangling protected dependency is found produce a warning + and skip it (RHEL-128445) + +* Fri Jan 23 2026 Petr Pisar - 4.20.0-20 +- Ignore untrusted signatures if there is trusted one (RHEL-112730) + +* Fri Jan 09 2026 Petr Pisar - 4.20.0-19 +- automatic: Expand email_to in command_email emitter to individual arguments + (RHEL-94331) + * Tue Jul 29 2025 Petr Pisar - 4.20.0-18 - Fix detecting releasever_minor in dnf-automatic (RHEL-106141)