From 62fa316b5bdf9b2bb66efa60d1a17b38dc80f946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=A9nainn=20Woodsend?= Date: Sun, 3 May 2026 12:22:48 +0100 Subject: [PATCH] Fix failure cleanup paths in ujson.dump() * Add missing dec-refs for if PyTuple_Pack() or writing the payload to file fails * Add missing bailout for failed PyTuple_Pack() * Add tests for all but the PyTuple_Pack() failing (which requires inducing a malloc() failure) CVE: CVE-2026-44660 Upstream-Status: Backport [https://github.com/ultrajson/ultrajson/commit/82af1d0ac01d09aa40c887b460d44b9d9f4bccd9] Backport Changes: - Adjusted source paths for ujson 5.9.0's pre-src-layout tree. (cherry picked from commit 82af1d0ac01d09aa40c887b460d44b9d9f4bccd9) Signed-off-by: Hetvi Thakar --- python/objToJSON.c | 7 +++++++ tests/test_ujson.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/python/objToJSON.c b/python/objToJSON.c index 9013205..47e46c1 100644 --- a/python/objToJSON.c +++ b/python/objToJSON.c @@ -909,6 +909,11 @@ PyObject* objToJSONFile(PyObject* self, PyObject *args, PyObject *kwargs) } argtuple = PyTuple_Pack(1, data); + if (argtuple == NULL) + { + Py_XDECREF(write); + return NULL; + } string = objToJSON (self, argtuple, kwargs); @@ -925,6 +930,7 @@ PyObject* objToJSONFile(PyObject* self, PyObject *args, PyObject *kwargs) if (argtuple == NULL) { Py_XDECREF(write); + Py_DECREF(string); return NULL; } @@ -932,6 +938,7 @@ PyObject* objToJSONFile(PyObject* self, PyObject *args, PyObject *kwargs) if (write_result == NULL) { Py_XDECREF(write); + Py_DECREF(string); Py_XDECREF(argtuple); return NULL; } diff --git a/tests/test_ujson.py b/tests/test_ujson.py index 9ba6f55..ccff37f 100644 --- a/tests/test_ujson.py +++ b/tests/test_ujson.py @@ -8,6 +8,7 @@ import os.path import re import subprocess import sys +import types import uuid from collections import OrderedDict from pathlib import Path @@ -365,6 +366,38 @@ def test_dump_to_file_like_object(): def test_dump_file_args_error(): with pytest.raises(TypeError): ujson.dump([], "") + with pytest.raises(TypeError): + ujson.dump([], "", "") + + +def test_dump_non_callable_write(): + file = types.SimpleNamespace(write="a") + with pytest.raises(TypeError): + ujson.dump([7] * 100, file) + + +def test_failed_dump(): + with pytest.raises(TypeError): + ujson.dump([[0] * 100, object()], io.StringIO()) + + +def test_failed_dump_bogus_file(): + file = types.SimpleNamespace(write=lambda: None) + with pytest.raises(TypeError, match="0 positional arguments"): + ujson.dump([0] * 100, file) + + +def test_failed_dump_failed_write(): + file = types.SimpleNamespace(write=lambda x: 1 / 0) + with pytest.raises(ZeroDivisionError): + ujson.dump([0] * 100, file) + + +def test_failed_dump_closed_file(): + file = io.StringIO() + file.close() + with pytest.raises(ValueError, match="closed file"): + ujson.dump([0] * 100, file) def test_load_file(): -- 2.35.6