From 86a1acc9e3f50729f3d95e8979e9ed133e1c24f3 Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Fri, 27 Jun 2025 09:41:06 -0700 Subject: [PATCH] crypto: Fix traceback on non-utf8, non-swift paths fetch_crypto_keys can fail like get_keys(): from callback: 'utf-8' codec can't encode character '\udcc0' in position 1: surrogates not allowed: Traceback (most recent call last): File ".../swift/common/middleware/crypto/crypto_utils.py", line 166, in get_keys keys = fetch_crypto_keys(key_id=key_id) File ".../swift/common/middleware/crypto/keymaster.py", line 148, in fetch_crypto_keys keys['container'] = self.keymaster.create_key( File ".../swift/common/middleware/crypto/keymaster.py", line 322, in create_key path = path.encode('utf-8') UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc0' in position 1: surrogates not allowed This doesn't fix *all* non-utf8 paths, but - it was easy enough to avoid the non-swift ones, which have been seen in prod, and - there's ample precedent in other middlewares for checking API version. Signed-off-by: Tim Burke Change-Id: I8c342c4751ba3ca682efd152e90e396e9f8eb851 --- swift/common/middleware/crypto/decrypter.py | 5 +++++ .../common/middleware/crypto/test_decrypter.py | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/swift/common/middleware/crypto/decrypter.py b/swift/common/middleware/crypto/decrypter.py index 3ede9b2ebc..3431e4efc6 100644 --- a/swift/common/middleware/crypto/decrypter.py +++ b/swift/common/middleware/crypto/decrypter.py @@ -16,6 +16,7 @@ import base64 import json +from swift.common.constraints import valid_api_version from swift.common.header_key_dict import HeaderKeyDict from swift.common.http import is_success from swift.common.middleware.crypto.crypto_utils import CryptoWSGIContext, \ @@ -454,8 +455,12 @@ class Decrypter(object): is_cont_or_obj_req = True except ValueError: is_cont_or_obj_req = False + if not is_cont_or_obj_req: return self.app(env, start_response) + if not valid_api_version(parts[0]): + # Not a swift request + return self.app(env, start_response) if parts[3] and req.method in ('GET', 'HEAD'): handler = DecrypterObjContext(self, self.logger).handle diff --git a/test/unit/common/middleware/crypto/test_decrypter.py b/test/unit/common/middleware/crypto/test_decrypter.py index 4e88956109..7f95fa9db6 100644 --- a/test/unit/common/middleware/crypto/test_decrypter.py +++ b/test/unit/common/middleware/crypto/test_decrypter.py @@ -22,7 +22,7 @@ from unittest import mock from swift.common.request_helpers import is_object_transient_sysmeta from swift.common.utils import MD5_OF_EMPTY_STRING from swift.common.header_key_dict import HeaderKeyDict -from swift.common.middleware.crypto import decrypter +from swift.common.middleware.crypto import decrypter, keymaster from swift.common.middleware.crypto.crypto_utils import CRYPTO_KEY_CALLBACK, \ dump_crypto_meta, Crypto, load_crypto_meta from swift.common.swob import Request, HTTPException, HTTPOk, \ @@ -1211,6 +1211,18 @@ class TestDecrypter(unittest.TestCase): req.get_response(app) self.assertEqual(FakeAppThatExcepts.MESSAGE, catcher.exception.body) + def test_non_swift_path(self): + path = '/\xC0.\xC0./\xC0.\xC0./\xC0.\xC0./\xC0.\xC0./winnt/win.ini' + fake_swift = FakeSwift() + fake_swift.register('GET', path, HTTPNotFound, {}) + app = keymaster.KeyMaster(decrypter.Decrypter(fake_swift, {}), { + 'encryption_root_secret': 'A' * 80, + }) + app.app.logger = debug_logger() + req = Request.blank(path) + resp = req.get_response(app) + self.assertEqual(resp.status_int, 404) + if __name__ == '__main__': unittest.main()