CVE-2013-2132: Other (NVD-CWE-Other)
value: bson/_cbsonmodule.c in the mongo-python-driver (aka. pymongo) before 2.5.2, as used in MongoDB, allows context-dependent attackers to cause a denial of service (NULL pointer dereference and crash) via vectors related to decoding of an "invalid DBRef."
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Published: 2013-08-15T17:55Z
Repository: https://github.com/mongodb/mongo-python-driver
Language: Python
Files changed: bson/__init__.py, bson/_cbsonmodule.c, test/test_collection.py
Commit date: 2013-05-31 10:17:46 -0400
Commit message: Fix null pointer when decoding invalid DBRef PYTHON-532

Diff: diff --git a/bson/__init__.py b/bson/__init__.py
index fe6bc4f..9197727 100644
--- a/bson/__init__.py
+++ b/bson/__init__.py
@@ -148,11 +148,11 @@ def _get_object(data, position, as_class, tz_aware, uuid_subtype):
     obj_size = struct.unpack("<i", data[position:position + 4])[0]
     encoded = data[position + 4:position + obj_size - 1]
     object = _elements_to_dict(encoded, as_class, tz_aware, uuid_subtype)
     position += obj_size
     if "$ref" in object:
-        return (DBRef(object.pop("$ref"), object.pop("$id"),
+        return (DBRef(object.pop("$ref"), object.pop("$id", None),
                       object.pop("$db", None), object), position)
     return object, position
 
 
 def _get_array(data, position, as_class, tz_aware, uuid_subtype):
diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c
index 0e50f1d..19a56b0 100644
--- a/bson/_cbsonmodule.c
+++ b/bson/_cbsonmodule.c
@@ -1200,12 +1200,18 @@ static PyObject* get_value(PyObject* self, const char* buffer, int* position,
                 PyObject* id = PyDict_GetItemString(value, "$id");
                 PyObject* database = PyDict_GetItemString(value, "$db");
 
                 Py_INCREF(collection);
                 PyDict_DelItemString(value, "$ref");
-                Py_INCREF(id);
-                PyDict_DelItemString(value, "$id");
+
+                if (id == NULL) {
+                    id = Py_None;
+                    Py_INCREF(id);
+                } else {
+                    Py_INCREF(id);
+                    PyDict_DelItemString(value, "$id");
+                }
 
                 if (database == NULL) {
                     database = Py_None;
                     Py_INCREF(database);
                 } else {
diff --git a/test/test_collection.py b/test/test_collection.py
index b54e574..0a4da34 100644
--- a/test/test_collection.py
+++ b/test/test_collection.py
@@ -28,10 +28,11 @@ from nose.plugins.skip import SkipTest
 
 sys.path[0:0] = [""]
 
 from bson.binary import Binary, UUIDLegacy, OLD_UUID_SUBTYPE, UUID_SUBTYPE
 from bson.code import Code
+from bson.dbref import DBRef
 from bson.objectid import ObjectId
 from bson.py3compat import b
 from bson.son import SON
 from pymongo import (ASCENDING, DESCENDING, GEO2D,
                      GEOHAYSTACK, GEOSPHERE, HASHED)
@@ -1673,10 +1674,35 @@ class TestCollection(unittest.TestCase):
         c = self.db.test
         warnings.simplefilter("ignore")
         self.assertRaises(InvalidDocument, c.save, {"x": c})
         warnings.simplefilter("default")
 
+    def test_bad_dbref(self):
+        c = self.db.test
+        c.drop()
+
+        # Incomplete DBRefs.
+        self.assertRaises(
+            InvalidDocument,
+            c.insert, {'ref': {'$ref': 'collection'}})
+
+        self.assertRaises(
+            InvalidDocument,
+            c.insert, {'ref': {'$id': ObjectId()}})
+
+        ref_only = {'ref': {'$ref': 'collection'}}
+        id_only = {'ref': {'$id': ObjectId()}}
+
+        # Force insert of ref without $id.
+        c.insert(ref_only, check_keys=False)
+        self.assertEqual(DBRef('collection', id=None), c.find_one()['ref'])
+        c.drop()
+
+        # DBRef without $ref is decoded as normal subdocument.
+        c.insert(id_only, check_keys=False)
+        self.assertEqual(id_only, c.find_one())
+
     def test_as_class(self):
         c = self.db.test
         c.drop()
         c.insert({"x": 1})
 

Vulnerable code:         return (DBRef(object.pop("$ref"), object.pop("$id"),
                Py_INCREF(id);
                PyDict_DelItemString(value, "$id");
Fixed code:         return (DBRef(object.pop("$ref"), object.pop("$id", None),

                if (id == NULL) {
                    id = Py_None;
                    Py_INCREF(id);
                } else {
                    Py_INCREF(id);
                    PyDict_DelItemString(value, "$id");
                }
from bson.dbref import DBRef
    def test_bad_dbref(self):
        c = self.db.test
        c.drop()

        # Incomplete DBRefs.
        self.assertRaises(
            InvalidDocument,
            c.insert, {'ref': {'$ref': 'collection'}})

        self.assertRaises(
            InvalidDocument,
            c.insert, {'ref': {'$id': ObjectId()}})

        ref_only = {'ref': {'$ref': 'collection'}}
        id_only = {'ref': {'$id': ObjectId()}}

        # Force insert of ref without $id.
        c.insert(ref_only, check_keys=False)
        self.assertEqual(DBRef('collection', id=None), c.find_one()['ref'])
        c.drop()

        # DBRef without $ref is decoded as normal subdocument.
        c.insert(id_only, check_keys=False)
        self.assertEqual(id_only, c.find_one())