diff -Nru nova-17.0.13/debian/changelog nova-17.0.13/debian/changelog --- nova-17.0.13/debian/changelog 2022-06-23 06:41:00.000000000 +0000 +++ nova-17.0.13/debian/changelog 2023-04-26 20:21:53.000000000 +0000 @@ -1,3 +1,36 @@ +nova (2:17.0.13-0ubuntu5.4) bionic; urgency=medium + + * Update keypairs when saving an instance (LP: #1843708) + - d/p/objects-Update-keypairs-when-saving-an-instance.patch + + -- Zhang Hua Thu, 27 Apr 2023 04:21:53 +0800 + +nova (2:17.0.13-0ubuntu5.3) bionic-security; urgency=medium + + * SECURITY UPDATE: information disclosure vulnerability + - debian/patches/CVE-2015-9543.patch: Mask the token used to allow + access to consoles + - CVE-2015-9543 + * SECURITY UPDATE: machine takeover vulnerability + - debian/patches/CVE-2020-17376.patch: libvirt: Provide + VIR_MIGRATE_PARAM_PERSIST_XML during live migration + - CVE-2020-17376 + * SECURITY UPDATE: open redirect vulnerability + - debian/patches/CVE-2021-3654-*.patch: Reject open redirection in the + console proxy + - CVE-2021-3654 + + -- Nishit Majithia Fri, 10 Feb 2023 14:20:43 +0530 + +nova (2:17.0.13-0ubuntu5.2) bionic-security; urgency=medium + + * SECURITY UPDATE: Arbitrary file access + - debian/patches/CVE-2022-47951.patch: Check VMDK create-type + against an allowed list. + - CVE-2022-47951 + + -- Marc Deslauriers Mon, 06 Feb 2023 08:21:41 -0500 + nova (2:17.0.13-0ubuntu5) bionic; urgency=medium * Fixes API to disallow source compute service/node deletion while instances diff -Nru nova-17.0.13/debian/patches/CVE-2015-9543.patch nova-17.0.13/debian/patches/CVE-2015-9543.patch --- nova-17.0.13/debian/patches/CVE-2015-9543.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-17.0.13/debian/patches/CVE-2015-9543.patch 2023-02-10 08:49:50.000000000 +0000 @@ -0,0 +1,133 @@ +From 2927519cf05a40cdd13d21739e39e7aaf574e5c1 Mon Sep 17 00:00:00 2001 +From: Balazs Gibizer +Date: Fri, 23 Aug 2019 15:51:34 +0200 +Subject: [PATCH] Mask the token used to allow access to consoles + +Hide the novncproxy token from the logs. + +Conflicts: + nova/tests/unit/consoleauth/test_consoleauth.py +NOTE: conflict is due to Iffdd4e251bfa2bac1bfd49498e32b738843709de is +only backported till Queens. + +Co-Authored-By:paul-carlton2 +Co-Authored-By:Tristan Cacqueray + +Change-Id: I5b8fa4233d297722c3af08176901d12887bae3de +Closes-Bug: #1492140 +(cherry picked from commit 26d4047e17eba9bc271f8868f1d0ffeec97b555e) +(cherry picked from commit d7826bcd761af035f3f76f67c607dde2a1d04e48) +(cherry picked from commit d8fbf04f325f593836f8d44b6bbf42b85bde94e3) +(cherry picked from commit 08f1f914cc219cf526adfb08c46b8f40b4e78232) +(cherry picked from commit 366515dcd1090ca2f9f303009c78394b5665ce1f) +--- + nova/console/websocketproxy.py | 6 +++++- + nova/consoleauth/manager.py | 9 ++++---- + .../tests/unit/console/test_websocketproxy.py | 3 +++ + .../unit/consoleauth/test_consoleauth.py | 21 ++++++++++++++++++- + 4 files changed, 32 insertions(+), 7 deletions(-) + +--- nova-17.0.13.orig/nova/console/websocketproxy.py ++++ nova-17.0.13/nova/console/websocketproxy.py +@@ -18,6 +18,7 @@ Websocket proxy that is compatible with + Leverages websockify.py by Joel Martin + ''' + ++import copy + import socket + import sys + +@@ -183,7 +184,10 @@ class NovaProxyRequestHandlerBase(object + detail = _("Origin header protocol does not match this host.") + raise exception.ValidationError(detail=detail) + +- self.msg(_('connect info: %s'), str(connect_info)) ++ sanitized_info = copy.copy(connect_info) ++ sanitized_info['token'] = '***' ++ self.msg(_('connect info: %s'), sanitized_info) ++ + host = connect_info['host'] + port = int(connect_info['port']) + +--- nova-17.0.13.orig/nova/consoleauth/manager.py ++++ nova-17.0.13/nova/consoleauth/manager.py +@@ -100,9 +100,8 @@ class ConsoleAuthManager(manager.Manager + + self.mc_instance.set(instance_uuid.encode('UTF-8'), + jsonutils.dumps(tokens)) +- +- LOG.info("Received Token: %(token)s, %(token_dict)s", +- {'token': token, 'token_dict': token_dict}) ++ token_dict['token'] = '***' ++ LOG.info("Received Token: %(token_dict)s", {'token_dict': token_dict}) + + def _validate_token(self, context, token): + instance_uuid = token['instance_uuid'] +@@ -130,8 +129,8 @@ class ConsoleAuthManager(manager.Manager + def check_token(self, context, token): + token_str = self.mc.get(token.encode('UTF-8')) + token_valid = (token_str is not None) +- LOG.info("Checking Token: %(token)s, %(token_valid)s", +- {'token': token, 'token_valid': token_valid}) ++ LOG.info("Checking that token is known: %(token_valid)s", ++ {'token_valid': token_valid}) + if token_valid: + token = jsonutils.loads(token_str) + if self._validate_token(context, token): +--- nova-17.0.13.orig/nova/tests/unit/console/test_websocketproxy.py ++++ nova-17.0.13/nova/tests/unit/console/test_websocketproxy.py +@@ -109,6 +109,9 @@ class NovaProxyRequestHandlerBaseTestCas + check_token.assert_called_with(mock.ANY, token="123-456-789") + self.wh.socket.assert_called_with('node1', 10000, connect=True) + self.wh.do_proxy.assert_called_with('') ++ # ensure that token is masked when logged ++ connection_info = self.wh.msg.mock_calls[0][1][1] ++ self.assertEqual('***', connection_info['token']) + + @mock.patch('nova.consoleauth.rpcapi.ConsoleAuthAPI.check_token') + def test_new_websocket_client_ipv6_url(self, check_token): +--- nova-17.0.13.orig/nova/tests/unit/consoleauth/test_consoleauth.py ++++ nova-17.0.13/nova/tests/unit/consoleauth/test_consoleauth.py +@@ -88,6 +88,17 @@ class ConsoleauthTestCase(test.NoDBTestC + self.stub_out(self.rpcapi + 'validate_console_port', + fake_validate_console_port) + ++ @mock.patch('nova.consoleauth.manager.LOG.info') ++ def test_authorize_does_not_log_token_secrete(self, mock_info): ++ self.manager_api.authorize_console( ++ self.context, 'secret', 'novnc', '127.0.0.1', '8080', 'host', ++ self.instance_uuid) ++ ++ mock_info.assert_called_once_with( ++ 'Received Token: %(token_dict)s', test.MatchType(dict)) ++ self.assertEqual( ++ '***', mock_info.mock_calls[0][1][1]['token_dict']['token']) ++ + @mock.patch('nova.objects.instance.Instance.get_by_uuid') + def test_multiple_tokens_for_instance(self, mock_get): + mock_get.return_value = None +@@ -139,8 +150,9 @@ class ConsoleauthTestCase(test.NoDBTestC + mock_delete.assert_called_once_with( + self.instance_uuid.encode('UTF-8')) + ++ @mock.patch('nova.consoleauth.manager.LOG.info') + @mock.patch('nova.objects.instance.Instance.get_by_uuid') +- def test_wrong_token_has_port(self, mock_get): ++ def test_wrong_token_has_port(self, mock_get, mock_log): + mock_get.return_value = None + + token = u'mytok' +@@ -151,6 +163,13 @@ class ConsoleauthTestCase(test.NoDBTestC + '127.0.0.1', '8080', 'host', + instance_uuid=self.instance_uuid) + self.assertIsNone(self.manager_api.check_token(self.context, token)) ++ mock_log.assert_has_calls([ ++ mock.call( ++ 'Received Token: %(token_dict)s', mock.ANY), ++ mock.call( ++ 'Checking that token is known: %(token_valid)s', ++ {'token_valid': True}), ++ ]) + + def test_delete_expired_tokens(self): + self.useFixture(test.TimeOverride()) diff -Nru nova-17.0.13/debian/patches/CVE-2020-17376.patch nova-17.0.13/debian/patches/CVE-2020-17376.patch --- nova-17.0.13/debian/patches/CVE-2020-17376.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-17.0.13/debian/patches/CVE-2020-17376.patch 2023-02-10 08:50:14.000000000 +0000 @@ -0,0 +1,224 @@ +From a721ca5f510ce3c8ef24f22dac9e475b3d7651db Mon Sep 17 00:00:00 2001 +From: Lee Yarwood +Date: Wed, 05 Aug 2020 23:00:06 +0100 +Subject: [PATCH] libvirt: Provide VIR_MIGRATE_PARAM_PERSIST_XML during live migration + +The VIR_MIGRATE_PARAM_PERSIST_XML parameter was introduced in libvirt +v1.3.4 and is used to provide the new persistent configuration for the +destination during a live migration: + +https://libvirt.org/html/libvirt-libvirt-domain.html#VIR_MIGRATE_PARAM_PERSIST_XML + +Without this parameter the persistent configuration on the destination +will be the same as the original persistent configuration on the source +when the VIR_MIGRATE_PERSIST_DEST flag is provided. + +As Nova does not currently provide the VIR_MIGRATE_PARAM_PERSIST_XML +param but does provide the VIR_MIGRATE_PERSIST_DEST flag this means that +a soft reboot by Nova of the instance after a live migration can revert +the domain back to the original persistent configuration from the +source. + +Note that this is only possible in Nova as a soft reboot actually +results in the virDomainShutdown and virDomainLaunch libvirt APIs being +called that recreate the domain using the persistent configuration. +virDomainReboot does not result in this but is not called at this time. + +The impact of this on the instance after the soft reboot is pretty +severe, host devices referenced in the original persistent configuration +on the source may not exist or could even be used by other users on the +destination. CPU and NUMA affinity could also differ drastically between +the two hosts resulting in the instance being unable to start etc. + +As MIN_LIBVIRT_VERSION is now > v1.3.4 this change simply includes the +VIR_MIGRATE_PARAM_PERSIST_XML param using the same updated XML for the +destination as is already provided to VIR_MIGRATE_PARAM_DEST_XML. + +Conflicts: + nova/tests/unit/virt/libvirt/test_driver.py + nova/tests/unit/virt/test_virt_drivers.py + nova/virt/libvirt/driver.py + nova/virt/libvirt/guest.py + +NOTE(lyarwood): Conflicts as If0a091a7441f2c3269148e40ececc3696d69684c +(libvirt: Bump MIN_{LIBVIRT,QEMU}_VERSION for "Rocky"), +Id9ee1feeadf612fa79c3d280cee3a614a74a00a7 (libvirt: Remove usage of +migrateToURI{2} APIs) and I3af68f745ffb23ef2b5407ccec0bebf4b2645734 +(Remove mox in test_virt_drivers.py) are not present on stable/queens. +As a result we can now add the parameter directly in +_live_migration_operation before calling down into guest.migrate. + +Co-authored-by: Tadayoshi Hosoya +Closes-Bug: #1890501 +Change-Id: Ia3f1d8e83cbc574ce5cb440032e12bbcb1e10e98 +(cherry picked from commit 1bb8ee95d4c3ddc3f607ac57526b75af1b7fbcff) +(cherry picked from commit bbf9d1de06e9991acd968fceee899a8df3776d60) +(cherry picked from commit 6a07edb4b29d8bfb5c86ed14263f7cd7525958c1) +(cherry picked from commit b9ea91d17703f5b324a50727b6503ace0f4e95eb) +(cherry picked from commit c438fd9a0eb1903306a53ab44e3ae80660d8a429) +--- + +--- nova-17.0.13.orig/nova/tests/unit/virt/libvirt/test_driver.py ++++ nova-17.0.13/nova/tests/unit/virt/libvirt/test_driver.py +@@ -9138,6 +9138,76 @@ class LibvirtConnTestCase(test.NoDBTestC + 'instance', data, block_device_info=bdi)) + self.assertEqual(0, mock_get_instance_disk_info.call_count) + ++ @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3") ++ @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml') ++ @mock.patch.object(fakelibvirt.Connection, 'getLibVersion') ++ def test_live_migration_persistent_xml( ++ self, mock_get_version, mock_get_updated_xml, mock_migrateToURI3): ++ """Assert that persistent_xml only provided when libvirt is >= v1.3.4 ++ """ ++ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) ++ instance = self.test_instance ++ dest = '127.0.0.1' ++ block_migration = False ++ migrate_data = objects.LibvirtLiveMigrateData( ++ graphics_listen_addr_vnc='10.0.0.1', ++ graphics_listen_addr_spice='10.0.0.2', ++ serial_listen_addr='127.0.0.1', ++ target_connect_addr='127.0.0.1', ++ bdms=[], ++ block_migration=block_migration) ++ guest = libvirt_guest.Guest(fakelibvirt.virDomain) ++ device_names = ['vda'] ++ ++ mock_get_updated_xml.return_value = mock.sentinel.dest_xml ++ ++ # persistent_xml was introduced in v1.3.4 so provide v1.3.3 ++ v1_3_3 = versionutils.convert_version_to_int((1, 3, 3)) ++ mock_get_version.return_value = v1_3_3 ++ ++ drvr._live_migration_operation( ++ self.context, instance, dest, block_migration, migrate_data, ++ guest, device_names) ++ ++ expected_uri = drvr._live_migration_uri(dest) ++ expected_flags = 0 ++ expected_params = { ++ 'bandwidth': 0, ++ 'destination_xml': mock.sentinel.dest_xml, ++ 'migrate_disks': device_names, ++ 'migrate_uri': 'tcp://127.0.0.1' ++ } ++ ++ # Assert that migrateToURI3 is called without the persistent_xml param ++ mock_get_version.assert_called() ++ mock_migrateToURI3.assert_called_once_with( ++ expected_uri, params=expected_params, flags=expected_flags) ++ ++ # reset mocks and try again with v1.3.4 ++ mock_get_version.reset_mock() ++ mock_migrateToURI3.reset_mock() ++ ++ # persistent_xml was introduced in v1.3.4 so provide it this time ++ v1_3_4 = versionutils.convert_version_to_int((1, 3, 4)) ++ mock_get_version.return_value = v1_3_4 ++ ++ drvr._live_migration_operation( ++ self.context, instance, dest, ++ block_migration, migrate_data, guest, device_names) ++ ++ expected_params = { ++ 'bandwidth': 0, ++ 'destination_xml': mock.sentinel.dest_xml, ++ 'persistent_xml': mock.sentinel.dest_xml, ++ 'migrate_disks': device_names, ++ 'migrate_uri': 'tcp://127.0.0.1' ++ } ++ ++ # Assert that migrateToURI3 is called with the persistent_xml param ++ mock_get_version.assert_called() ++ mock_migrateToURI3.assert_called_once_with( ++ expected_uri, params=expected_params, flags=expected_flags) ++ + def test_live_migration_update_graphics_xml(self): + self.compute = manager.ComputeManager() + instance_dict = dict(self.test_instance) +@@ -9709,19 +9779,21 @@ class LibvirtConnTestCase(test.NoDBTestC + + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3") +- @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml', +- return_value='') ++ @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml') + @mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', + return_value='') + def test_live_migration_uses_migrateToURI3( + self, mock_old_xml, mock_new_xml, mock_migrateToURI3, + mock_min_version): ++ ++ mock_new_xml.return_value = mock.sentinel.new_xml + # Preparing mocks + disk_paths = ['vda', 'vdb'] + params = { + 'migrate_disks': ['vda', 'vdb'], + 'bandwidth': CONF.libvirt.live_migration_bandwidth, +- 'destination_xml': '', ++ 'destination_xml': mock.sentinel.new_xml, ++ 'persistent_xml': mock.sentinel.new_xml, + } + mock_migrateToURI3.side_effect = fakelibvirt.libvirtError("ERR") + +@@ -9776,6 +9848,7 @@ class LibvirtConnTestCase(test.NoDBTestC + 'migrate_disks': device_names, + 'bandwidth': CONF.libvirt.live_migration_bandwidth, + 'destination_xml': '', ++ 'persistent_xml': '', + } + mock_migrateToURI3.assert_called_once_with( + drvr._live_migration_uri('dest'), params=params, +@@ -9804,18 +9877,21 @@ class LibvirtConnTestCase(test.NoDBTestC + + @mock.patch.object(host.Host, 'has_min_version', return_value=True) + @mock.patch.object(fakelibvirt.virDomain, "migrateToURI3") +- @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml', +- return_value='') ++ @mock.patch('nova.virt.libvirt.migration.get_updated_guest_xml') + @mock.patch('nova.virt.libvirt.guest.Guest.get_xml_desc', return_value='') + def test_block_live_migration_tunnelled_migrateToURI3( + self, mock_old_xml, mock_new_xml, + mock_migrateToURI3, mock_min_version): + self.flags(live_migration_tunnelled=True, group='libvirt') ++ ++ mock_new_xml.return_value = mock.sentinel.new_xml ++ + # Preparing mocks + disk_paths = [] + params = { + 'bandwidth': CONF.libvirt.live_migration_bandwidth, +- 'destination_xml': '', ++ 'destination_xml': mock.sentinel.new_xml, ++ 'persistent_xml': mock.sentinel.new_xml, + } + # Start test + migrate_data = objects.LibvirtLiveMigrateData( +--- nova-17.0.13.orig/nova/virt/libvirt/driver.py ++++ nova-17.0.13/nova/virt/libvirt/driver.py +@@ -328,6 +328,11 @@ MIN_LIBVIRT_BETTER_SIGKILL_HANDLING = (4 + + VGPU_RESOURCE_SEMAPHORE = "vgpu_resources" + ++# libvirt >= v1.3.4 introduced VIR_MIGRATE_PARAM_PERSIST_XML that needs to be ++# provided when the VIR_MIGRATE_PERSIST_DEST flag is used to ensure the updated ++# domain XML is persisted on the destination. ++MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML = (1, 3, 4) ++ + + class LibvirtDriver(driver.ComputeDriver): + capabilities = { +@@ -7050,6 +7055,12 @@ class LibvirtDriver(driver.ComputeDriver + libvirt.VIR_MIGRATE_TUNNELLED != 0): + params.pop('migrate_disks') + ++ # NOTE(lyarwood): Only available from v1.3.4 ++ if self._host.has_min_version( ++ MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML ++ ): ++ params['persistent_xml'] = new_xml_str ++ + # TODO(sahid): This should be in + # post_live_migration_at_source but no way to retrieve + # ports acquired on the host for the guest at this diff -Nru nova-17.0.13/debian/patches/CVE-2021-3654-1.patch nova-17.0.13/debian/patches/CVE-2021-3654-1.patch --- nova-17.0.13/debian/patches/CVE-2021-3654-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-17.0.13/debian/patches/CVE-2021-3654-1.patch 2023-02-10 08:50:27.000000000 +0000 @@ -0,0 +1,114 @@ +Backport: + - Remove tests patches + +From 04d48527b62a35d912f93bc75613a6cca606df66 Mon Sep 17 00:00:00 2001 +From: melanie witt +Date: Thu, 13 May 2021 05:43:42 +0000 +Subject: [PATCH] Reject open redirection in the console proxy + +NOTE(melwitt): This is the combination of two commits, the bug fix and +a followup change to the unit test to enable it also run on +Python < 3.6. + +Our console proxies (novnc, serial, spice) run in a websockify server +whose request handler inherits from the python standard +SimpleHTTPRequestHandler. There is a known issue [1] in the +SimpleHTTPRequestHandler which allows open redirects by way of URLs +in the following format: + + http://vncproxy.my.domain.com//example.com/%2F.. + +which if visited, will redirect a user to example.com. + +We can intercept a request and reject requests that pass a redirection +URL beginning with "//" by implementing the +SimpleHTTPRequestHandler.send_head() method containing the +vulnerability to reject such requests with a 400 Bad Request. + +This code is copied from a patch suggested in one of the issue comments +[2]. + +Closes-Bug: #1927677 + +[1] https://bugs.python.org/issue32084 +[2] https://bugs.python.org/issue32084#msg306545 + +Conflicts: + nova/tests/unit/console/test_websocketproxy.py + +NOTE(melwitt): The conflict is because change +I23ac1cc79482d0fabb359486a4b934463854cae5 (Allow TLS ciphers/protocols +to be configurable for console proxies) is not in Train. + +NOTE(melwitt): The difference from the cherry picked change: +HTTPStatus.BAD_REQUEST => 400 is due to the fact that HTTPStatus does +not exist in Python 2.7. + +Reduce mocking in test_reject_open_redirect for compat + +This is a followup for change Ie36401c782f023d1d5f2623732619105dc2cfa24 +to reduce mocking in the unit test coverage for it. + +While backporting the bug fix, it was found to be incompatible with +earlier versions of Python < 3.6 due to a difference in internal +implementation [1]. + +This reduces the mocking in the unit test to be more agnostic to the +internals of the StreamRequestHandler (ancestor of +SimpleHTTPRequestHandler) and work across Python versions >= 2.7. + +Related-Bug: #1927677 + +[1] https://github.com/python/cpython/commit/34eeed42901666fce099947f93dfdfc05411f286 + +Change-Id: I546d376869a992601b443fb95acf1034da2a8f36 +(cherry picked from commit 214cabe6848a1fdb4f5941d994c6cc11107fc4af) +(cherry picked from commit 9c2f29783734cb5f9cb05a08d328c10e1d16c4f1) +(cherry picked from commit 94e265f3ca615aa18de0081a76975019997b8709) +(cherry picked from commit d43b88a33407b1253e7bce70f720a44f7688141f) + +Change-Id: Ie36401c782f023d1d5f2623732619105dc2cfa24 +(cherry picked from commit 781612b33282ed298f742c85dab58a075c8b793e) +(cherry picked from commit 470925614223c8dd9b1233f54f5a96c02b2d4f70) +(cherry picked from commit 6b70350bdcf59a9712f88b6435ba2c6500133e5b) +(cherry picked from commit 719e651e6be277950632e0c2cf5cc9a018344e7b) +--- + +--- nova-17.0.13.orig/nova/console/websocketproxy.py ++++ nova-17.0.13/nova/console/websocketproxy.py +@@ -19,6 +19,7 @@ Leverages websockify.py by Joel Martin + ''' + + import copy ++import os + import socket + import sys + +@@ -248,6 +249,27 @@ class NovaProxyRequestHandler(NovaProxyR + def socket(self, *args, **kwargs): + return websockify.WebSocketServer.socket(*args, **kwargs) + ++ def send_head(self): ++ # This code is copied from this example patch: ++ # https://bugs.python.org/issue32084#msg306545 ++ path = self.translate_path(self.path) ++ if os.path.isdir(path): ++ parts = urlparse.urlsplit(self.path) ++ if not parts.path.endswith('/'): ++ # redirect browser - doing basically what apache does ++ new_parts = (parts[0], parts[1], parts[2] + '/', ++ parts[3], parts[4]) ++ new_url = urlparse.urlunsplit(new_parts) ++ ++ # Browsers interpret "Location: //uri" as an absolute URI ++ # like "http://URI" ++ if new_url.startswith('//'): ++ self.send_error(400, ++ "URI must not start with //") ++ return None ++ ++ return super(NovaProxyRequestHandler, self).send_head() ++ + + class NovaWebSocketProxy(websockify.WebSocketProxy): + def __init__(self, *args, **kwargs): diff -Nru nova-17.0.13/debian/patches/CVE-2021-3654-2.patch nova-17.0.13/debian/patches/CVE-2021-3654-2.patch --- nova-17.0.13/debian/patches/CVE-2021-3654-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-17.0.13/debian/patches/CVE-2021-3654-2.patch 2023-02-10 08:50:35.000000000 +0000 @@ -0,0 +1,59 @@ +Backport: + - Remove tests patches + +From 8906552cfc2525a44251d4cf313ece61e57251eb Mon Sep 17 00:00:00 2001 +From: Sean Mooney +Date: Mon, 23 Aug 2021 15:37:48 +0100 +Subject: [PATCH] address open redirect with 3 forward slashes + +Ie36401c782f023d1d5f2623732619105dc2cfa24 was intended +to address OSSA-2021-002 (CVE-2021-3654) however after its +release it was discovered that the fix only worked +for urls with 2 leading slashes or more then 4. + +This change adresses the missing edgecase for 3 leading slashes +and also maintian support for rejecting 2+. + +Conflicts: + nova/console/websocketproxy.py + nova/tests/unit/console/test_websocketproxy.py + +NOTE(melwitt): The conflict and difference in websocketproxy.py from +the cherry picked change: HTTPStatus.BAD_REQUEST => 400 is due to the +fact that HTTPStatus does not exist in Python 2.7. The conflict in +test_websocketproxy.py is because change +I23ac1cc79482d0fabb359486a4b934463854cae5 (Allow TLS ciphers/protocols +to be configurable for console proxies) is not in Train. The difference +in test_websocketproxy.py from the cherry picked change is due to a +difference in internal implementation [1] in Python < 3.6. See change +I546d376869a992601b443fb95acf1034da2a8f36 for reference. + +[1] https://github.com/python/cpython/commit/34eeed42901666fce099947f93dfdfc05411f286 + +Change-Id: I95f68be76330ff09e5eabb5ef8dd9a18f5547866 +co-authored-by: Matteo Pozza +Closes-Bug: #1927677 +(cherry picked from commit 6fbd0b758dcac71323f3be179b1a9d1c17a4acc5) +(cherry picked from commit 47dad4836a26292e9d34e516e1525ecf00be127c) +(cherry picked from commit 9588cdbfd4649ea53d60303f2d10c5d62a070a07) +(cherry picked from commit 0997043f459ac616b594363b5b253bd0ae6ed9eb) +--- + +--- nova-17.0.13.orig/nova/console/websocketproxy.py ++++ nova-17.0.13/nova/console/websocketproxy.py +@@ -256,14 +256,9 @@ class NovaProxyRequestHandler(NovaProxyR + if os.path.isdir(path): + parts = urlparse.urlsplit(self.path) + if not parts.path.endswith('/'): +- # redirect browser - doing basically what apache does +- new_parts = (parts[0], parts[1], parts[2] + '/', +- parts[3], parts[4]) +- new_url = urlparse.urlunsplit(new_parts) +- + # Browsers interpret "Location: //uri" as an absolute URI + # like "http://URI" +- if new_url.startswith('//'): ++ if self.path.startswith('//'): + self.send_error(400, + "URI must not start with //") + return None diff -Nru nova-17.0.13/debian/patches/CVE-2022-47951.patch nova-17.0.13/debian/patches/CVE-2022-47951.patch --- nova-17.0.13/debian/patches/CVE-2022-47951.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-17.0.13/debian/patches/CVE-2022-47951.patch 2023-02-06 13:21:41.000000000 +0000 @@ -0,0 +1,219 @@ +Backport of: + +From 3fe8880d3759cbd7b19d75dcf235dfd5c511be13 Mon Sep 17 00:00:00 2001 +From: Dan Smith +Date: Thu, 10 Nov 2022 09:55:48 -0800 +Subject: [PATCH] [stable-only][cve] Check VMDK create-type against an allowed + list + +NOTE(sbauza): Stable policy allows us to proactively merge a backport without waiting for the parent patch to be merged (exception to rule #4 in [1]. Marking [stable-only] in order to silence nova-tox-validate-backport + +[1] https://docs.openstack.org/project-team-guide/stable-branches.html#appropriate-fixes + +Conflicts vs victoria in: + nova/conf/compute.py + +Related-Bug: #1996188 +Change-Id: I5a399f1d3d702bfb76c067893e9c924904c8c360 +--- + nova/conf/compute.py | 9 ++++++ + nova/tests/unit/virt/test_images.py | 46 +++++++++++++++++++++++++++++ + nova/virt/images.py | 31 +++++++++++++++++++ + 3 files changed, 86 insertions(+) + +--- a/nova/conf/compute.py ++++ b/nova/conf/compute.py +@@ -717,6 +717,15 @@ Related options: + failing if ``vif_plugging_is_fatal`` is True, or simply continuing with the + live migration + """), ++ cfg.ListOpt('vmdk_allowed_types', ++ default=['streamOptimized', 'monolithicSparse'], ++ help=""" ++A list of strings describing allowed VMDK "create-type" subformats ++that will be allowed. This is recommended to only include ++single-file-with-sparse-header variants to avoid potential host file ++exposure due to processing named extents. If this list is empty, then no ++form of VMDK image will be allowed. ++"""), + ] + + interval_opts = [ +--- a/nova/tests/unit/virt/test_images.py ++++ b/nova/tests/unit/virt/test_images.py +@@ -16,6 +16,8 @@ import os + + import mock + from oslo_concurrency import processutils ++from oslo_serialization import jsonutils ++from oslo_utils import imageutils + import six + + from nova import exception +@@ -120,3 +122,54 @@ class QemuTestCase(test.NoDBTestCase): + expected = ('qemu-img', 'convert', '-t', 'writethrough', + '-O', 'out_format', '-f', 'in_format', 'source', 'dest') + self.assertTupleEqual(expected, mock_execute.call_args[0]) ++ ++ def test_convert_image_vmdk_allowed_list_checking(self): ++ info = {'format': 'vmdk', ++ 'format-specific': { ++ 'type': 'vmdk', ++ 'data': { ++ 'create-type': 'monolithicFlat', ++ }}} ++ ++ data = imageutils.QemuImgInfo(jsonutils.dumps(info), format='json') ++ # When executed in a Python 2.7 environment, the QemuImgInfo object ++ # does not have a 'format_specific' attribute, so we add it manually. ++ # (This is handled in the nova code by images.qemu_img_info().) ++ if not hasattr(data, 'format_specific'): ++ data.format_specific = info.get('format-specific') ++ ++ # If the format is not in the allowed list, we should get an error ++ self.assertRaises(exception.ImageUnacceptable, ++ images.check_vmdk_image, 'foo', data) ++ ++ # With the format in the allowed list, no error ++ self.flags(vmdk_allowed_types=['streamOptimized', 'monolithicFlat', ++ 'monolithicSparse'], ++ group='compute') ++ images.check_vmdk_image('foo', data) ++ ++ # With an empty list, allow nothing ++ self.flags(vmdk_allowed_types=[], group='compute') ++ self.assertRaises(exception.ImageUnacceptable, ++ images.check_vmdk_image, 'foo', data) ++ ++ @mock.patch.object(images, 'fetch') ++ @mock.patch.object(images, 'qemu_img_info') ++ def test_fetch_checks_vmdk_rules(self, mock_info, mock_fetch): ++ info = {'format': 'vmdk', ++ 'format-specific': { ++ 'type': 'vmdk', ++ 'data': { ++ 'create-type': 'monolithicFlat', ++ }}} ++ data = imageutils.QemuImgInfo(jsonutils.dumps(info), format='json') ++ # When executed in a Python 2.7 environment, the QemuImgInfo object ++ # does not have a 'format_specific' attribute, so we add it manually. ++ # (This is handled in the nova code by images.qemu_img_info().) ++ if not hasattr(data, 'format_specific'): ++ data.format_specific = info.get('format-specific') ++ mock_info.return_value = data ++ with mock.patch('os.path.exists', return_value=True): ++ e = self.assertRaises(exception.ImageUnacceptable, ++ images.fetch_to_raw, None, 'foo', 'anypath') ++ self.assertIn('Invalid VMDK create-type specified', str(e)) +--- a/nova/virt/images.py ++++ b/nova/virt/images.py +@@ -21,6 +21,7 @@ Handling of VM disk images. + + import operator + import os ++import json + + from oslo_concurrency import processutils + from oslo_log import log as logging +@@ -49,7 +50,7 @@ QEMU_VERSION = None + QEMU_VERSION_REQ_SHARED = 2010000 + + +-def qemu_img_info(path, format=None): ++def qemu_img_info(path, format=None, qemu_ofmt=None): + """Return an object containing the parsed output from qemu-img info.""" + # TODO(mikal): this code should not be referring to a libvirt specific + # flag. +@@ -64,6 +65,8 @@ def qemu_img_info(path, format=None): + path = os.path.join(path, "root.hds") + + cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path) ++ if qemu_ofmt == 'json': ++ cmd = cmd + ('--output=json',) + if format is not None: + cmd = cmd + ('-f', format) + # Check to see if the qemu version is >= 2.10 because if so, we need +@@ -91,7 +94,19 @@ def qemu_img_info(path, format=None): + {'path': path, 'error': err}) + raise exception.InvalidDiskInfo(reason=msg) + +- return imageutils.QemuImgInfo(out) ++ # The fix for bug #1996188 requires using the format_specific ++ # attribute of QemuImgInfo introduced in oslo.utils 4.1.0. ++ # Since Nova's requirements.txt in this branch specifies ++ # a lower version, we may need to handle this manually. ++ if qemu_ofmt == 'json': ++ info = imageutils.QemuImgInfo(out, format='json') ++ if not hasattr(info, 'format_specific'): ++ qemu_info = json.loads(out) ++ info.format_specific = qemu_info.get('format-specific') ++ else: ++ info = imageutils.QemuImgInfo(out) ++ ++ return info + + + def convert_image(source, dest, in_format, out_format, run_as_root=False): +@@ -155,12 +170,40 @@ def get_info(context, image_href): + return IMAGE_API.get(context, image_href) + + ++def check_vmdk_image(image_id, data): ++ # Check some rules about VMDK files. Specifically we want to make ++ # sure that the "create-type" of the image is one that we allow. ++ # Some types of VMDK files can reference files outside the disk ++ # image and we do not want to allow those for obvious reasons. ++ ++ types = CONF.compute.vmdk_allowed_types ++ ++ if not len(types): ++ LOG.warning('Refusing to allow VMDK image as vmdk_allowed_' ++ 'types is empty') ++ msg = _('Invalid VMDK create-type specified') ++ raise exception.ImageUnacceptable(image_id=image_id, reason=msg) ++ ++ try: ++ create_type = data.format_specific['data']['create-type'] ++ except KeyError: ++ msg = _('Unable to determine VMDK create-type') ++ raise exception.ImageUnacceptable(image_id=image_id, reason=msg) ++ ++ if create_type not in CONF.compute.vmdk_allowed_types: ++ LOG.warning('Refusing to process VMDK file with create-type of %r ' ++ 'which is not in allowed set of: %s', create_type, ++ ','.join(CONF.compute.vmdk_allowed_types)) ++ msg = _('Invalid VMDK create-type specified') ++ raise exception.ImageUnacceptable(image_id=image_id, reason=msg) ++ ++ + def fetch_to_raw(context, image_href, path): + path_tmp = "%s.part" % path + fetch(context, image_href, path_tmp) + + with fileutils.remove_path_on_error(path_tmp): +- data = qemu_img_info(path_tmp) ++ data = qemu_img_info(path_tmp, qemu_ofmt='json') + + fmt = data.file_format + if fmt is None: +@@ -174,6 +217,9 @@ def fetch_to_raw(context, image_href, pa + reason=(_("fmt=%(fmt)s backed by: %(backing_file)s") % + {'fmt': fmt, 'backing_file': backing_file})) + ++ if fmt == 'vmdk': ++ check_vmdk_image(image_href, data) ++ + if fmt != "raw" and CONF.force_raw_images: + staged = "%s.converted" % path + LOG.debug("%s was %s, converting to raw", image_href, fmt) +--- a/nova/tests/unit/virt/libvirt/test_utils.py ++++ b/nova/tests/unit/virt/libvirt/test_utils.py +@@ -644,7 +644,7 @@ disk size: 4.4M + def fake_rm_on_error(path, remove=None): + self.executes.append(('rm', '-f', path)) + +- def fake_qemu_img_info(path): ++ def fake_qemu_img_info(path, format=None, qemu_ofmt=None): + class FakeImgInfo(object): + pass + diff -Nru nova-17.0.13/debian/patches/objects-Update-keypairs-when-saving-an-instance.patch nova-17.0.13/debian/patches/objects-Update-keypairs-when-saving-an-instance.patch --- nova-17.0.13/debian/patches/objects-Update-keypairs-when-saving-an-instance.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-17.0.13/debian/patches/objects-Update-keypairs-when-saving-an-instance.patch 2023-04-26 20:21:53.000000000 +0000 @@ -0,0 +1,196 @@ +From 18ec0d7d3bafa8b9270ea63da4bbece1b1380a80 Mon Sep 17 00:00:00 2001 +From: Takashi NATSUME +Date: Thu, 19 Sep 2019 15:57:44 +0900 +Subject: [PATCH] objects: Update keypairs when saving an instance + +The keypair of a server is updated when rebuilding the server with a +keypair. This function has been added since API microversion 2.54. +However the 'keypairs' of the instance object is not saved when saving +the instance object currently. + +Make the instance object update the 'keypairs' field when saving the +instance object. + +Change-Id: I8a2726b39d0444de8c35480024078a97430f5d0c +Closes-Bug: #1843708 +Co-authored-by: Stephen Finucane +(cherry picked from commit 086796021b189c3ac64805ed8f6bde833906d284) +(cherry picked from commit aed86ee5d6289edf1baf9fe0b2a9e509031fdd25) +(cherry picked from commit b971dc82cb524fe86284c95ec671e2bad1c2874f) +(cherry picked from commit 0bc5a4ecb524a73aacb5d0dd2887799885bdbb14) +(cherry picked from commit aa7a6939d5177c0dd8c9f5a7bf7975264d2f5a2a) +(cherry picked from commit 6a7a78a44ea19497ea3e331ddd672f95cd49c50e) +Signed-off-by: zhhuabj +--- + nova/objects/instance.py | 5 +- + .../regressions/test_bug_1843708.py | 92 +++++++++++++++++++ + nova/tests/unit/fake_instance.py | 3 +- + nova/tests/unit/objects/test_instance.py | 24 ++++- + 4 files changed, 116 insertions(+), 8 deletions(-) + create mode 100644 nova/tests/functional/regressions/test_bug_1843708.py + +--- nova-17.0.13.orig/nova/objects/instance.py ++++ nova-17.0.13/nova/objects/instance.py +@@ -695,8 +695,9 @@ class Instance(base.NovaPersistentObject + pass + + def _save_keypairs(self, context): +- # NOTE(danms): Read-only so no need to save this. +- pass ++ if 'keypairs' in self.obj_what_changed(): ++ self._save_extra_generic('keypairs') ++ self.obj_reset_changes(['keypairs'], recursive=True) + + def _save_extra_generic(self, field): + if field in self.obj_what_changed(): +--- /dev/null ++++ nova-17.0.13/nova/tests/functional/regressions/test_bug_1843708.py +@@ -0,0 +1,92 @@ ++# Copyright 2019 NTT Corporation ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); you may ++# not use this file except in compliance with the License. You may obtain ++# a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++# License for the specific language governing permissions and limitations ++# under the License. ++ ++from nova import context ++from nova import objects ++from nova import test ++from nova.tests import fixtures as nova_fixtures ++from nova.tests.functional import integrated_helpers ++from nova.tests.unit import fake_notifier ++from nova.tests.unit.image import fake as fake_image ++ ++ ++class RebuildWithKeypairTestCase( ++ test.TestCase, integrated_helpers.InstanceHelperMixin, ++): ++ """Regression test for bug 1843708. ++ ++ This tests a rebuild scenario with new key pairs. ++ """ ++ ++ def setUp(self): ++ super(RebuildWithKeypairTestCase, self).setUp() ++ # Start standard fixtures. ++ self.useFixture(nova_fixtures.PlacementFixture()) ++ self.useFixture(nova_fixtures.NeutronFixture(self)) ++ fake_image.stub_out_image_service(self) ++ self.addCleanup(fake_image.FakeImageService_reset) ++ fake_notifier.stub_notifier(self) ++ self.addCleanup(fake_notifier.reset) ++ self.api = self.useFixture(nova_fixtures.OSAPIFixture( ++ api_version='v2.1')).admin_api ++ self.api.microversion = 'latest' ++ # Start nova services. ++ self.start_service('conductor') ++ self.start_service('scheduler') ++ self.start_service('compute') ++ ++ def test_rebuild_with_keypair(self): ++ keypair_req = { ++ 'keypair': { ++ 'name': 'test-key1', ++ 'type': 'ssh', ++ }, ++ } ++ keypair1 = self.api.post_keypair(keypair_req) ++ keypair_req['keypair']['name'] = 'test-key2' ++ keypair2 = self.api.post_keypair(keypair_req) ++ ++ server = self._build_minimal_create_server_request( ++ self.api, 'test-rebuild-with-keypair', ++ image_uuid=fake_image.get_valid_image_id(), ++ networks='none') ++ server.update({'key_name': 'test-key1'}) ++ ++ # Create a server with keypair 'test-key1' ++ server = self.api.post_server({'server': server}) ++ self._wait_for_state_change(self.api, server, 'ACTIVE') ++ ++ # Check keypairs ++ ctxt = context.get_admin_context() ++ instance = objects.Instance.get_by_uuid( ++ ctxt, server['id'], expected_attrs=['keypairs']) ++ self.assertEqual( ++ keypair1['public_key'], instance.keypairs[0].public_key) ++ ++ # Rebuild a server with keypair 'test-key2' ++ body = { ++ 'rebuild': { ++ 'imageRef': fake_image.get_valid_image_id(), ++ 'key_name': 'test-key2', ++ }, ++ } ++ self.api.api_post('servers/%s/action' % server['id'], body) ++ fake_notifier.wait_for_versioned_notifications('instance.rebuild.end') ++ self._wait_for_state_change(self.api, server, 'ACTIVE') ++ ++ # Check keypairs changed ++ instance = objects.Instance.get_by_uuid( ++ ctxt, server['id'], expected_attrs=['keypairs']) ++ self.assertEqual( ++ keypair2['public_key'], instance.keypairs[0].public_key) +--- nova-17.0.13.orig/nova/tests/unit/fake_instance.py ++++ nova-17.0.13/nova/tests/unit/fake_instance.py +@@ -117,7 +117,6 @@ def fake_instance_obj(context, obj_insta + is_public=True, + extra_specs={}, + projects=[]) +- flavor.obj_reset_changes() + inst = obj_instance_class._from_db_object(context, + obj_instance_class(), fake_db_instance(**updates), + expected_attrs=expected_attrs) +@@ -133,7 +132,7 @@ def fake_instance_obj(context, obj_insta + inst.memory_mb = flavor.memory_mb + inst.old_flavor = None + inst.new_flavor = None +- inst.obj_reset_changes() ++ inst.obj_reset_changes(recursive=True) + return inst + + +--- nova-17.0.13.orig/nova/tests/unit/objects/test_instance.py ++++ nova-17.0.13/nova/tests/unit/objects/test_instance.py +@@ -683,14 +683,30 @@ class _TestInstanceObject(object): + inst.numa_topology = None + inst.migration_context = None + inst.vcpu_model = test_vcpu_model.fake_vcpumodel +- inst.save() ++ inst.keypairs = objects.KeyPairList( ++ objects=[objects.KeyPair(name='foo')]) ++ + json_vcpu_model = jsonutils.dumps( + test_vcpu_model.fake_vcpumodel.obj_to_primitive()) +- expected_vals = {'numa_topology': None, +- 'migration_context': None, +- 'vcpu_model': json_vcpu_model} ++ json_keypairs = jsonutils.dumps(inst.keypairs.obj_to_primitive()) ++ ++ # Check changed fields in the instance object ++ self.assertIn('keypairs', inst.obj_what_changed()) ++ self.assertEqual({'objects'}, inst.keypairs.obj_what_changed()) ++ ++ inst.save() ++ ++ expected_vals = { ++ 'numa_topology': None, ++ 'migration_context': None, ++ 'vcpu_model': json_vcpu_model, ++ 'keypairs': json_keypairs, ++ } + mock_update.assert_called_once_with(self.context, inst.uuid, + expected_vals) ++ # Verify that the record of changed fields has been cleared ++ self.assertNotIn('keypairs', inst.obj_what_changed()) ++ self.assertEqual(set(), inst.keypairs.obj_what_changed()) + + @mock.patch.object(notifications, 'send_update') + @mock.patch.object(cells_rpcapi.CellsAPI, 'instance_update_from_api') diff -Nru nova-17.0.13/debian/patches/series nova-17.0.13/debian/patches/series --- nova-17.0.13/debian/patches/series 2022-06-23 06:41:00.000000000 +0000 +++ nova-17.0.13/debian/patches/series 2023-04-26 20:21:53.000000000 +0000 @@ -11,3 +11,9 @@ 0002-lp1852610_Add_functional_recreate_test_for_bug_1852610.patch 0003-lp1852610_Add_functional_recreate_revert_resize_test_for_bug_1852610.patch 0004-lp1852610_api_allows_source_compute_service.patch +CVE-2022-47951.patch +CVE-2015-9543.patch +CVE-2020-17376.patch +CVE-2021-3654-1.patch +CVE-2021-3654-2.patch +objects-Update-keypairs-when-saving-an-instance.patch