diff --git a/CHANGES/1310.bugfix.rst b/CHANGES/1310.bugfix.rst new file mode 100644 index 000000000..369ff2ed7 --- /dev/null +++ b/CHANGES/1310.bugfix.rst @@ -0,0 +1,2 @@ +A segmentation fault that could be triggered when getting an item is now fixed +-- by :user:`Vizonex`. diff --git a/multidict/_multidict_py.py b/multidict/_multidict_py.py index 65fb83be6..8f9e5962b 100644 --- a/multidict/_multidict_py.py +++ b/multidict/_multidict_py.py @@ -834,8 +834,22 @@ def _parse_args( f"multidict update sequence element #{pos} " f"has length {len(item)}; 2 is required" ) - identity = identity_func(item[0]) - yield _Entry(hash(identity), identity, item[0], item[1]) + try: + key = item[0] + except Exception as exc: + raise ValueError( + f"multidict update sequence element #{pos}'s " + f"key could not be fetched" + ) from exc + try: + value = item[1] + except Exception as exc: + raise ValueError( + f"multidict update sequence element #{pos}'s " + f"value could not be fetched" + ) from exc + identity = identity_func(key) + yield _Entry(hash(identity), identity, key, value) else: yield len(kwargs) for key, value in kwargs.items(): diff --git a/multidict/_multilib/hashtable.h b/multidict/_multilib/hashtable.h index ece41c43f..f2ba9868a 100644 --- a/multidict/_multilib/hashtable.h +++ b/multidict/_multilib/hashtable.h @@ -1470,8 +1470,8 @@ _err_cannot_fetch(Py_ssize_t i, const char *name) PyErr_Format(PyExc_ValueError, "multidict update sequence element #%zd's " "%s could not be fetched", - name, - i); + i, + name); } static int @@ -1507,11 +1507,11 @@ _md_parse_item(Py_ssize_t i, PyObject *item, PyObject **pkey, goto fail; } *pkey = PySequence_ITEM(item, 0); - *pvalue = PySequence_ITEM(item, 1); if (*pkey == NULL) { _err_cannot_fetch(i, "key"); goto fail; } + *pvalue = PySequence_ITEM(item, 1); if (*pvalue == NULL) { _err_cannot_fetch(i, "value"); goto fail; diff --git a/tests/test_multidict.py b/tests/test_multidict.py index 28e483194..1a55e450f 100644 --- a/tests/test_multidict.py +++ b/tests/test_multidict.py @@ -300,6 +300,42 @@ def test_cannot_create_from_unaccepted( ): cls([(1, 2, 3)]) # type: ignore[call-arg] + def test_cannot_create_from_item_with_failing_getitem( + self, + cls: type[MutableMultiMapping[str]], + ) -> None: + class BadItem: + def __len__(self) -> int: + return 2 + + def __getitem__(self, i: int) -> object: + raise RuntimeError("intentional getitem failure") + + with pytest.raises( + ValueError, + match=r"^multidict update sequence element #0's key could not be fetched$", + ): + cls([BadItem()]) # type: ignore[call-arg] + + def test_cannot_create_from_item_with_failing_getitem_value( + self, + cls: type[MutableMultiMapping[str]], + ) -> None: + class BadValueItem: + def __len__(self) -> int: + return 2 + + def __getitem__(self, i: int) -> object: + if i == 0: + return "key" + raise RuntimeError("intentional getitem failure") + + with pytest.raises( + ValueError, + match=r"^multidict update sequence element #0's value could not be fetched$", + ): + cls([BadValueItem()]) # type: ignore[call-arg] + def test_keys_is_set_less(self, cls: type[MultiDict[str]]) -> None: d = cls([("key", "value1")])