| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,6 +18,7 @@ | |||
| 18 | 18 | #define PY_SSIZE_T_CLEAN | |
| 19 | 19 | #include "Python.h" | |
| 20 | 20 | ||
| 21 | + #include <limits.h> | ||
| 21 | 22 | #include <stdlib.h> | |
| 22 | 23 | #include <string.h> | |
| 23 | 24 | ||
@@ -105,18 +106,25 @@ static int buffer_grow(buffer_t buffer, int min_length) { | |||
| 105 | 106 | * Return non-zero and sets MemoryError on allocation failure. | |
| 106 | 107 | * Return non-zero and sets ValueError if `size` would exceed 2GiB. */ | |
| 107 | 108 | static int buffer_assure_space(buffer_t buffer, int size) { | |
| 108 | - int new_size = buffer->position + size; | ||
| 109 | - /* Check for overflow. */ | ||
| 110 | - if (new_size < buffer->position) { | ||
| 109 | + long long new_size; | ||
| 110 | + if (size < 0) { | ||
| 111 | 111 | PyErr_SetString(PyExc_ValueError, | |
| 112 | 112 | "Document would overflow BSON size limit"); | |
| 113 | 113 | return 1; | |
| 114 | 114 | } | |
| 115 | 115 | ||
| 116 | - if (new_size <= buffer->size) { | ||
| 116 | + /* Compute in a wider type so the addition cannot overflow `int`. */ | ||
| 117 | + new_size = (long long)buffer->position + (long long)size; | ||
| 118 | + if (new_size > INT_MAX) { | ||
| 119 | + PyErr_SetString(PyExc_ValueError, | ||
| 120 | + "Document would overflow BSON size limit"); | ||
| 121 | + return 1; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + if ((int)new_size <= buffer->size) { | ||
| 117 | 125 | return 0; | |
| 118 | 126 | } | |
| 119 | - return buffer_grow(buffer, new_size); | ||
| 127 | + return buffer_grow(buffer, (int)new_size); | ||
| 120 | 128 | } | |
| 121 | 129 | ||
| 122 | 130 | /* Save `size` bytes from the current position in `buffer` (and grow if needed). | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,24 @@ | |||
| 1 | 1 | Changelog | |
| 2 | 2 | ========= | |
| 3 | 3 | ||
| 4 | + Changes in Version 4.18.2 (2026/09/24) | ||
| 5 | + -------------------------------------- | ||
| 6 | + | ||
| 7 | + Version 4.18.2 is a bug fix release. | ||
| 8 | + | ||
| 9 | + - Hardened the bson buffer size guard against signed integer overflow. | ||
| 10 | + - Fixed connection string parsing to percent-decode each host individually. | ||
| 11 | + - Client-side field level encryption now rejects a KMS endpoint ending in | ||
| 12 | + ``.sock``. | ||
| 13 | + | ||
| 14 | + Issues Resolved | ||
| 15 | + ............... | ||
| 16 | + | ||
| 17 | + See the `PyMongo 4.18.2 release notes in JIRA`_ for the list of resolved issues | ||
| 18 | + in this release. | ||
| 19 | + | ||
| 20 | + .. _PyMongo 4.18.2 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=52896 | ||
| 21 | + | ||
| 4 | 22 | Changes in Version 4.18.1 (2026/09/10) | |
| 5 | 23 | -------------------------------------- | |
| 6 | 24 | ||
@@ -9,7 +27,7 @@ Version 4.18.1 is a bug fix release. | |||
| 9 | 27 | - Use an exact match for the file ID in GridFS delete methods | |
| 10 | 28 | (`CVE-2026-88029`_). | |
| 11 | 29 | ||
| 12 | - .. CVE-2026-88029: https://www.cve.org/CVERecord?id=CVE-2026-88029 | ||
| 30 | + .. _CVE-2026-88029: https://www.cve.org/CVERecord?id=CVE-2026-88029 | ||
| 13 | 31 | ||
| 14 | 32 | Changes in Version 4.18.0 (2026/09/03) | |
| 15 | 33 | -------------------------------------- | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,7 +19,7 @@ | |||
| 19 | 19 | import re | |
| 20 | 20 | from typing import Union | |
| 21 | 21 | ||
| 22 | - __version__ = "4.18.2.dev0" | ||
| 22 | + __version__ = "4.18.2" | ||
| 23 | 23 | ||
| 24 | 24 | ||
| 25 | 25 | def get_version_tuple(version: str) -> tuple[Union[int, str], ...]: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -187,6 +187,8 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None: | |||
| 187 | 187 | ssl_context=ctx, | |
| 188 | 188 | ) | |
| 189 | 189 | address = parse_host(endpoint, _HTTPS_PORT) | |
| 190 | + if address[0].endswith(".sock"): | ||
| 191 | + raise ConfigurationError(f"Invalid KMS endpoint {endpoint!r}") | ||
| 190 | 192 | sleep_u = kms_context.usleep | |
| 191 | 193 | if sleep_u: | |
| 192 | 194 | sleep_sec = float(sleep_u) / 1e6 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,7 +18,6 @@ | |||
| 18 | 18 | from __future__ import annotations | |
| 19 | 19 | ||
| 20 | 20 | from typing import Any, Optional | |
| 21 | - from urllib.parse import unquote_plus | ||
| 22 | 21 | ||
| 23 | 22 | from pymongo.asynchronous.srv_resolver import _SrvResolver | |
| 24 | 23 | from pymongo.common import SRV_SERVICE_NAME, _CaseInsensitiveDictionary | |
@@ -159,7 +158,6 @@ async def _parse_srv( | |||
| 159 | 158 | else: | |
| 160 | 159 | hosts = host_part | |
| 161 | 160 | ||
| 162 | - hosts = unquote_plus(hosts) | ||
| 163 | 161 | srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts") | |
| 164 | 162 | srv_allowed_hosts_suffix = srv_allowed_hosts_suffix or options.get("srvAllowedHostsSuffix") | |
| 165 | 163 | if is_srv: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -186,6 +186,8 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None: | |||
| 186 | 186 | ssl_context=ctx, | |
| 187 | 187 | ) | |
| 188 | 188 | address = parse_host(endpoint, _HTTPS_PORT) | |
| 189 | + if address[0].endswith(".sock"): | ||
| 190 | + raise ConfigurationError(f"Invalid KMS endpoint {endpoint!r}") | ||
| 189 | 191 | sleep_u = kms_context.usleep | |
| 190 | 192 | if sleep_u: | |
| 191 | 193 | sleep_sec = float(sleep_u) / 1e6 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,7 +18,6 @@ | |||
| 18 | 18 | from __future__ import annotations | |
| 19 | 19 | ||
| 20 | 20 | from typing import Any, Optional | |
| 21 | - from urllib.parse import unquote_plus | ||
| 22 | 21 | ||
| 23 | 22 | from pymongo.common import SRV_SERVICE_NAME, _CaseInsensitiveDictionary | |
| 24 | 23 | from pymongo.errors import ConfigurationError, InvalidURI | |
@@ -159,7 +158,6 @@ def _parse_srv( | |||
| 159 | 158 | else: | |
| 160 | 159 | hosts = host_part | |
| 161 | 160 | ||
| 162 | - hosts = unquote_plus(hosts) | ||
| 163 | 161 | srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts") | |
| 164 | 162 | srv_allowed_hosts_suffix = srv_allowed_hosts_suffix or options.get("srvAllowedHostsSuffix") | |
| 165 | 163 | if is_srv: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -450,10 +450,23 @@ def split_hosts(hosts: str, default_port: Optional[int] = DEFAULT_PORT) -> list[ | |||
| 450 | 450 | if not entity: | |
| 451 | 451 | raise ConfigurationError("Empty host (or extra comma in host list)") | |
| 452 | 452 | port = default_port | |
| 453 | - # Unix socket entities don't have ports | ||
| 453 | + node = entity | ||
| 454 | + # Decoding happens per entity, after splitting on ",". | ||
| 454 | 455 | if entity.endswith(".sock"): | |
| 456 | + # Unix socket entities don't have ports. Socket paths are the | ||
| 457 | + # only host identifiers permitted to contain reserved | ||
| 458 | + # characters (e.g. "/") that require escaping. | ||
| 459 | + node = unquote_plus(entity) | ||
| 455 | 460 | port = None | |
| 456 | - nodes.append(parse_host(entity, port)) | ||
| 461 | + elif entity.startswith("["): | ||
| 462 | + # An IPv6 zone index is escaped as "%25" (RFC 6874). | ||
| 463 | + node = entity.replace("%25", "%") | ||
| 464 | + elif "%" in entity: | ||
| 465 | + raise InvalidURI( | ||
| 466 | + "Percent-encoding is only allowed in Unix domain socket paths " | ||
| 467 | + f"and IPv6 zone indexes, not in hostnames: {entity}" | ||
| 468 | + ) | ||
| 469 | + nodes.append(parse_host(node, port)) | ||
| 457 | 470 | return nodes | |
| 458 | 471 | ||
| 459 | 472 | ||
@@ -576,7 +589,6 @@ def _validate_uri( | |||
| 576 | 589 | if "/" in hosts: | |
| 577 | 590 | raise InvalidURI(f"Any '/' in a unix domain socket must be percent-encoded: {host_part}") | |
| 578 | 591 | ||
| 579 | - hosts = unquote_plus(hosts) | ||
| 580 | 592 | fqdn = None | |
| 581 | 593 | srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts") | |
| 582 | 594 | if is_srv: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1299,6 +1299,14 @@ async def test_04_kmip_endpoint_invalid_port(self): | |||
| 1299 | 1299 | with self.assertRaisesRegex(EncryptionError, "localhost:12345"): | |
| 1300 | 1300 | await self.client_encryption.create_data_key("kmip", master_key=master_key) | |
| 1301 | 1301 | ||
| 1302 | + async def test_kmip_endpoint_unix_socket_rejected(self): | ||
| 1303 | + # PYTHON-5990: a masterKey.endpoint ending in ".sock" must not be | ||
| 1304 | + # treated as a Unix domain socket path. KMS endpoints must be a TCP | ||
| 1305 | + # host[:port]. | ||
| 1306 | + master_key = {"keyId": "1", "endpoint": "example.sock"} | ||
| 1307 | + with self.assertRaisesRegex(EncryptionError, "Invalid KMS endpoint"): | ||
| 1308 | + await self.client_encryption.create_data_key("kmip", master_key=master_key) | ||
| 1309 | + | ||
| 1302 | 1310 | @unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set") | |
| 1303 | 1311 | async def test_05_aws_endpoint_wrong_region(self): | |
| 1304 | 1312 | master_key = { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -691,6 +691,14 @@ def test_overflow(self): | |||
| 691 | 691 | self.assertTrue(encode({"x": -9223372036854775808})) | |
| 692 | 692 | self.assertRaises(OverflowError, encode, {"x": -9223372036854775809}) | |
| 693 | 693 | ||
| 694 | + @unittest.skipUnless(bson.has_c(), "This test requires the C extension") | ||
| 695 | + def test_encode_size_limit(self): | ||
| 696 | + # PYTHON-5996: encoding must raise when a document's encoded size | ||
| 697 | + # exceeds the BSON size limit. | ||
| 698 | + big_value = "a" * (1 << 30) | ||
| 699 | + with self.assertRaises(ValueError): | ||
| 700 | + encode({"a": big_value, "b": big_value, "c": big_value}) | ||
| 701 | + | ||
| 694 | 702 | def test_small_long_encode_decode(self): | |
| 695 | 703 | encoded1 = encode({"x": 256}) | |
| 696 | 704 | decoded1 = decode(encoded1)["x"] | |
| Back | FazBrowse Home | New Git URL |
0 commit comments