Repository: ultrajson/ultrajson (language: python)

Issue:
KeyboardInterrupt inside dict key's `__str__` method leads to segmentation fault
Reported by @smason in #419, filing as a separate issue so it doesn't get lost after that issue is fixed:

> While debugging this I also noticed that if I hit Ctl+C in the above loop Python would sometimes get terminated with SIGSEGV . I can reliably reproduce this via:
>
> ```python3
> class Obj:
>     def __str__(self):
>         raise KeyboardInterrupt
> 
> dumps({Obj(): 1})
> ```

I can still reproduce this with ujson 5.2.0, Python 3.10.1 on Debian Sid and also with #521 applied.


Discussion / hints:
There's an error check missing for `PyObject_Str` in `Dict_iterNext`. `itemName` becomes `NULL` when an exception is raised during the string conversion, and from there it blows up.

Fix patch:
diff --git a/python/objToJSON.c b/python/objToJSON.c
index 8f4be736..d7f6cb92 100644
--- a/python/objToJSON.c
+++ b/python/objToJSON.c
@@ -257,6 +257,11 @@ static int Dict_iterNext(JSOBJ obj, JSONTypeContext *tc)
     itemNameTmp = GET_TC(tc)->itemName;
     GET_TC(tc)->itemName = PyObject_Str(GET_TC(tc)->itemName);
     Py_DECREF(itemNameTmp);
+    if (PyErr_Occurred())
+    {
+      PRINTMARK();
+      return -1;
+    }
     itemNameTmp = GET_TC(tc)->itemName;
     GET_TC(tc)->itemName = PyUnicode_AsUTF8String (GET_TC(tc)->itemName);
     Py_DECREF(itemNameTmp);
@@ -332,6 +337,10 @@ static int SortedDict_iterNext(JSOBJ obj, JSONTypeContext *tc)
       else if (!PyBytes_Check(key))
       {
         key = PyObject_Str(key);
+        if (PyErr_Occurred())
+        {
+          goto error;
+        }
         keyTmp = key;
         key = PyUnicode_AsUTF8String(key);
         Py_DECREF(keyTmp);


Test patch:
diff --git a/tests/test_ujson.py b/tests/test_ujson.py
index ed8a2b2d..19596571 100644
--- a/tests/test_ujson.py
+++ b/tests/test_ujson.py
@@ -996,6 +996,16 @@ def convert(self):
     )
 
 
+@pytest.mark.parametrize("sort_keys", [False, True])
+def test_obj_str_exception(sort_keys):
+    class Obj:
+        def __str__(self):
+            raise NotImplementedError
+
+    with pytest.raises(NotImplementedError):
+        ujson.dumps({Obj(): 1}, sort_keys=sort_keys)
+
+
 """
 def test_decode_numeric_int_frc_overflow():
 input = "X.Y"