diff --git a/backport-CVE-2022-2084.patch b/backport-CVE-2022-2084.patch new file mode 100644 index 0000000000000000000000000000000000000000..7bc090f025c0a7db6ad87c8fc23589aaaaef21ad --- /dev/null +++ b/backport-CVE-2022-2084.patch @@ -0,0 +1,90 @@ +From 4d467b14363d800b2185b89790d57871f11ea88c Mon Sep 17 00:00:00 2001 +From: James Falcon +Date: Wed, 29 Jun 2022 17:27:44 -0500 +Subject: [PATCH] Remove schema errors from log (#1551) + +When schema errors are encountered, the section of userdata in question +gets printed to the cloud-init log. As this could contain sensitive +data, so log a generic warning instead and redirect user to run +cloud-init schema --system as root. + +LP: #1978422 +CVE: 2022-2084 +--- + cloudinit/config/schema.py | 15 ++++++++++++--- + tests/unittests/test_handler/test_schema.py | 17 +++++++++++++++++ + 2 files changed, 29 insertions(+), 3 deletions(-) + +diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py +index 456bab2..a1e1a54 100644 +--- a/cloudinit/config/schema.py ++++ b/cloudinit/config/schema.py +@@ -72,7 +72,7 @@ def is_schema_byte_string(checker, instance): + isinstance(instance, (bytes,))) + + +-def validate_cloudconfig_schema(config, schema, strict=False): ++def validate_cloudconfig_schema(config, schema, strict=False, log_details=True): + """Validate provided config meets the schema definition. + + @param config: Dict of cloud configuration settings validated against +@@ -81,6 +81,9 @@ def validate_cloudconfig_schema(config, schema, strict=False): + for the cloud config module (config.cc_*). + @param strict: Boolean, when True raise SchemaValidationErrors instead of + logging warnings. ++ @param log_details: Boolean, when True logs details of validation errors. ++ If there are concerns about logging sensitive userdata, this should ++ be set to False. + + @raises: SchemaValidationError when provided config does not validate + against the provided schema. +@@ -118,10 +121,16 @@ def validate_cloudconfig_schema(config, schema, strict=False): + errors += ((path, error.message),) + if errors: + if strict: ++ # This could output/log sensitive data + raise SchemaValidationError(errors) +- else: ++ if log_details: + messages = ['{0}: {1}'.format(k, msg) for k, msg in errors] +- logging.warning('Invalid config:\n%s', '\n'.join(messages)) ++ details = "\n" + "\n".join(messages) ++ else: ++ details = ( ++ "Please run 'sudo cloud-init devel schema --system' or 'cloud-init devel schema -c config-file' to see the schema errors." ++ ) ++ logging.warning('Invalid config:%s', details) + + + def annotated_cloudconfig_file(cloudconfig, original_content, schema_errors): +diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py +index 1dae223..5650c88 100644 +--- a/tests/unittests/test_handler/test_schema.py ++++ b/tests/unittests/test_handler/test_schema.py +@@ -86,6 +86,23 @@ class ValidateCloudConfigSchemaTest(CiTestCase): + "Invalid config:\np1: -1 is not of type 'string'\n", + self.logs.getvalue()) + ++ @skipUnlessJsonSchema() ++ def test_validateconfig_schema_sensitive(self): ++ """When log_details=False, ensure details are omitted""" ++ schema = { ++ "properties": {"hashed_password": {"type": "string"}}, ++ "additionalProperties": False, ++ } ++ validate_cloudconfig_schema( ++ {"hashed-password": "secret"}, ++ schema, ++ strict=False, ++ log_details=False, ++ ) ++ self.assertIn( ++ "Please run 'sudo cloud-init devel schema --system' or 'cloud-init devel schema -c config-file' to see the schema errors.", ++ self.logs.getvalue()) ++ + @skipUnlessJsonSchema() + def test_validateconfig_schema_emits_warning_on_missing_jsonschema(self): + """Warning from validate_cloudconfig_schema when missing jsonschema.""" +-- +2.33.0 + diff --git a/cloud-init.spec b/cloud-init.spec index 04f9614b2b356e28a599624869cea8c97e59ff01..f10344fec64bc867368fc2342b1f493115630de7 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -1,6 +1,6 @@ Name: cloud-init Version: 21.4 -Release: 13 +Release: 14 Summary: the defacto multi-distribution package that handles early initialization of a cloud instance. License: ASL 2.0 or GPLv3 URL: http://launchpad.net/cloud-init @@ -22,6 +22,8 @@ Patch10: backport-Fix-the-distro.osfamily-output-problem.patch Patch11: backport-netplan-keep-custom-strict-perms-when-50-cloud-init.patch Patch12: backport-Do-not-change-permissions-of-netrules-target.patch Patch13: fix-a-small-unitest-error.patch +Patch14: backport-CVE-2022-2084.patch +Patch15: remove-schema-errors-from-log-for-cloudinit-config-cc_.patch Patch9000: Fix-the-error-level-logs-displayed-for-the-cloud-init-local-service.patch @@ -133,6 +135,9 @@ fi %exclude /usr/share/doc/* %changelog +* Sun May 14 2023 shixuantong - 21.4-14 +- fix CVE-2022-2084 + * Sun Apr 23 2023 shixuantong - 21.4-13 - Fix a unitest error diff --git a/remove-schema-errors-from-log-for-cloudinit-config-cc_.patch b/remove-schema-errors-from-log-for-cloudinit-config-cc_.patch new file mode 100644 index 0000000000000000000000000000000000000000..86f9be049e4c65c6064d4a5f015cf5b4795b89f9 --- /dev/null +++ b/remove-schema-errors-from-log-for-cloudinit-config-cc_.patch @@ -0,0 +1,355 @@ +From 54539aebe19e3fd604723b3cf0da1f34bd86b841 Mon Sep 17 00:00:00 2001 +From: sxt1001 +Date: Sun, 14 May 2023 22:24:32 +0800 +Subject: [PATCH] remove schema errors from log for cloudinit/config/cc_* + +--- + cloudinit/config/cc_apk_configure.py | 2 +- + cloudinit/config/cc_apt_configure.py | 2 +- + cloudinit/config/cc_bootcmd.py | 2 +- + cloudinit/config/cc_chef.py | 2 +- + cloudinit/config/cc_install_hotplug.py | 2 +- + cloudinit/config/cc_locale.py | 2 +- + cloudinit/config/cc_ntp.py | 2 +- + cloudinit/config/cc_resizefs.py | 2 +- + cloudinit/config/cc_runcmd.py | 2 +- + cloudinit/config/cc_snap.py | 2 +- + cloudinit/config/cc_ubuntu_advantage.py | 2 +- + cloudinit/config/cc_ubuntu_drivers.py | 2 +- + cloudinit/config/cc_write_files.py | 2 +- + cloudinit/config/cc_write_files_deferred.py | 2 +- + cloudinit/config/tests/test_snap.py | 5 ++--- + tests/unittests/test_handler/test_handler_bootcmd.py | 8 ++++---- + tests/unittests/test_handler/test_handler_resizefs.py | 4 ++-- + tests/unittests/test_handler/test_handler_runcmd.py | 8 ++++---- + .../test_handler/test_handler_write_files.py | 11 ++++++----- + .../test_handler/test_handler_write_files_deferred.py | 7 +++++-- + 20 files changed, 37 insertions(+), 34 deletions(-) + +diff --git a/cloudinit/config/cc_apk_configure.py b/cloudinit/config/cc_apk_configure.py +index 84d7a0b..09acdbd 100644 +--- a/cloudinit/config/cc_apk_configure.py ++++ b/cloudinit/config/cc_apk_configure.py +@@ -200,7 +200,7 @@ def handle(name, cfg, cloud, log, _args): + " no 'apk_repos' section found"), name) + return + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + + # If "preserve_repositories" is explicitly set to True in + # the configuration do nothing. +diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py +index 86d0fea..342eded 100644 +--- a/cloudinit/config/cc_apt_configure.py ++++ b/cloudinit/config/cc_apt_configure.py +@@ -451,7 +451,7 @@ def handle(name, ocfg, cloud, log, _): + "Expected dictionary for 'apt' config, found {config_type}".format( + config_type=type(cfg))) + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + apply_debconf_selections(cfg, target) + apply_apt(cfg, cloud, target) + +diff --git a/cloudinit/config/cc_bootcmd.py b/cloudinit/config/cc_bootcmd.py +index 246e449..5383126 100644 +--- a/cloudinit/config/cc_bootcmd.py ++++ b/cloudinit/config/cc_bootcmd.py +@@ -84,7 +84,7 @@ def handle(name, cfg, cloud, log, _args): + " no 'bootcmd' key in configuration"), name) + return + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + with temp_utils.ExtendedTemporaryFile(suffix=".sh") as tmpf: + try: + content = util.shellify(cfg["bootcmd"]) +diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py +index 7b20222..cb9fe12 100644 +--- a/cloudinit/config/cc_chef.py ++++ b/cloudinit/config/cc_chef.py +@@ -408,7 +408,7 @@ def handle(name, cfg, cloud, log, _args): + " no 'chef' key in configuration"), name) + return + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + chef_cfg = cfg['chef'] + + # Ensure the chef directories we use exist +diff --git a/cloudinit/config/cc_install_hotplug.py b/cloudinit/config/cc_install_hotplug.py +index da98c40..08cc802 100644 +--- a/cloudinit/config/cc_install_hotplug.py ++++ b/cloudinit/config/cc_install_hotplug.py +@@ -95,7 +95,7 @@ LABEL="cloudinit_end" + + + def handle(_name, cfg, cloud, log, _args): +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + network_hotplug_enabled = ( + 'updates' in cfg and + 'network' in cfg['updates'] and +diff --git a/cloudinit/config/cc_locale.py b/cloudinit/config/cc_locale.py +index 4f8b7bf..ff55186 100644 +--- a/cloudinit/config/cc_locale.py ++++ b/cloudinit/config/cc_locale.py +@@ -71,7 +71,7 @@ def handle(name, cfg, cloud, log, args): + name, locale) + return + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + + log.debug("Setting locale to %s", locale) + locale_cfgfile = util.get_cfg_option_str(cfg, "locale_configfile") +diff --git a/cloudinit/config/cc_ntp.py b/cloudinit/config/cc_ntp.py +index b107359..d37cb3f 100644 +--- a/cloudinit/config/cc_ntp.py ++++ b/cloudinit/config/cc_ntp.py +@@ -539,7 +539,7 @@ def handle(name, cfg, cloud, log, _args): + "'ntp' key existed in config, but not a dictionary type," + " is a {_type} instead".format(_type=type_utils.obj_name(ntp_cfg))) + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + + # Allow users to explicitly enable/disable + enabled = ntp_cfg.get('enabled', True) +diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py +index 990a693..abb812b 100644 +--- a/cloudinit/config/cc_resizefs.py ++++ b/cloudinit/config/cc_resizefs.py +@@ -201,7 +201,7 @@ def handle(name, cfg, _cloud, log, args): + resize_root = args[0] + else: + resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + if not util.translate_bool(resize_root, addons=[NOBLOCK]): + log.debug("Skipping module named %s, resizing disabled", name) + return +diff --git a/cloudinit/config/cc_runcmd.py b/cloudinit/config/cc_runcmd.py +index 15960c7..47905f4 100644 +--- a/cloudinit/config/cc_runcmd.py ++++ b/cloudinit/config/cc_runcmd.py +@@ -85,7 +85,7 @@ def handle(name, cfg, cloud, log, _args): + " no 'runcmd' key in configuration"), name) + return + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + out_fn = os.path.join(cloud.get_ipath('scripts'), "runcmd") + cmd = cfg["runcmd"] + try: +diff --git a/cloudinit/config/cc_snap.py b/cloudinit/config/cc_snap.py +index 20ed7d2..003955d 100644 +--- a/cloudinit/config/cc_snap.py ++++ b/cloudinit/config/cc_snap.py +@@ -239,7 +239,7 @@ def handle(name, cfg, cloud, log, args): + " no 'snap' key in configuration"), name) + return + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + if util.is_true(cfgin.get('squashfuse_in_container', False)): + maybe_install_squashfuse(cloud) + add_assertions(cfgin.get('assertions', [])) +diff --git a/cloudinit/config/cc_ubuntu_advantage.py b/cloudinit/config/cc_ubuntu_advantage.py +index d61dc65..e55772a 100644 +--- a/cloudinit/config/cc_ubuntu_advantage.py ++++ b/cloudinit/config/cc_ubuntu_advantage.py +@@ -163,7 +163,7 @@ def handle(name, cfg, cloud, log, args): + LOG.debug("Skipping module named %s," + " no 'ubuntu_advantage' configuration found", name) + return +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + if 'commands' in ua_section: + msg = ( + 'Deprecated configuration "ubuntu-advantage: commands" provided.' +diff --git a/cloudinit/config/cc_ubuntu_drivers.py b/cloudinit/config/cc_ubuntu_drivers.py +index 2d1d2b3..7ee3fef 100644 +--- a/cloudinit/config/cc_ubuntu_drivers.py ++++ b/cloudinit/config/cc_ubuntu_drivers.py +@@ -157,5 +157,5 @@ def handle(name, cfg, cloud, log, _args): + log.debug("Skipping module named %s, no 'drivers' key in config", name) + return + +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + install_drivers(cfg['drivers'], cloud.distro.install_packages) +diff --git a/cloudinit/config/cc_write_files.py b/cloudinit/config/cc_write_files.py +index 41c75fa..b1678b8 100644 +--- a/cloudinit/config/cc_write_files.py ++++ b/cloudinit/config/cc_write_files.py +@@ -191,7 +191,7 @@ __doc__ = get_schema_doc(schema) # Supplement python help() + + + def handle(name, cfg, _cloud, log, _args): +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + file_list = cfg.get('write_files', []) + filtered_files = [ + f for f in file_list if not util.get_cfg_option_bool(f, +diff --git a/cloudinit/config/cc_write_files_deferred.py b/cloudinit/config/cc_write_files_deferred.py +index 0c75aa2..b055871 100644 +--- a/cloudinit/config/cc_write_files_deferred.py ++++ b/cloudinit/config/cc_write_files_deferred.py +@@ -38,7 +38,7 @@ __doc__ = None + + + def handle(name, cfg, _cloud, log, _args): +- validate_cloudconfig_schema(cfg, schema) ++ validate_cloudconfig_schema(cfg, schema, log_details=False) + file_list = cfg.get('write_files', []) + filtered_files = [ + f for f in file_list if util.get_cfg_option_bool(f, +diff --git a/cloudinit/config/tests/test_snap.py b/cloudinit/config/tests/test_snap.py +index 6d4c014..8877c8e 100644 +--- a/cloudinit/config/tests/test_snap.py ++++ b/cloudinit/config/tests/test_snap.py +@@ -503,9 +503,8 @@ class TestHandle(CiTestCase): + 'cloudinit.config.cc_snap', + {'ASSERTIONS_FILE': {'new': assert_file}}, + handle, 'snap', cfg=cfg, cloud=None, log=self.logger, args=None) +- self.assertEqual( +- "WARNING: Invalid config:\nsnap: Additional properties are not" +- " allowed ('invalid' was unexpected)\n", ++ self.assertIn( ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or 'cloud-init devel schema -c config-file' to see the schema errors.", + self.logs.getvalue()) + + +diff --git a/tests/unittests/test_handler/test_handler_bootcmd.py b/tests/unittests/test_handler/test_handler_bootcmd.py +index 8cd3a5e..4368640 100644 +--- a/tests/unittests/test_handler/test_handler_bootcmd.py ++++ b/tests/unittests/test_handler/test_handler_bootcmd.py +@@ -70,7 +70,8 @@ class TestBootcmd(CiTestCase): + with self.assertRaises(TypeError): + handle('cc_bootcmd', invalid_config, cc, LOG, []) + self.assertIn( +- 'Invalid config:\nbootcmd: 1 is not of type \'array\'', ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + self.logs.getvalue()) + self.assertIn('Failed to shellify', self.logs.getvalue()) + +@@ -87,9 +88,8 @@ class TestBootcmd(CiTestCase): + with self.assertRaises(TypeError) as context_manager: + handle('cc_bootcmd', invalid_config, cc, LOG, []) + expected_warnings = [ +- 'bootcmd.1: 20 is not valid under any of the given schemas', +- 'bootcmd.3: {\'a\': \'n\'} is not valid under any of the given' +- ' schema' ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + ] + logs = self.logs.getvalue() + for warning in expected_warnings: +diff --git a/tests/unittests/test_handler/test_handler_resizefs.py b/tests/unittests/test_handler/test_handler_resizefs.py +index 28d5507..a4d15ed 100644 +--- a/tests/unittests/test_handler/test_handler_resizefs.py ++++ b/tests/unittests/test_handler/test_handler_resizefs.py +@@ -79,8 +79,8 @@ class TestResizefs(CiTestCase): + handle('cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) + logs = self.logs.getvalue() + self.assertIn( +- "WARNING: Invalid config:\nresize_rootfs: 'junk' is not one of" +- " [True, False, 'noblock']", ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + logs) + self.assertIn( + 'DEBUG: Skipping module named cc_resizefs, resizing disabled\n', +diff --git a/tests/unittests/test_handler/test_handler_runcmd.py b/tests/unittests/test_handler/test_handler_runcmd.py +index 672e809..79785df 100644 +--- a/tests/unittests/test_handler/test_handler_runcmd.py ++++ b/tests/unittests/test_handler/test_handler_runcmd.py +@@ -69,7 +69,8 @@ class TestRuncmd(FilesystemMockingTestCase): + with self.assertRaises(TypeError) as cm: + handle('cc_runcmd', invalid_config, cc, LOG, []) + self.assertIn( +- 'Invalid config:\nruncmd: 1 is not of type \'array\'', ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + self.logs.getvalue()) + self.assertIn('Failed to shellify', str(cm.exception)) + +@@ -86,9 +87,8 @@ class TestRuncmd(FilesystemMockingTestCase): + with self.assertRaises(TypeError) as cm: + handle('cc_runcmd', invalid_config, cc, LOG, []) + expected_warnings = [ +- 'runcmd.1: 20 is not valid under any of the given schemas', +- 'runcmd.3: {\'a\': \'n\'} is not valid under any of the given' +- ' schema' ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + ] + logs = self.logs.getvalue() + for warning in expected_warnings: +diff --git a/tests/unittests/test_handler/test_handler_write_files.py b/tests/unittests/test_handler/test_handler_write_files.py +index 0af9280..3c5093e 100644 +--- a/tests/unittests/test_handler/test_handler_write_files.py ++++ b/tests/unittests/test_handler/test_handler_write_files.py +@@ -69,7 +69,6 @@ class TestWriteFilesSchema(CiTestCase): + self.assertNotIn('Invalid config:', self.logs.getvalue()) + handle('cc_write_file', INVALID_SCHEMA, cc, LOG, []) + self.assertIn('Invalid config:', self.logs.getvalue()) +- self.assertIn("'path' is a required property", self.logs.getvalue()) + + def test_schema_validation_warns_non_string_type_for_files( + self, m_write_files): +@@ -87,7 +86,8 @@ class TestWriteFilesSchema(CiTestCase): + mock.call('cc_write_file', invalid_config['write_files']), + m_write_files.call_args_list) + self.assertIn( +- 'write_files.0.%s: 1 is not of type \'%s\'' % (key, key_type), ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + self.logs.getvalue()) + self.assertIn('Invalid config:', self.logs.getvalue()) + +@@ -99,8 +99,8 @@ class TestWriteFilesSchema(CiTestCase): + invalid_config['write_files'][0]['bogus'] = 'value' + handle('cc_write_file', invalid_config, cc, LOG, []) + self.assertIn( +- "Invalid config:\nwrite_files.0: Additional properties" +- " are not allowed ('bogus' was unexpected)", ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + self.logs.getvalue()) + + +@@ -121,7 +121,8 @@ class TestWriteFiles(FilesystemMockingTestCase): + with self.assertRaises(TypeError): + handle('cc_write_file', invalid_config, cc, LOG, []) + self.assertIn( +- 'Invalid config:\nwrite_files: 1 is not of type \'array\'', ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", + self.logs.getvalue()) + + def test_simple(self): +diff --git a/tests/unittests/test_handler/test_handler_write_files_deferred.py b/tests/unittests/test_handler/test_handler_write_files_deferred.py +index 57b6934..48a4f5e 100644 +--- a/tests/unittests/test_handler/test_handler_write_files_deferred.py ++++ b/tests/unittests/test_handler/test_handler_write_files_deferred.py +@@ -41,8 +41,11 @@ class TestWriteFilesDeferredSchema(CiTestCase): + self.assertNotIn('Invalid config:', self.logs.getvalue()) + handle('cc_write_files_deferred', invalid_config, cc, LOG, []) + self.assertIn('Invalid config:', self.logs.getvalue()) +- self.assertIn("defer: 'no' is not of type 'boolean'", +- self.logs.getvalue()) ++ self.assertIn( ++ "Invalid config:Please run 'sudo cloud-init devel schema --system' or" ++ " 'cloud-init devel schema -c config-file' to see the schema errors.", ++ self.logs.getvalue() ++ ) + + + class TestWriteFilesDeferred(FilesystemMockingTestCase): +-- +2.33.0 +