-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add detection for custom libraries registered by ld.so.conf
The in-place upgrade process does not support custom libraries and also does not handle customized configuration of dynamic linked. In such a case it can happen (and it happens) that the upgrade could break in critical phases when linked libraries dissapear or are not compatible with the new system. We cannot decide whether or not such a custom configuration affects the upgrade negatively, so let's detect any customisations or unexpected configurations related to dynamic linker and in such a case generate a high severity report, informing user about the possible impact on the upgrade process. Currently it's detectect: * modified default LD configuration: /etc/ld.so.conf * drop int configuration files under /etc/ld.so.conf.d/ that are not owned by any RHEL RPMs * envars: LD_LIBRARY_PATH, LD_PRELOAD Jira ref.: OAMG-4460 / RHEL-11958 BZ ref.: BZ 1927700
- Loading branch information
1 parent
5a3bded
commit 043c6cb
Showing
7 changed files
with
528 additions
and
0 deletions.
There are no files selected for viewing
22 changes: 22 additions & 0 deletions
22
repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from leapp.actors import Actor | ||
from leapp.libraries.actor.checkdynamiclinkerconfiguration import check_dynamic_linker_configuration | ||
from leapp.models import DynamicLinkerConfiguration, Report | ||
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag | ||
|
||
|
||
class CheckDynamicLinkerConfiguration(Actor): | ||
""" | ||
Check for customization of dynamic linker configuration. | ||
The in-place upgrade could potentionally be impacted in a negative way due | ||
to the customization of dynamic linker configuration by user. This actor creates high | ||
severity report upon detecting such customization. | ||
""" | ||
|
||
name = 'check_dynamic_linker_configuration' | ||
consumes = (DynamicLinkerConfiguration,) | ||
produces = (Report,) | ||
tags = (ChecksPhaseTag, IPUWorkflowTag) | ||
|
||
def process(self): | ||
check_dynamic_linker_configuration() |
79 changes: 79 additions & 0 deletions
79
...ommon/actors/checkdynamiclinkerconfiguration/libraries/checkdynamiclinkerconfiguration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from leapp import reporting | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import DynamicLinkerConfiguration | ||
|
||
LD_SO_CONF_DIR = '/etc/ld.so.conf.d' | ||
LD_SO_CONF_MAIN = '/etc/ld.so.conf' | ||
LD_LIBRARY_PATH_VAR = 'LD_LIBRARY_PATH' | ||
LD_PRELOAD_VAR = 'LD_PRELOAD' | ||
FMT_LIST_SEPARATOR_1 = '\n- ' | ||
FMT_LIST_SEPARATOR_2 = '\n - ' | ||
|
||
|
||
def _report_custom_dynamic_linker_configuration(summary): | ||
reporting.create_report([ | ||
reporting.Title( | ||
'Detected customized configuration for dynamic linker.' | ||
), | ||
reporting.Summary(summary), | ||
reporting.Remediation(hint=('Remove or revert the custom dynamic linker configurations and apply the changes ' | ||
'using the ldconfig command. In case of possible active software collections we ' | ||
'suggest disabling them persistently.')), | ||
reporting.RelatedResource('file', '/etc/ld.so.conf'), | ||
reporting.RelatedResource('directory', '/etc/ld.so.conf.d'), | ||
reporting.Severity(reporting.Severity.HIGH), | ||
reporting.Groups([reporting.Groups.OS_FACTS]), | ||
]) | ||
|
||
|
||
def check_dynamic_linker_configuration(): | ||
configuration = next(api.consume(DynamicLinkerConfiguration), None) | ||
if not configuration: | ||
return | ||
|
||
custom_configurations = '' | ||
if configuration.main_config.modified: | ||
custom_configurations += ( | ||
'{}The {} file has unexpected contents:{}{}' | ||
.format(FMT_LIST_SEPARATOR_1, LD_SO_CONF_MAIN, | ||
FMT_LIST_SEPARATOR_2, FMT_LIST_SEPARATOR_2.join(configuration.main_config.modified_lines)) | ||
) | ||
|
||
custom_configs = [] | ||
for config in configuration.included_configs: | ||
if config.modified: | ||
custom_configs.append(config.path) | ||
|
||
if custom_configs: | ||
custom_configurations += ( | ||
'{}The following drop in config files were marked as custom:{}{}' | ||
.format(FMT_LIST_SEPARATOR_1, FMT_LIST_SEPARATOR_2, FMT_LIST_SEPARATOR_2.join(custom_configs)) | ||
) | ||
|
||
if configuration.used_variables: | ||
custom_configurations += ( | ||
'{}The following variables contain unexpected dynamic linker configuration:{}{}' | ||
.format(FMT_LIST_SEPARATOR_1, FMT_LIST_SEPARATOR_2, | ||
FMT_LIST_SEPARATOR_2.join(configuration.used_variables)) | ||
) | ||
|
||
if custom_configurations: | ||
summary = ( | ||
'Custom configurations to the dynamic linker could potentially impact ' | ||
'the upgrade in a negative way. The custom configuration includes ' | ||
'modifications to {main_conf}, custom or modified drop in config ' | ||
'files in the {conf_dir} directory and additional entries in the ' | ||
'{ldlib_envar} or {ldpre_envar} variables. These modifications ' | ||
'configure the dynamic linker to use different libraries that might ' | ||
'not be provided by Red Hat products or might not be present during ' | ||
'the whole upgrade process. The following custom configurations ' | ||
'were detected by leapp:{cust_configs}' | ||
.format( | ||
main_conf=LD_SO_CONF_MAIN, | ||
conf_dir=LD_SO_CONF_DIR, | ||
ldlib_envar=LD_LIBRARY_PATH_VAR, | ||
ldpre_envar=LD_PRELOAD_VAR, | ||
cust_configs=custom_configurations | ||
) | ||
) | ||
_report_custom_dynamic_linker_configuration(summary) |
65 changes: 65 additions & 0 deletions
65
...mmon/actors/checkdynamiclinkerconfiguration/tests/test_checkdynamiclinkerconfiguration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import pytest | ||
|
||
from leapp import reporting | ||
from leapp.libraries.actor.checkdynamiclinkerconfiguration import ( | ||
check_dynamic_linker_configuration, | ||
LD_LIBRARY_PATH_VAR, | ||
LD_PRELOAD_VAR | ||
) | ||
from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import DynamicLinkerConfiguration, LDConfigFile, MainLDConfigFile | ||
|
||
INCLUDED_CONFIG_PATHS = ['/etc/ld.so.conf.d/dyninst-x86_64.conf', | ||
'/etc/ld.so.conf.d/mariadb-x86_64.conf', | ||
'/custom/path/custom1.conf'] | ||
|
||
|
||
@pytest.mark.parametrize(('included_configs_modifications', 'used_variables', 'modified_lines'), | ||
[ | ||
([False, False, False], [], []), | ||
([True, True, True], [], []), | ||
([False, False, False], [LD_LIBRARY_PATH_VAR], []), | ||
([False, False, False], [], ['modified line 1', 'midified line 2']), | ||
([True, False, True], [LD_LIBRARY_PATH_VAR, LD_PRELOAD_VAR], ['modified line']), | ||
]) | ||
def test_check_ld_so_configuration(monkeypatch, included_configs_modifications, used_variables, modified_lines): | ||
assert len(INCLUDED_CONFIG_PATHS) == len(included_configs_modifications) | ||
|
||
main_config = MainLDConfigFile(path="/etc/ld.so.conf", modified=any(modified_lines), modified_lines=modified_lines) | ||
included_configs = [] | ||
for path, modified in zip(INCLUDED_CONFIG_PATHS, included_configs_modifications): | ||
included_configs.append(LDConfigFile(path=path, modified=modified)) | ||
|
||
configuration = DynamicLinkerConfiguration(main_config=main_config, | ||
included_configs=included_configs, | ||
used_variables=used_variables) | ||
|
||
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[configuration])) | ||
monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) | ||
|
||
check_dynamic_linker_configuration() | ||
|
||
report_expected = any(included_configs_modifications) or modified_lines or used_variables | ||
if not report_expected: | ||
assert reporting.create_report.called == 0 | ||
return | ||
|
||
assert reporting.create_report.called == 1 | ||
assert 'configuration for dynamic linker' in reporting.create_report.reports[0]['title'] | ||
summary = reporting.create_report.reports[0]['summary'] | ||
|
||
if any(included_configs_modifications): | ||
assert 'The following drop in config files were marked as custom:' in summary | ||
for config, modified in zip(INCLUDED_CONFIG_PATHS, included_configs_modifications): | ||
assert modified == (config in summary) | ||
|
||
if modified_lines: | ||
assert 'The /etc/ld.so.conf file has unexpected contents' in summary | ||
for line in modified_lines: | ||
assert line in summary | ||
|
||
if used_variables: | ||
assert 'The following variables contain unexpected dynamic linker configuration:' in summary | ||
for var in used_variables: | ||
assert '- {}'.format(var) in summary |
23 changes: 23 additions & 0 deletions
23
repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from leapp.actors import Actor | ||
from leapp.libraries.actor.scandynamiclinkerconfiguration import scan_dynamic_linker_configuration | ||
from leapp.models import DynamicLinkerConfiguration, InstalledRedHatSignedRPM | ||
from leapp.tags import FactsPhaseTag, IPUWorkflowTag | ||
|
||
|
||
class ScanDynamicLinkerConfiguration(Actor): | ||
""" | ||
Scan the dynamic linker configuration and find modifications. | ||
The dynamic linker configuration files can be used to replace standard libraries | ||
with different custom libraries. The in-place upgrade does not support customization | ||
of this configuration by user. This actor produces information about detected | ||
modifications. | ||
""" | ||
|
||
name = 'scan_dynamic_linker_configuration' | ||
consumes = (InstalledRedHatSignedRPM,) | ||
produces = (DynamicLinkerConfiguration,) | ||
tags = (FactsPhaseTag, IPUWorkflowTag) | ||
|
||
def process(self): | ||
scan_dynamic_linker_configuration() |
117 changes: 117 additions & 0 deletions
117
.../common/actors/scandynamiclinkerconfiguration/libraries/scandynamiclinkerconfiguration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import glob | ||
import os | ||
|
||
from leapp.libraries.common.rpms import has_package | ||
from leapp.libraries.stdlib import api, CalledProcessError, run | ||
from leapp.models import DynamicLinkerConfiguration, InstalledRedHatSignedRPM, LDConfigFile, MainLDConfigFile | ||
|
||
LD_SO_CONF_DIR = '/etc/ld.so.conf.d' | ||
LD_SO_CONF_MAIN = '/etc/ld.so.conf' | ||
LD_SO_CONF_DEFAULT_INCLUDE = 'ld.so.conf.d/*.conf' | ||
LD_SO_CONF_COMMENT_PREFIX = '#' | ||
LD_LIBRARY_PATH_VAR = 'LD_LIBRARY_PATH' | ||
LD_PRELOAD_VAR = 'LD_PRELOAD' | ||
|
||
|
||
def _read_file(file_path): | ||
with open(file_path, 'r') as fd: | ||
return fd.readlines() | ||
|
||
|
||
def _is_modified(config_path): | ||
""" Decide if the configuration file was modified based on the package it belongs to. """ | ||
result = run(['rpm', '-Vf', config_path], checked=False) | ||
if not result['exit_code']: | ||
return False | ||
modification_flags = result['stdout'].split(' ', 1)[0] | ||
# The file is considered modified only when the checksum does not match | ||
return '5' in modification_flags | ||
|
||
|
||
def _is_included_config_custom(config_path): | ||
if not os.path.isfile(config_path): | ||
return False | ||
|
||
# Check if the config file has any lines that have an effect on dynamic linker configuration | ||
has_effective_line = False | ||
for line in _read_file(config_path): | ||
line = line.strip() | ||
if line and not line.startswith(LD_SO_CONF_COMMENT_PREFIX): | ||
has_effective_line = True | ||
break | ||
|
||
if not has_effective_line: | ||
return False | ||
|
||
is_custom = False | ||
try: | ||
package_name = run(['rpm', '-qf', '--queryformat', '%{NAME}', config_path])['stdout'] | ||
is_custom = not has_package(InstalledRedHatSignedRPM, package_name) or _is_modified(config_path) | ||
except CalledProcessError: | ||
is_custom = True | ||
|
||
return is_custom | ||
|
||
|
||
def _parse_main_config(): | ||
""" | ||
Extracts included configs from the main dynamic linker configuration file (/etc/ld.so.conf) | ||
along with lines that are likely custom. The lines considered custom are simply those that are | ||
not includes. | ||
:returns: tuple containing all the included files and lines considered custom | ||
:rtype: tuple(list, list) | ||
""" | ||
config = _read_file(LD_SO_CONF_MAIN) | ||
|
||
included_configs = [] | ||
other_lines = [] | ||
for line in config: | ||
line = line.strip() | ||
if line.startswith('include'): | ||
cfg_glob = line.split(' ', 1)[1].strip() | ||
cfg_glob = os.path.join('/etc', cfg_glob) if not os.path.isabs(cfg_glob) else cfg_glob | ||
included_configs.append(cfg_glob) | ||
elif line and not line.startswith(LD_SO_CONF_COMMENT_PREFIX): | ||
other_lines.append(line) | ||
|
||
return included_configs, other_lines | ||
|
||
|
||
def scan_dynamic_linker_configuration(): | ||
included_configs, other_lines = _parse_main_config() | ||
|
||
is_default_include_present = '/etc/' + LD_SO_CONF_DEFAULT_INCLUDE in included_configs | ||
if not is_default_include_present: | ||
api.current_logger().debug('The default include "{}" is not present in ' | ||
'the {} file.'.format(LD_SO_CONF_DEFAULT_INCLUDE, LD_SO_CONF_MAIN)) | ||
|
||
if is_default_include_present and len(included_configs) != 1: | ||
# The additional included configs will most likely be created manually by the user | ||
# and therefore will get flagged as custom in the next part of this function | ||
api.current_logger().debug('The default include "{}" is not the only include in ' | ||
'the {} file.'.format(LD_SO_CONF_DEFAULT_INCLUDE, LD_SO_CONF_MAIN)) | ||
|
||
main_config_file = MainLDConfigFile(path=LD_SO_CONF_MAIN, modified=any(other_lines), modified_lines=other_lines) | ||
|
||
# Expand the config paths from globs and ensure uniqueness of resulting paths | ||
config_paths = set() | ||
for cfg_glob in included_configs: | ||
for cfg in glob.glob(cfg_glob): | ||
config_paths.add(cfg) | ||
|
||
included_config_files = [] | ||
for config_path in config_paths: | ||
config_file = LDConfigFile(path=config_path, modified=_is_included_config_custom(config_path)) | ||
included_config_files.append(config_file) | ||
|
||
# Check if dynamic linker variables used for specifying custom libraries are set | ||
variables = [LD_LIBRARY_PATH_VAR, LD_PRELOAD_VAR] | ||
used_variables = [var for var in variables if os.getenv(var, None)] | ||
|
||
configuration = DynamicLinkerConfiguration(main_config=main_config_file, | ||
included_configs=included_config_files, | ||
used_variables=used_variables) | ||
|
||
if other_lines or any([config.modified for config in included_config_files]) or used_variables: | ||
api.produce(configuration) |
Oops, something went wrong.