From c130df75a18c83d230fa56095dbf43c0d3990bbc Mon Sep 17 00:00:00 2001 From: Clif Houck Date: Wed, 15 Apr 2026 11:54:05 -0500 Subject: [PATCH] Add a flag to disable installing bootloaders Disables bootloader installations (calls to grub-install) for security reasons as part of the mitigation for CVE-2026-43003. Depends-On: https://review.opendev.org/c/openstack/ironic/+/990724 Related-Bug: 2148310 Change-Id: I10c88426d5838820ecf6853dca5b3878dc29bdf4 Signed-off-by: Clif Houck Signed-off-by: Jay Faulkner (cherry picked from commit 6cd463a657edcddf7b79416ac69bdef5b6f30099) (cherry picked from commit c0eb9283312264d161590fd40bee724344a58f4b) --- ironic_python_agent/agent.py | 3 ++ ironic_python_agent/config.py | 7 ++++ ironic_python_agent/extensions/image.py | 31 +++++++++++------- .../tests/unit/extensions/test_image.py | 32 +++++++++++++++++++ ...otloaders-by-default-1029ed4faaf049aa.yaml | 9 ++++++ 5 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/disable-installing-bootloaders-by-default-1029ed4faaf049aa.yaml diff --git a/ironic_python_agent/agent.py b/ironic_python_agent/agent.py index 4d8406c01..66bc29c58 100644 --- a/ironic_python_agent/agent.py +++ b/ironic_python_agent/agent.py @@ -513,6 +513,9 @@ def process_lookup_data(self, content): # Update config with values from Ironic config = content.get('config', {}) + if config.get('enable_bios_bootloader_install'): + cfg.CONF.set_override('enable_bios_bootloader_install', + config['enable_bios_bootloader_install']) if config.get('agent_containers'): for opt, val in config['agent_containers'].items(): cfg.CONF.set_override(opt, val, group='container') diff --git a/ironic_python_agent/config.py b/ironic_python_agent/config.py index e89b80af6..32b1ba950 100644 --- a/ironic_python_agent/config.py +++ b/ironic_python_agent/config.py @@ -404,6 +404,13 @@ help='This disables bootc deployment methods in the ramdisk ' 'because the bootc command inside of the ramdisk ' 'comes from the supplied image to be deployed.'), + cfg.BoolOpt('enable_bios_bootloader_install', + default=False, + help='Enables support for partition images which require a ' + 'legacy bootloader -- and a call to ``grub-install``. ' + 'Generally, this should remain disabled for maximum ' + 'security, however, this option allows it to be ' + 're-enabled for compatibility.'), ] disk_utils_opts = [ diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py index 5ca3c6465..7c0f86741 100644 --- a/ironic_python_agent/extensions/image.py +++ b/ironic_python_agent/extensions/image.py @@ -723,15 +723,24 @@ def install_bootloader(self, root_uuid, efi_system_part_uuid=None, ' Assuming a whole disk image') return - # In case we can't use efibootmgr for uefi we will continue using grub2 - LOG.debug('Using grub2-install to set up boot files') - try: - _install_grub2(device, - root_uuid=root_uuid, - efi_system_part_uuid=efi_system_part_uuid, - prep_boot_part_uuid=prep_boot_part_uuid, - target_boot_mode=target_boot_mode) - except Exception as e: - LOG.error('Error setting up bootloader. Error %s', e) + if CONF.enable_bios_bootloader_install: + # In case we can't use efibootmgr for uefi we will continue + # using grub2 + LOG.debug('Using grub2-install to set up boot files') + try: + _install_grub2(device, + root_uuid=root_uuid, + efi_system_part_uuid=efi_system_part_uuid, + prep_boot_part_uuid=prep_boot_part_uuid, + target_boot_mode=target_boot_mode) + except Exception as e: + LOG.error('Error setting up bootloader. Error %s', e) + if not ignore_failure: + raise + else: + msg = ("Install of legacy BIOS bootloaders disabled by " + "CONF.enable_bios_bootloader_install as part of " + "CVE-2026-43003 mitigation.") + LOG.error(msg) if not ignore_failure: - raise + raise errors.InvalidImage(details=msg) diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index 93f7717a8..47275d26a 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -52,6 +52,7 @@ def setUp(self): self.fake_efi_system_part_uuid = '45AB-2312' self.fake_prep_boot_part_uuid = '76937797-3253-8843-999999999999' self.fake_dir = '/tmp/fake-dir' + self.config(enable_bios_bootloader_install=True) @mock.patch.object(image, '_install_grub2', autospec=True) def test__install_bootloader_bios(self, mock_grub2, @@ -68,8 +69,39 @@ def test__install_bootloader_bios(self, mock_grub2, self.fake_dev, root_uuid=self.fake_root_uuid, efi_system_part_uuid=None, prep_boot_part_uuid=None, target_boot_mode='bios' + ) + @mock.patch.object(image, '_install_grub2', autospec=True) + def test__install_bootloader_bios_disabled(self, mock_grub2, + mock_execute, mock_dispatch): + self.config(enable_bios_bootloader_install=False) + mock_dispatch.side_effect = [ + self.fake_dev, hardware.BootInfo(current_boot_mode='bios') + ] + self.agent_extension.install_bootloader( + root_uuid=self.fake_root_uuid).join() + mock_dispatch.assert_any_call('get_os_install_device') + mock_dispatch.assert_any_call('get_boot_info') + self.assertEqual(2, mock_dispatch.call_count) + mock_grub2.assert_not_called() + + @mock.patch.object(image, '_install_grub2', autospec=True) + def test__install_bootloader_bios_disabled_dont_ignore_failures( + self, mock_grub2, mock_execute, mock_dispatch): + self.config(enable_bios_bootloader_install=False) + self.config(ignore_bootloader_failure=False) + mock_dispatch.side_effect = [ + self.fake_dev, hardware.BootInfo(current_boot_mode='bios') + ] + result = self.agent_extension.install_bootloader( + root_uuid=self.fake_root_uuid).join() + mock_dispatch.assert_any_call('get_os_install_device') + mock_dispatch.assert_any_call('get_boot_info') + self.assertEqual(2, mock_dispatch.call_count) + self.assertIsNotNone(result.command_error) + mock_grub2.assert_not_called() + @mock.patch.object(efi_utils, 'manage_uefi', autospec=True) @mock.patch.object(image, '_install_grub2', autospec=True) def test__install_bootloader_uefi(self, mock_grub2, mock_uefi, diff --git a/releasenotes/notes/disable-installing-bootloaders-by-default-1029ed4faaf049aa.yaml b/releasenotes/notes/disable-installing-bootloaders-by-default-1029ed4faaf049aa.yaml new file mode 100644 index 000000000..28a771345 --- /dev/null +++ b/releasenotes/notes/disable-installing-bootloaders-by-default-1029ed4faaf049aa.yaml @@ -0,0 +1,9 @@ +--- +security: + - | + Disable installation of bootloaders (via grub-install) by default in order + to improve security posture by adding a new configuration option + `enable_bios_bootloader_install` which defaults to `False`. Operators + who still need this functionality can re-enable installation of + bootloaders by setting `enable_bios_bootloader_install` to `True`. + Addresses CVE-2026-43003.