diff mbox series

[meta-python,wrynose,1/2] python3-pymongo: Fix CVE-2026-88029

Message ID 20261105071242.2090700-1-yurade@cisco.com
State New
Headers show
Series [meta-python,wrynose,1/2] python3-pymongo: Fix CVE-2026-88029 | expand

Commit Message

Yogita Urade Nov. 5, 2026, 7:12 a.m. UTC
This patch applies the upstream fix as referenced in [2],
using the commit shown in [1].

[1] https://github.com/mongodb/mongo-python-driver/commit/fa676586ba4b399168a4d1d41c30dc2166cc44fd
[2] https://jira.mongodb.org/browse/PYTHON-5994

Signed-off-by: Yogita Urade <yurade@cisco.com>
---
 .../python3-pymongo/CVE-2026-88029.patch      | 1255 +++++++++++++++++
 .../python/python3-pymongo_4.16.0.bb          |    2 +
 2 files changed, 1257 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-pymongo/CVE-2026-88029.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-pymongo/CVE-2026-88029.patch b/meta-python/recipes-devtools/python/python3-pymongo/CVE-2026-88029.patch
new file mode 100644
index 0000000000..edb5683bd0
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-pymongo/CVE-2026-88029.patch
@@ -0,0 +1,1255 @@ 
+From fa676586ba4b399168a4d1d41c30dc2166cc44fd Mon Sep 17 00:00:00 2001
+From: Jeffrey 'Alex' Clark <aclark@aclark.net>
+Date: Thu, 10 Sep 2026 10:12:37 -0400
+Subject: [PATCH] PYTHON-5994 Use exact match for file ID in GridFS delete
+ methods (#3046)
+
+Co-authored-by: Iris <58442094+sleepyStick@users.noreply.github.com>
+
+CVE: CVE-2026-88029
+Upstream-Status: Backport [https://github.com/mongodb/mongo-python-driver/commit/fa676586ba4b399168a4d1d41c30dc2166cc44fd]
+
+(cherry picked from commit fa676586ba4b399168a4d1d41c30dc2166cc44fd)
+Signed-off-by: Yogita Urade <yurade@cisco.com>
+---
+ gridfs/asynchronous/grid_file.py              |  36 +-
+ gridfs/grid_file_shared.py                    |   4 +-
+ gridfs/synchronous/grid_file.py               |  34 +-
+ test/asynchronous/test_gridfs_bucket.py       |  30 +
+ test/gridfs/queries-use-eq.json               | 528 ++++++++++++++++++
+ .../unified/gridfs-download-serverErrors.json | 100 +++-
+ .../unified/gridfs-download.json              |  24 +-
+ test/test_gridfs_bucket.py                    |  28 +
+ 8 files changed, 728 insertions(+), 56 deletions(-)
+
+diff --git a/gridfs/asynchronous/grid_file.py b/gridfs/asynchronous/grid_file.py
+index e512f796..bd5d75aa 100644
+--- a/gridfs/asynchronous/grid_file.py
++++ b/gridfs/asynchronous/grid_file.py
+@@ -282,8 +282,8 @@ class AsyncGridFS:
+            ``delete`` no longer ensures indexes.
+         """
+         _disallow_transactions(session)
+-        await self._files.delete_one({"_id": file_id}, session=session)
+-        await self._chunks.delete_many({"files_id": file_id}, session=session)
++        await self._files.delete_one({"_id": {"$eq": file_id}}, session=session)
++        await self._chunks.delete_many({"files_id": {"$eq": file_id}}, session=session)
+ 
+     async def list(self, session: Optional[AsyncClientSession] = None) -> list[str]:
+         """List the names of all files stored in this instance of
+@@ -339,7 +339,7 @@ class AsyncGridFS:
+            Added ``session`` parameter.
+         """
+         if filter is not None and not isinstance(filter, abc.Mapping):
+-            filter = {"_id": filter}
++            filter = {"_id": {"$eq": filter}}
+ 
+         _disallow_transactions(session)
+         async for f in self.find(filter, *args, session=session, **kwargs):
+@@ -453,6 +453,8 @@ class AsyncGridFS:
+         if kwargs:
+             f = await self._files.find_one(kwargs, ["_id"], session=session)
+         else:
++            if document_or_id is not None and not isinstance(document_or_id, abc.Mapping):
++                document_or_id = {"_id": {"$eq": document_or_id}}
+             f = await self._files.find_one(document_or_id, ["_id"], session=session)
+ 
+         return f is not None
+@@ -829,8 +831,8 @@ class AsyncGridFSBucket:
+            Added ``session`` parameter.
+         """
+         _disallow_transactions(session)
+-        res = await self._files.delete_one({"_id": file_id}, session=session)
+-        await self._chunks.delete_many({"files_id": file_id}, session=session)
++        res = await self._files.delete_one({"_id": {"$eq": file_id}}, session=session)
++        await self._chunks.delete_many({"files_id": {"$eq": file_id}}, session=session)
+         if not res.deleted_count:
+             raise NoFile("no file could be deleted because none matched %s" % file_id)
+ 
+@@ -1042,7 +1044,7 @@ class AsyncGridFSBucket:
+         """
+         _disallow_transactions(session)
+         result = await self._files.update_one(
+-            {"_id": file_id}, {"$set": {"filename": new_filename}}, session=session
++            {"_id": {"$eq": file_id}}, {"$set": {"filename": new_filename}}, session=session
+         )
+         if not result.matched_count:
+             raise NoFile(
+@@ -1197,8 +1199,12 @@ class AsyncGridIn:
+ 
+     async def abort(self) -> None:
+         """Remove all chunks/files that may have been uploaded and close."""
+-        await self._coll.chunks.delete_many({"files_id": self._file["_id"]}, session=self._session)
+-        await self._coll.files.delete_one({"_id": self._file["_id"]}, session=self._session)
++        await self._coll.chunks.delete_many(
++            {"files_id": {"$eq": self._file["_id"]}}, session=self._session
++        )
++        await self._coll.files.delete_one(
++            {"_id": {"$eq": self._file["_id"]}}, session=self._session
++        )
+         object.__setattr__(self, "_closed", True)
+ 
+     @property
+@@ -1247,7 +1253,9 @@ class AsyncGridIn:
+             self._file[name] = value
+             if self._closed:
+                 if _IS_SYNC:
+-                    self._coll.files.update_one({"_id": self._file["_id"]}, {"$set": {name: value}})
++                    self._coll.files.update_one(
++                        {"_id": {"$eq": self._file["_id"]}}, {"$set": {name: value}}
++                    )
+                 else:
+                     raise AttributeError(
+                         "AsyncGridIn does not support __setattr__ after being closed(). Set the attribute before closing the file or use AsyncGridIn.set() instead"
+@@ -1256,7 +1264,9 @@ class AsyncGridIn:
+     async def set(self, name: str, value: Any) -> None:
+         self._file[name] = value
+         if self._closed:
+-            await self._coll.files.update_one({"_id": self._file["_id"]}, {"$set": {name: value}})
++            await self._coll.files.update_one(
++                {"_id": {"$eq": self._file["_id"]}}, {"$set": {name: value}}
++            )
+ 
+     async def _flush_data(self, data: Any, force: bool = False) -> None:
+         """Flush `data` to a chunk."""
+@@ -1582,7 +1592,9 @@ class AsyncGridOut(GRIDOUT_BASE_CLASS):  # type: ignore
+     async def open(self) -> None:
+         if not self._file:
+             _disallow_transactions(self._session)
+-            self._file = await self._files.find_one({"_id": self._file_id}, session=self._session)
++            self._file = await self._files.find_one(
++                {"_id": {"$eq": self._file_id}}, session=self._session
++            )
+             if not self._file:
+                 raise NoFile(
+                     f"no file in gridfs collection {self._files!r} with _id {self._file_id!r}"
+@@ -1853,7 +1865,7 @@ class _AsyncGridOutChunkIterator:
+         return self
+ 
+     def _create_cursor(self) -> None:
+-        filter = {"files_id": self._id}
++        filter = {"files_id": {"$eq": self._id}}
+         if self._next_chunk > 0:
+             filter["n"] = {"$gte": self._next_chunk}
+         _disallow_transactions(self._session)
+diff --git a/gridfs/grid_file_shared.py b/gridfs/grid_file_shared.py
+index 79a0ad7f..79d863c8 100644
+--- a/gridfs/grid_file_shared.py
++++ b/gridfs/grid_file_shared.py
+@@ -122,7 +122,9 @@ def _grid_in_property(
+         if warn_str:
+             warnings.warn(warn_str, stacklevel=2, category=DeprecationWarning)
+         if self._closed:
+-            self._coll.files.update_one({"_id": self._file["_id"]}, {"$set": {field_name: value}})
++            self._coll.files.update_one(
++                {"_id": {"$eq": self._file["_id"]}}, {"$set": {field_name: value}}
++            )
+         self._file[field_name] = value
+ 
+     if read_only:
+diff --git a/gridfs/synchronous/grid_file.py b/gridfs/synchronous/grid_file.py
+index 70a4f807..a6cc6b50 100644
+--- a/gridfs/synchronous/grid_file.py
++++ b/gridfs/synchronous/grid_file.py
+@@ -282,8 +282,8 @@ class GridFS:
+            ``delete`` no longer ensures indexes.
+         """
+         _disallow_transactions(session)
+-        self._files.delete_one({"_id": file_id}, session=session)
+-        self._chunks.delete_many({"files_id": file_id}, session=session)
++        self._files.delete_one({"_id": {"$eq": file_id}}, session=session)
++        self._chunks.delete_many({"files_id": {"$eq": file_id}}, session=session)
+ 
+     def list(self, session: Optional[ClientSession] = None) -> list[str]:
+         """List the names of all files stored in this instance of
+@@ -337,7 +337,7 @@ class GridFS:
+            Added ``session`` parameter.
+         """
+         if filter is not None and not isinstance(filter, abc.Mapping):
+-            filter = {"_id": filter}
++            filter = {"_id": {"$eq": filter}}
+ 
+         _disallow_transactions(session)
+         for f in self.find(filter, *args, session=session, **kwargs):
+@@ -451,6 +451,8 @@ class GridFS:
+         if kwargs:
+             f = self._files.find_one(kwargs, ["_id"], session=session)
+         else:
++            if document_or_id is not None and not isinstance(document_or_id, abc.Mapping):
++                document_or_id = {"_id": {"$eq": document_or_id}}
+             f = self._files.find_one(document_or_id, ["_id"], session=session)
+ 
+         return f is not None
+@@ -825,8 +827,8 @@ class GridFSBucket:
+            Added ``session`` parameter.
+         """
+         _disallow_transactions(session)
+-        res = self._files.delete_one({"_id": file_id}, session=session)
+-        self._chunks.delete_many({"files_id": file_id}, session=session)
++        res = self._files.delete_one({"_id": {"$eq": file_id}}, session=session)
++        self._chunks.delete_many({"files_id": {"$eq": file_id}}, session=session)
+         if not res.deleted_count:
+             raise NoFile("no file could be deleted because none matched %s" % file_id)
+ 
+@@ -1034,7 +1036,7 @@ class GridFSBucket:
+         """
+         _disallow_transactions(session)
+         result = self._files.update_one(
+-            {"_id": file_id}, {"$set": {"filename": new_filename}}, session=session
++            {"_id": {"$eq": file_id}}, {"$set": {"filename": new_filename}}, session=session
+         )
+         if not result.matched_count:
+             raise NoFile(
+@@ -1185,8 +1187,10 @@ class GridIn:
+ 
+     def abort(self) -> None:
+         """Remove all chunks/files that may have been uploaded and close."""
+-        self._coll.chunks.delete_many({"files_id": self._file["_id"]}, session=self._session)
+-        self._coll.files.delete_one({"_id": self._file["_id"]}, session=self._session)
++        self._coll.chunks.delete_many(
++            {"files_id": {"$eq": self._file["_id"]}}, session=self._session
++        )
++        self._coll.files.delete_one({"_id": {"$eq": self._file["_id"]}}, session=self._session)
+         object.__setattr__(self, "_closed", True)
+ 
+     @property
+@@ -1235,7 +1239,9 @@ class GridIn:
+             self._file[name] = value
+             if self._closed:
+                 if _IS_SYNC:
+-                    self._coll.files.update_one({"_id": self._file["_id"]}, {"$set": {name: value}})
++                    self._coll.files.update_one(
++                        {"_id": {"$eq": self._file["_id"]}}, {"$set": {name: value}}
++                    )
+                 else:
+                     raise AttributeError(
+                         "GridIn does not support __setattr__ after being closed(). Set the attribute before closing the file or use GridIn.set() instead"
+@@ -1244,7 +1250,9 @@ class GridIn:
+     def set(self, name: str, value: Any) -> None:
+         self._file[name] = value
+         if self._closed:
+-            self._coll.files.update_one({"_id": self._file["_id"]}, {"$set": {name: value}})
++            self._coll.files.update_one(
++                {"_id": {"$eq": self._file["_id"]}}, {"$set": {name: value}}
++            )
+ 
+     def _flush_data(self, data: Any, force: bool = False) -> None:
+         """Flush `data` to a chunk."""
+@@ -1570,7 +1578,9 @@ class GridOut(GRIDOUT_BASE_CLASS):  # type: ignore
+     def open(self) -> None:
+         if not self._file:
+             _disallow_transactions(self._session)
+-            self._file = self._files.find_one({"_id": self._file_id}, session=self._session)
++            self._file = self._files.find_one(
++                {"_id": {"$eq": self._file_id}}, session=self._session
++            )
+             if not self._file:
+                 raise NoFile(
+                     f"no file in gridfs collection {self._files!r} with _id {self._file_id!r}"
+@@ -1841,7 +1851,7 @@ class GridOutChunkIterator:
+         return self
+ 
+     def _create_cursor(self) -> None:
+-        filter = {"files_id": self._id}
++        filter = {"files_id": {"$eq": self._id}}
+         if self._next_chunk > 0:
+             filter["n"] = {"$gte": self._next_chunk}
+         _disallow_transactions(self._session)
+diff --git a/test/asynchronous/test_gridfs_bucket.py b/test/asynchronous/test_gridfs_bucket.py
+index 6b3503d122..ac850f1063 100644
+--- a/test/asynchronous/test_gridfs_bucket.py
++++ b/test/asynchronous/test_gridfs_bucket.py
+@@ -33,6 +33,7 @@
+ import gridfs
+ from bson.binary import Binary
+ from bson.int64 import Int64
++from bson.min_key import MinKey
+ from bson.objectid import ObjectId
+ from bson.son import SON
+ from gridfs.errors import CorruptGridFile, NoFile
+@@ -42,10 +42,11 @@
+ from pymongo.errors import (
+     ConfigurationError,
+     NotPrimaryError,
+     ServerSelectionTimeoutError,
+     WriteConcernError,
++    WriteError,
+ )
+ from pymongo.read_preferences import ReadPreference
+ 
+ _IS_SYNC = False
+ 
+@@ -475,6 +477,34 @@ async def test_abort(self):
+             await gin.write(b"test4")
+         self.assertEqual(0, await self.db.fs.chunks.count_documents({"files_id": gin._id}))
+ 
++    # Server versions older than 5.0 do not support document values with
++    # "$"-prefixed keys.
++    @async_client_context.require_version_min(5, 0)
++    async def test_abort_with_injected_file_id_does_not_delete_other_files(self):
++        bucket = self.fs
++        file1_bytes = b"\x11\x22\x33\x44"
++        await bucket.upload_from_stream("file1", file1_bytes)
++
++        upload_stream = bucket.open_upload_stream_with_id(
++            {"$gt": MinKey()}, "file2", chunk_size_bytes=2
++        )
++        await upload_stream.write(b"\x55\x66\x77\x88")
++        try:
++            await upload_stream.abort()
++        except WriteError:
++            # Newer server versions reject an equality match on "_id" whose
++            # operand itself resembles a query operator, even when the
++            # driver safely wraps it in "$eq". The chunks are still cleaned
++            # up in that case; only the (never created) files document
++            # delete is rejected.
++            pass
++
++        self.assertEqual(
++            file1_bytes, await (await bucket.open_download_stream_by_name("file1")).read()
++        )
++        with self.assertRaises(NoFile):
++            await bucket.open_download_stream_by_name("file2")
++
+     async def test_download_to_stream(self):
+         file1 = BytesIO(b"hello world")
+         # Test with one chunk.
+diff --git a/test/gridfs/queries-use-eq.json b/test/gridfs/queries-use-eq.json
+new file mode 100644
+index 0000000000..e068168b91
+--- /dev/null
++++ b/test/gridfs/queries-use-eq.json
+@@ -0,0 +1,528 @@
++{
++  "description": "gridfs-queriesUseEq",
++  "schemaVersion": "1.1",
++  "createEntities": [
++    {
++      "client": {
++        "id": "client0",
++        "observeEvents": [
++          "commandStartedEvent"
++        ]
++      }
++    },
++    {
++      "database": {
++        "id": "database0",
++        "client": "client0",
++        "databaseName": "gridfs-tests"
++      }
++    },
++    {
++      "bucket": {
++        "id": "bucket0",
++        "database": "database0"
++      }
++    },
++    {
++      "collection": {
++        "id": "bucket0_files_collection",
++        "database": "database0",
++        "collectionName": "fs.files"
++      }
++    },
++    {
++      "collection": {
++        "id": "bucket0_chunks_collection",
++        "database": "database0",
++        "collectionName": "fs.chunks"
++      }
++    }
++  ],
++  "initialData": [
++    {
++      "collectionName": "fs.files",
++      "databaseName": "gridfs-tests",
++      "documents": [
++        {
++          "_id": {
++            "$oid": "000000000000000000000001"
++          },
++          "length": 2,
++          "chunkSize": 4,
++          "uploadDate": {
++            "$date": "1970-01-01T00:00:00.000Z"
++          },
++          "filename": "file-1"
++        },
++        {
++          "_id": {
++            "$oid": "000000000000000000000002"
++          },
++          "length": 0,
++          "chunkSize": 4,
++          "uploadDate": {
++            "$date": "1970-01-01T00:00:00.000Z"
++          },
++          "filename": "file-2"
++        }
++      ]
++    },
++    {
++      "collectionName": "fs.chunks",
++      "databaseName": "gridfs-tests",
++      "documents": [
++        {
++          "_id": {
++            "$oid": "000000000000000000000011"
++          },
++          "files_id": {
++            "$oid": "000000000000000000000001"
++          },
++          "n": 0,
++          "data": {
++            "$binary": {
++              "base64": "ESI=",
++              "subType": "00"
++            }
++          }
++        }
++      ]
++    }
++  ],
++  "_yamlAnchors": {
++    "injectedId": {
++      "$gt": {
++        "$minKey": 1
++      }
++    },
++    "unchangedOutcome": [
++      {
++        "collectionName": "fs.files",
++        "databaseName": "gridfs-tests",
++        "documents": [
++          {
++            "_id": {
++              "$oid": "000000000000000000000001"
++            },
++            "length": 2,
++            "chunkSize": 4,
++            "uploadDate": {
++              "$date": "1970-01-01T00:00:00.000Z"
++            },
++            "filename": "file-1"
++          },
++          {
++            "_id": {
++              "$oid": "000000000000000000000002"
++            },
++            "length": 0,
++            "chunkSize": 4,
++            "uploadDate": {
++              "$date": "1970-01-01T00:00:00.000Z"
++            },
++            "filename": "file-2"
++          }
++        ]
++      },
++      {
++        "collectionName": "fs.chunks",
++        "databaseName": "gridfs-tests",
++        "documents": [
++          {
++            "_id": {
++              "$oid": "000000000000000000000011"
++            },
++            "files_id": {
++              "$oid": "000000000000000000000001"
++            },
++            "n": 0,
++            "data": {
++              "$binary": {
++                "base64": "ESI=",
++                "subType": "00"
++              }
++            }
++          }
++        ]
++      }
++    ]
++  },
++  "tests": [
++    {
++      "description": "download uses $eq to query the files and chunks collections",
++      "operations": [
++        {
++          "name": "download",
++          "object": "bucket0",
++          "arguments": {
++            "id": {
++              "$oid": "000000000000000000000001"
++            }
++          },
++          "expectResult": {
++            "$$matchesHexBytes": "1122"
++          }
++        }
++      ],
++      "expectEvents": [
++        {
++          "client": "client0",
++          "events": [
++            {
++              "commandStartedEvent": {
++                "command": {
++                  "find": "fs.files",
++                  "filter": {
++                    "_id": {
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
++                    }
++                  }
++                }
++              }
++            },
++            {
++              "commandStartedEvent": {
++                "command": {
++                  "find": "fs.chunks",
++                  "filter": {
++                    "files_id": {
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
++                    }
++                  }
++                }
++              }
++            }
++          ]
++        }
++      ]
++    },
++    {
++      "description": "delete uses $eq to delete the files collection document and chunks",
++      "operations": [
++        {
++          "name": "delete",
++          "object": "bucket0",
++          "arguments": {
++            "id": {
++              "$oid": "000000000000000000000001"
++            }
++          }
++        }
++      ],
++      "expectEvents": [
++        {
++          "client": "client0",
++          "events": [
++            {
++              "commandStartedEvent": {
++                "command": {
++                  "delete": "fs.files",
++                  "deletes": [
++                    {
++                      "q": {
++                        "_id": {
++                          "$eq": {
++                            "$oid": "000000000000000000000001"
++                          }
++                        }
++                      },
++                      "limit": 1
++                    }
++                  ]
++                }
++              }
++            },
++            {
++              "commandStartedEvent": {
++                "command": {
++                  "delete": "fs.chunks",
++                  "deletes": [
++                    {
++                      "q": {
++                        "files_id": {
++                          "$eq": {
++                            "$oid": "000000000000000000000001"
++                          }
++                        }
++                      },
++                      "limit": 0
++                    }
++                  ]
++                }
++              }
++            }
++          ]
++        }
++      ],
++      "outcome": [
++        {
++          "collectionName": "fs.files",
++          "databaseName": "gridfs-tests",
++          "documents": [
++            {
++              "_id": {
++                "$oid": "000000000000000000000002"
++              },
++              "length": 0,
++              "chunkSize": 4,
++              "uploadDate": {
++                "$date": "1970-01-01T00:00:00.000Z"
++              },
++              "filename": "file-2"
++            }
++          ]
++        },
++        {
++          "collectionName": "fs.chunks",
++          "databaseName": "gridfs-tests",
++          "documents": []
++        }
++      ]
++    },
++    {
++      "description": "rename uses $eq to update the files collection document",
++      "operations": [
++        {
++          "name": "rename",
++          "object": "bucket0",
++          "arguments": {
++            "id": {
++              "$oid": "000000000000000000000001"
++            },
++            "newFilename": "file-1-renamed"
++          }
++        }
++      ],
++      "expectEvents": [
++        {
++          "client": "client0",
++          "events": [
++            {
++              "commandStartedEvent": {
++                "command": {
++                  "update": "fs.files",
++                  "updates": [
++                    {
++                      "q": {
++                        "_id": {
++                          "$eq": {
++                            "$oid": "000000000000000000000001"
++                          }
++                        }
++                      },
++                      "u": {
++                        "$set": {
++                          "filename": "file-1-renamed"
++                        }
++                      },
++                      "multi": {
++                        "$$unsetOrMatches": false
++                      },
++                      "upsert": {
++                        "$$unsetOrMatches": false
++                      }
++                    }
++                  ]
++                }
++              }
++            }
++          ]
++        }
++      ],
++      "outcome": [
++        {
++          "collectionName": "fs.files",
++          "databaseName": "gridfs-tests",
++          "documents": [
++            {
++              "_id": {
++                "$oid": "000000000000000000000001"
++              },
++              "length": 2,
++              "chunkSize": 4,
++              "uploadDate": {
++                "$date": "1970-01-01T00:00:00.000Z"
++              },
++              "filename": "file-1-renamed"
++            },
++            {
++              "_id": {
++                "$oid": "000000000000000000000002"
++              },
++              "length": 0,
++              "chunkSize": 4,
++              "uploadDate": {
++                "$date": "1970-01-01T00:00:00.000Z"
++              },
++              "filename": "file-2"
++            }
++          ]
++        }
++      ]
++    },
++    {
++      "description": "delete with a file id containing a query operator does not delete any files",
++      "operations": [
++        {
++          "name": "delete",
++          "object": "bucket0",
++          "arguments": {
++            "id": {
++              "$gt": {
++                "$minKey": 1
++              }
++            }
++          },
++          "expectError": {
++            "isError": true
++          }
++        }
++      ],
++      "outcome": [
++        {
++          "collectionName": "fs.files",
++          "databaseName": "gridfs-tests",
++          "documents": [
++            {
++              "_id": {
++                "$oid": "000000000000000000000001"
++              },
++              "length": 2,
++              "chunkSize": 4,
++              "uploadDate": {
++                "$date": "1970-01-01T00:00:00.000Z"
++              },
++              "filename": "file-1"
++            },
++            {
++              "_id": {
++                "$oid": "000000000000000000000002"
++              },
++              "length": 0,
++              "chunkSize": 4,
++              "uploadDate": {
++                "$date": "1970-01-01T00:00:00.000Z"
++              },
++              "filename": "file-2"
++            }
++          ]
++        },
++        {
++          "collectionName": "fs.chunks",
++          "databaseName": "gridfs-tests",
++          "documents": [
++            {
++              "_id": {
++                "$oid": "000000000000000000000011"
++              },
++              "files_id": {
++                "$oid": "000000000000000000000001"
++              },
++              "n": 0,
++              "data": {
++                "$binary": {
++                  "base64": "ESI=",
++                  "subType": "00"
++                }
++              }
++            }
++          ]
++        }
++      ]
++    },
++    {
++      "description": "download with a file id containing a query operator does not return any files",
++      "operations": [
++        {
++          "name": "download",
++          "object": "bucket0",
++          "arguments": {
++            "id": {
++              "$gt": {
++                "$minKey": 1
++              }
++            }
++          },
++          "expectError": {
++            "isClientError": true
++          }
++        }
++      ]
++    },
++    {
++      "description": "rename with a file id containing a query operator does not rename any files",
++      "operations": [
++        {
++          "name": "rename",
++          "object": "bucket0",
++          "arguments": {
++            "id": {
++              "$gt": {
++                "$minKey": 1
++              }
++            },
++            "newFilename": "injected"
++          },
++          "expectError": {
++            "isError": true
++          }
++        }
++      ],
++      "outcome": [
++        {
++          "collectionName": "fs.files",
++          "databaseName": "gridfs-tests",
++          "documents": [
++            {
++              "_id": {
++                "$oid": "000000000000000000000001"
++              },
++              "length": 2,
++              "chunkSize": 4,
++              "uploadDate": {
++                "$date": "1970-01-01T00:00:00.000Z"
++              },
++              "filename": "file-1"
++            },
++            {
++              "_id": {
++                "$oid": "000000000000000000000002"
++              },
++              "length": 0,
++              "chunkSize": 4,
++              "uploadDate": {
++                "$date": "1970-01-01T00:00:00.000Z"
++              },
++              "filename": "file-2"
++            }
++          ]
++        },
++        {
++          "collectionName": "fs.chunks",
++          "databaseName": "gridfs-tests",
++          "documents": [
++            {
++              "_id": {
++                "$oid": "000000000000000000000011"
++              },
++              "files_id": {
++                "$oid": "000000000000000000000001"
++              },
++              "n": 0,
++              "data": {
++                "$binary": {
++                  "base64": "ESI=",
++                  "subType": "00"
++                }
++              }
++            }
++          ]
++        }
++      ]
++    }
++  ]
++}
+diff --git a/test/retryable_reads/unified/gridfs-download-serverErrors.json b/test/retryable_reads/unified/gridfs-download-serverErrors.json
+index 5bb7eee0b2..dc87dadce4 100644
+--- a/test/retryable_reads/unified/gridfs-download-serverErrors.json
++++ b/test/retryable_reads/unified/gridfs-download-serverErrors.json
+@@ -128,7 +128,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -141,7 +143,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -205,7 +209,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -218,7 +224,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -282,7 +290,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -295,7 +305,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -359,7 +371,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -372,7 +386,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -436,7 +452,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -449,7 +467,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -513,7 +533,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -526,7 +548,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -590,7 +614,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -603,7 +629,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -667,7 +695,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -680,7 +710,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -744,7 +776,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -757,7 +791,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -821,7 +857,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -834,7 +872,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -898,7 +938,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -911,7 +953,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -975,7 +1019,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -988,7 +1034,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -1077,7 +1125,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+diff --git a/test/retryable_reads/unified/gridfs-download.json b/test/retryable_reads/unified/gridfs-download.json
+index 69fe8ff7c8..9b2f1d93d4 100644
+--- a/test/retryable_reads/unified/gridfs-download.json
++++ b/test/retryable_reads/unified/gridfs-download.json
+@@ -109,7 +109,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -173,7 +175,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -186,7 +190,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -283,7 +289,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -339,7 +347,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+@@ -352,7 +362,9 @@
+                   "find": "fs.files",
+                   "filter": {
+                     "_id": {
+-                      "$oid": "000000000000000000000001"
++                      "$eq": {
++                        "$oid": "000000000000000000000001"
++                      }
+                     }
+                   }
+                 },
+diff --git a/test/test_gridfs_bucket.py b/test/test_gridfs_bucket.py
+index 4d1ca7b811..90b063a9b1 100644
+--- a/test/test_gridfs_bucket.py
++++ b/test/test_gridfs_bucket.py
+@@ -33,6 +33,7 @@
+ import gridfs
+ from bson.binary import Binary
+ from bson.int64 import Int64
++from bson.min_key import MinKey
+ from bson.objectid import ObjectId
+ from bson.son import SON
+ from gridfs.errors import CorruptGridFile, NoFile
+@@ -41,6 +42,7 @@
+     NotPrimaryError,
+     ServerSelectionTimeoutError,
+     WriteConcernError,
++    WriteError,
+ )
+ from pymongo.read_preferences import ReadPreference
+ from pymongo.synchronous.mongo_client import MongoClient
+@@ -445,6 +447,32 @@ def test_abort(self):
+             gin.write(b"test4")
+         self.assertEqual(0, self.db.fs.chunks.count_documents({"files_id": gin._id}))
+ 
++    # Server versions older than 5.0 do not support document values with
++    # "$"-prefixed keys.
++    @client_context.require_version_min(5, 0)
++    def test_abort_with_injected_file_id_does_not_delete_other_files(self):
++        bucket = self.fs
++        file1_bytes = b"\x11\x22\x33\x44"
++        bucket.upload_from_stream("file1", file1_bytes)
++
++        upload_stream = bucket.open_upload_stream_with_id(
++            {"$gt": MinKey()}, "file2", chunk_size_bytes=2
++        )
++        upload_stream.write(b"\x55\x66\x77\x88")
++        try:
++            upload_stream.abort()
++        except WriteError:
++            # Newer server versions reject an equality match on "_id" whose
++            # operand itself resembles a query operator, even when the
++            # driver safely wraps it in "$eq". The chunks are still cleaned
++            # up in that case; only the (never created) files document
++            # delete is rejected.
++            pass
++
++        self.assertEqual(file1_bytes, (bucket.open_download_stream_by_name("file1")).read())
++        with self.assertRaises(NoFile):
++            bucket.open_download_stream_by_name("file2")
++
+     def test_download_to_stream(self):
+         file1 = BytesIO(b"hello world")
+         # Test with one chunk.
+-- 
+2.35.6
+
diff --git a/meta-python/recipes-devtools/python/python3-pymongo_4.16.0.bb b/meta-python/recipes-devtools/python/python3-pymongo_4.16.0.bb
index 75d0504d46..f3a4ae4ad1 100644
--- a/meta-python/recipes-devtools/python/python3-pymongo_4.16.0.bb
+++ b/meta-python/recipes-devtools/python/python3-pymongo_4.16.0.bb
@@ -8,6 +8,8 @@  HOMEPAGE = "https://github.com/mongodb/mongo-python-driver"
 LICENSE = "Apache-2.0"
 LIC_FILES_CHKSUM = "file://LICENSE;md5=86d3f3a95c324c9479bd8986968f4327"
 
+SRC_URI += "file://CVE-2026-88029.patch"
+
 SRC_URI[sha256sum] = "8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c"
 
 CVE_PRODUCT = "pymongo"