Visitar URL original
Prep 4.18.2 release (#3066) · mongodb/mongo-python-driver@640dd23 · GitHub
Skip to content

Commit 640dd23

Browse files
NoahStappaclark4lifeblink1073
authored
Prep 4.18.2 release (#3066)
Co-authored-by: Jeffrey 'Alex' Clark <alex.clark@mongodb.com> Co-authored-by: Steven Silvester <steve.silvester@mongodb.com>
1 parent 6a0ec65 commit 640dd23

12 files changed

Lines changed: 91 additions & 14 deletions

File tree

‎bson/buffer.c‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define PY_SSIZE_T_CLEAN
1919
#include "Python.h"
2020

21+
#include <limits.h>
2122
#include <stdlib.h>
2223
#include <string.h>
2324

@@ -105,18 +106,25 @@ static int buffer_grow(buffer_t buffer, int min_length) {
105106
* Return non-zero and sets MemoryError on allocation failure.
106107
* Return non-zero and sets ValueError if `size` would exceed 2GiB. */
107108
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) {
111111
PyErr_SetString(PyExc_ValueError,
112112
"Document would overflow BSON size limit");
113113
return 1;
114114
}
115115

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) {
117125
return 0;
118126
}
119-
return buffer_grow(buffer, new_size);
127+
return buffer_grow(buffer, (int)new_size);
120128
}
121129

122130
/* Save `size` bytes from the current position in `buffer` (and grow if needed).

‎doc/changelog.rst‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
Changelog
22
=========
33

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+
422
Changes in Version 4.18.1 (2026/09/10)
523
--------------------------------------
624

@@ -9,7 +27,7 @@ Version 4.18.1 is a bug fix release.
927
- Use an exact match for the file ID in GridFS delete methods
1028
(`CVE-2026-88029`_).
1129

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
1331

1432
Changes in Version 4.18.0 (2026/09/03)
1533
--------------------------------------

‎pymongo/_version.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import re
2020
from typing import Union
2121

22-
__version__ = "4.18.2.dev0"
22+
__version__ = "4.18.2"
2323

2424

2525
def get_version_tuple(version: str) -> tuple[Union[int, str], ...]:

‎pymongo/asynchronous/encryption.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
187187
ssl_context=ctx,
188188
)
189189
address = parse_host(endpoint, _HTTPS_PORT)
190+
if address[0].endswith(".sock"):
191+
raise ConfigurationError(f"Invalid KMS endpoint {endpoint!r}")
190192
sleep_u = kms_context.usleep
191193
if sleep_u:
192194
sleep_sec = float(sleep_u) / 1e6

‎pymongo/asynchronous/uri_parser.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from __future__ import annotations
1919

2020
from typing import Any, Optional
21-
from urllib.parse import unquote_plus
2221

2322
from pymongo.asynchronous.srv_resolver import _SrvResolver
2423
from pymongo.common import SRV_SERVICE_NAME, _CaseInsensitiveDictionary
@@ -159,7 +158,6 @@ async def _parse_srv(
159158
else:
160159
hosts = host_part
161160

162-
hosts = unquote_plus(hosts)
163161
srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts")
164162
srv_allowed_hosts_suffix = srv_allowed_hosts_suffix or options.get("srvAllowedHostsSuffix")
165163
if is_srv:

‎pymongo/synchronous/encryption.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
186186
ssl_context=ctx,
187187
)
188188
address = parse_host(endpoint, _HTTPS_PORT)
189+
if address[0].endswith(".sock"):
190+
raise ConfigurationError(f"Invalid KMS endpoint {endpoint!r}")
189191
sleep_u = kms_context.usleep
190192
if sleep_u:
191193
sleep_sec = float(sleep_u) / 1e6

‎pymongo/synchronous/uri_parser.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from __future__ import annotations
1919

2020
from typing import Any, Optional
21-
from urllib.parse import unquote_plus
2221

2322
from pymongo.common import SRV_SERVICE_NAME, _CaseInsensitiveDictionary
2423
from pymongo.errors import ConfigurationError, InvalidURI
@@ -159,7 +158,6 @@ def _parse_srv(
159158
else:
160159
hosts = host_part
161160

162-
hosts = unquote_plus(hosts)
163161
srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts")
164162
srv_allowed_hosts_suffix = srv_allowed_hosts_suffix or options.get("srvAllowedHostsSuffix")
165163
if is_srv:

‎pymongo/uri_parser_shared.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,23 @@ def split_hosts(hosts: str, default_port: Optional[int] = DEFAULT_PORT) -> list[
450450
if not entity:
451451
raise ConfigurationError("Empty host (or extra comma in host list)")
452452
port = default_port
453-
# Unix socket entities don't have ports
453+
node = entity
454+
# Decoding happens per entity, after splitting on ",".
454455
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)
455460
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))
457470
return nodes
458471

459472

@@ -576,7 +589,6 @@ def _validate_uri(
576589
if "/" in hosts:
577590
raise InvalidURI(f"Any '/' in a unix domain socket must be percent-encoded: {host_part}")
578591

579-
hosts = unquote_plus(hosts)
580592
fqdn = None
581593
srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts")
582594
if is_srv:

‎test/asynchronous/test_encryption.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,14 @@ async def test_04_kmip_endpoint_invalid_port(self):
12991299
with self.assertRaisesRegex(EncryptionError, "localhost:12345"):
13001300
await self.client_encryption.create_data_key("kmip", master_key=master_key)
13011301

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+
13021310
@unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set")
13031311
async def test_05_aws_endpoint_wrong_region(self):
13041312
master_key = {

‎test/test_bson.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,14 @@ def test_overflow(self):
691691
self.assertTrue(encode({"x": -9223372036854775808}))
692692
self.assertRaises(OverflowError, encode, {"x": -9223372036854775809})
693693

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+
694702
def test_small_long_encode_decode(self):
695703
encoded1 = encode({"x": 256})
696704
decoded1 = decode(encoded1)["x"]

0 commit comments

Comments
 (0)