leapp-repository/0038-add-detection-for-custom-libraries-registered-by-ld..patch

617 lines
29 KiB
Diff
Raw Normal View History

From 7dabc85a0ab5595bd4c7b232c78f14d04eed40fc Mon Sep 17 00:00:00 2001
From: PeterMocary <petermocary@gmail.com>
Date: Tue, 22 Aug 2023 17:03:48 +0200
Subject: [PATCH 38/38] 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
---
.../checkdynamiclinkerconfiguration/actor.py | 22 +++
.../checkdynamiclinkerconfiguration.py | 79 ++++++++
.../test_checkdynamiclinkerconfiguration.py | 65 +++++++
.../scandynamiclinkerconfiguration/actor.py | 23 +++
.../scandynamiclinkerconfiguration.py | 117 +++++++++++
.../test_scandynamiclinkerconfiguration.py | 181 ++++++++++++++++++
.../common/models/dynamiclinker.py | 41 ++++
7 files changed, 528 insertions(+)
create mode 100644 repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/actor.py
create mode 100644 repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/libraries/checkdynamiclinkerconfiguration.py
create mode 100644 repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/tests/test_checkdynamiclinkerconfiguration.py
create mode 100644 repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/actor.py
create mode 100644 repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/libraries/scandynamiclinkerconfiguration.py
create mode 100644 repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/tests/test_scandynamiclinkerconfiguration.py
create mode 100644 repos/system_upgrade/common/models/dynamiclinker.py
diff --git a/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/actor.py b/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/actor.py
new file mode 100644
index 00000000..6671eef4
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/actor.py
@@ -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()
diff --git a/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/libraries/checkdynamiclinkerconfiguration.py b/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/libraries/checkdynamiclinkerconfiguration.py
new file mode 100644
index 00000000..9ead892e
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/libraries/checkdynamiclinkerconfiguration.py
@@ -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)
diff --git a/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/tests/test_checkdynamiclinkerconfiguration.py b/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/tests/test_checkdynamiclinkerconfiguration.py
new file mode 100644
index 00000000..d640f0c5
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkdynamiclinkerconfiguration/tests/test_checkdynamiclinkerconfiguration.py
@@ -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
diff --git a/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/actor.py b/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/actor.py
new file mode 100644
index 00000000..11283cd0
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/actor.py
@@ -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()
diff --git a/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/libraries/scandynamiclinkerconfiguration.py b/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/libraries/scandynamiclinkerconfiguration.py
new file mode 100644
index 00000000..1a6ab6a2
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/libraries/scandynamiclinkerconfiguration.py
@@ -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)
diff --git a/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/tests/test_scandynamiclinkerconfiguration.py b/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/tests/test_scandynamiclinkerconfiguration.py
new file mode 100644
index 00000000..21144951
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scandynamiclinkerconfiguration/tests/test_scandynamiclinkerconfiguration.py
@@ -0,0 +1,181 @@
+import glob
+import os
+
+import pytest
+
+from leapp import reporting
+from leapp.libraries.actor import scandynamiclinkerconfiguration
+from leapp.libraries.common.testutils import produce_mocked
+from leapp.libraries.stdlib import api, CalledProcessError
+from leapp.models import InstalledRedHatSignedRPM
+
+INCLUDED_CONFIGS_GLOB_DICT_1 = {'/etc/ld.so.conf.d/*.conf': ['/etc/ld.so.conf.d/dyninst-x86_64.conf',
+ '/etc/ld.so.conf.d/mariadb-x86_64.conf',
+ '/etc/ld.so.conf.d/bind-export-x86_64.conf']}
+
+INCLUDED_CONFIGS_GLOB_DICT_2 = {'/etc/ld.so.conf.d/*.conf': ['/etc/ld.so.conf.d/dyninst-x86_64.conf',
+ '/etc/ld.so.conf.d/mariadb-x86_64.conf',
+ '/etc/ld.so.conf.d/bind-export-x86_64.conf',
+ '/etc/ld.so.conf.d/custom1.conf',
+ '/etc/ld.so.conf.d/custom2.conf']}
+
+INCLUDED_CONFIGS_GLOB_DICT_3 = {'/etc/ld.so.conf.d/*.conf': ['/etc/ld.so.conf.d/dyninst-x86_64.conf',
+ '/etc/ld.so.conf.d/custom1.conf',
+ '/etc/ld.so.conf.d/mariadb-x86_64.conf',
+ '/etc/ld.so.conf.d/bind-export-x86_64.conf',
+ '/etc/ld.so.conf.d/custom2.conf'],
+ '/custom/path/*.conf': ['/custom/path/custom1.conf',
+ '/custom/path/custom2.conf']}
+
+
+@pytest.mark.parametrize(('included_configs_glob_dict', 'other_lines', 'custom_configs', 'used_variables'),
+ [
+ (INCLUDED_CONFIGS_GLOB_DICT_1, [], [], []),
+ (INCLUDED_CONFIGS_GLOB_DICT_1, ['/custom/path.lib'], [], []),
+ (INCLUDED_CONFIGS_GLOB_DICT_1, [], [], ['LD_LIBRARY_PATH']),
+ (INCLUDED_CONFIGS_GLOB_DICT_2, [], ['/etc/ld.so.conf.d/custom1.conf',
+ '/etc/ld.so.conf.d/custom2.conf'], []),
+ (INCLUDED_CONFIGS_GLOB_DICT_3, ['/custom/path.lib'], ['/etc/ld.so.conf.d/custom1.conf',
+ '/etc/ld.so.conf.d/custom2.conf'
+ '/custom/path/custom1.conf',
+ '/custom/path/custom2.conf'], []),
+ ])
+def test_scan_dynamic_linker_configuration(monkeypatch, included_configs_glob_dict, other_lines,
+ custom_configs, used_variables):
+ monkeypatch.setattr(scandynamiclinkerconfiguration, '_parse_main_config',
+ lambda: (included_configs_glob_dict.keys(), other_lines))
+ monkeypatch.setattr(glob, 'glob', lambda glob: included_configs_glob_dict[glob])
+ monkeypatch.setattr(scandynamiclinkerconfiguration, '_is_included_config_custom',
+ lambda config: config in custom_configs)
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ for var in used_variables:
+ monkeypatch.setenv(var, '/some/path')
+
+ scandynamiclinkerconfiguration.scan_dynamic_linker_configuration()
+
+ produce_expected = custom_configs or other_lines or used_variables
+ if not produce_expected:
+ assert not api.produce.called
+ return
+
+ assert api.produce.called == 1
+
+ configuration = api.produce.model_instances[0]
+
+ all_configs = []
+ for configs in included_configs_glob_dict.values():
+ all_configs += configs
+
+ assert len(all_configs) == len(configuration.included_configs)
+ for config in configuration.included_configs:
+ if config.path in custom_configs:
+ assert config.modified
+
+ assert configuration.main_config.path == scandynamiclinkerconfiguration.LD_SO_CONF_MAIN
+ if other_lines:
+ assert configuration.main_config.modified
+ assert configuration.main_config.modified_lines == other_lines
+
+ if used_variables:
+ assert configuration.used_variables == used_variables
+
+
+@pytest.mark.parametrize(('config_contents', 'included_config_paths', 'other_lines'),
+ [
+ (['include ld.so.conf.d/*.conf\n'],
+ ['/etc/ld.so.conf.d/*.conf'], []),
+ (['include ld.so.conf.d/*.conf\n', '\n', '/custom/path.lib\n', '#comment'],
+ ['/etc/ld.so.conf.d/*.conf'], ['/custom/path.lib']),
+ (['include ld.so.conf.d/*.conf\n', 'include /custom/path.conf\n'],
+ ['/etc/ld.so.conf.d/*.conf', '/custom/path.conf'], []),
+ (['include ld.so.conf.d/*.conf\n', '#include /custom/path.conf\n', '#/custom/path.conf\n'],
+ ['/etc/ld.so.conf.d/*.conf'], []),
+ ([' \n'],
+ [], [])
+ ])
+def test_parse_main_config(monkeypatch, config_contents, included_config_paths, other_lines):
+ def mocked_read_file(path):
+ assert path == scandynamiclinkerconfiguration.LD_SO_CONF_MAIN
+ return config_contents
+
+ monkeypatch.setattr(scandynamiclinkerconfiguration, '_read_file', mocked_read_file)
+
+ _included_config_paths, _other_lines = scandynamiclinkerconfiguration._parse_main_config()
+
+ assert _included_config_paths == included_config_paths
+ assert _other_lines == other_lines
+
+
+@pytest.mark.parametrize(('config_path', 'run_result', 'is_modified'),
+ [
+ ('/etc/ld.so.conf.d/dyninst-x86_64.conf',
+ '.......T. c /etc/ld.so.conf.d/dyninst-x86_64.conf', False),
+ ('/etc/ld.so.conf.d/dyninst-x86_64.conf',
+ 'S.5....T. c /etc/ld.so.conf.d/dyninst-x86_64.conf', True),
+ ('/etc/ld.so.conf.d/kernel-3.10.0-1160.el7.x86_64.conf',
+ '', False)
+ ])
+def test_is_modified(monkeypatch, config_path, run_result, is_modified):
+ def mocked_run(command, checked):
+ assert config_path in command
+ assert checked is False
+ exit_code = 1 if run_result else 0
+ return {'stdout': run_result, 'exit_code': exit_code}
+
+ monkeypatch.setattr(scandynamiclinkerconfiguration, 'run', mocked_run)
+
+ _is_modified = scandynamiclinkerconfiguration._is_modified(config_path)
+ assert _is_modified == is_modified
+
+
+@pytest.mark.parametrize(('config_path',
+ 'config_contents', 'run_result',
+ 'is_installed_rh_signed_package', 'is_modified', 'has_effective_lines'),
+ [
+ ('/etc/ld.so.conf.d/dyninst-x86_64.conf',
+ ['/usr/lib64/dyninst\n'], 'dyninst',
+ True, False, True), # RH sighend package without modification - Not custom
+ ('/etc/ld.so.conf.d/dyninst-x86_64.conf',
+ ['/usr/lib64/my_dyninst\n'], 'dyninst',
+ True, True, True), # Was modified by user - Custom
+ ('/etc/custom/custom.conf',
+ ['/usr/lib64/custom'], 'custom',
+ False, None, True), # Third-party package - Custom
+ ('/etc/custom/custom.conf',
+ ['#/usr/lib64/custom\n'], 'custom',
+ False, None, False), # Third-party package without effective lines - Not custom
+ ('/etc/ld.so.conf.d/somelib.conf',
+ ['/usr/lib64/somelib\n'], CalledProcessError,
+ None, None, True), # User created configuration file - Custom
+ ('/etc/ld.so.conf.d/somelib.conf',
+ ['#/usr/lib64/somelib\n'], CalledProcessError,
+ None, None, False) # User created configuration file without effective lines - Not custom
+ ])
+def test_is_included_config_custom(monkeypatch, config_path, config_contents, run_result,
+ is_installed_rh_signed_package, is_modified, has_effective_lines):
+ def mocked_run(command):
+ assert config_path in command
+ if run_result and not isinstance(run_result, str):
+ raise CalledProcessError("message", command, "result")
+ return {'stdout': run_result}
+
+ def mocked_has_package(model, package_name):
+ assert model is InstalledRedHatSignedRPM
+ assert package_name == run_result
+ return is_installed_rh_signed_package
+
+ def mocked_read_file(path):
+ assert path == config_path
+ return config_contents
+
+ monkeypatch.setattr(scandynamiclinkerconfiguration, 'run', mocked_run)
+ monkeypatch.setattr(scandynamiclinkerconfiguration, 'has_package', mocked_has_package)
+ monkeypatch.setattr(scandynamiclinkerconfiguration, '_read_file', mocked_read_file)
+ monkeypatch.setattr(scandynamiclinkerconfiguration, '_is_modified', lambda *_: is_modified)
+ monkeypatch.setattr(os.path, 'isfile', lambda _: True)
+
+ result = scandynamiclinkerconfiguration._is_included_config_custom(config_path)
+ is_custom = not isinstance(run_result, str) or not is_installed_rh_signed_package or is_modified
+ is_custom &= has_effective_lines
+ assert result == is_custom
diff --git a/repos/system_upgrade/common/models/dynamiclinker.py b/repos/system_upgrade/common/models/dynamiclinker.py
new file mode 100644
index 00000000..4dc107f4
--- /dev/null
+++ b/repos/system_upgrade/common/models/dynamiclinker.py
@@ -0,0 +1,41 @@
+from leapp.models import fields, Model
+from leapp.topics import SystemFactsTopic
+
+
+class LDConfigFile(Model):
+ """
+ Represents a config file related to dynamic linker configuration
+ """
+ topic = SystemFactsTopic
+
+ path = fields.String()
+ """ Absolute path to the configuration file """
+
+ modified = fields.Boolean()
+ """ If True the file is considered custom and will trigger a report """
+
+
+class MainLDConfigFile(LDConfigFile):
+ """
+ Represents the main configuration file of the dynamic linker /etc/ld.so.conf
+ """
+ topic = SystemFactsTopic
+
+ modified_lines = fields.List(fields.String(), default=[])
+ """ Lines that are considered custom, generally those that are not includes of other configs """
+
+
+class DynamicLinkerConfiguration(Model):
+ """
+ Facts about configuration of dynamic linker
+ """
+ topic = SystemFactsTopic
+
+ main_config = fields.Model(MainLDConfigFile)
+ """ The main configuration file of dynamic linker (/etc/ld.so.conf) """
+
+ included_configs = fields.List(fields.Model(LDConfigFile))
+ """ All the configs that are included by the main configuration file """
+
+ used_variables = fields.List(fields.String(), default=[])
+ """ Environment variables that are currently used to modify dynamic linker configuration """
--
2.41.0