Every CVE Search/ Get API key

CVE-2026-104874

Multidict: Reference leak in CIMultiDict/MultiDict items-view union and subtraction

5.3 MediumFix available

Multidict is an implementation of a multidict data structure. From 6.7.0 until 6.9.1, the C extension's items-view reflected union operation, operand | d.items(), in multidict_itemsview_or2_impl and subtraction operation, d.items() - operand, in multidict_itemsview_sub1_impl fail to release new key-identity and value references returned for each operand element. Applications that perform these operations over attacker-influenced sequences can leak two strong references per element, and garbage collection cannot reclaim them, so repeated operations can cause unbounded process memory growth and denial of service. Forward union, intersection, non-tuple operand elements, and pure-Python builds are not affected by this reference leak. This issue is fixed in version 6.9.1.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
None
Availability
Low

Exploit likelihood

0.45%Up 0.2 points since Oct 3, 2026
90%50%10%1%

What each source reports

CVE List Score used 5.3

Multidict: Reference leak in CIMultiDict/MultiDict items-view union and subtraction

Multidict is an implementation of a multidict data structure. From 6.7.0 until 6.9.1, the C extension's items-view reflected union operation, operand | d.items(), in multidict_itemsview_or2_impl and subtraction operation, d.items() - operand, in multidict_itemsview_sub1_impl fail to release new key-identity and value references returned for each operand element. Applications that perform these operations over attacker-influenced sequences can leak two strong references per element, and garbage collection cannot reclaim them, so repeated operations can cause unbounded process memory growth and denial of service. Forward union, intersection, non-tuple operand elements, and pure-Python builds are not affected by this reference leak. This issue is fixed in version 6.9.1.

Record
CVE-2026-104874
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-401
Updated
2026-11-02
EUVDEUVD-2026-91800 5.3

Multidict is an implementation of a multidict data structure. From 6.7.0 until 6.9.1, the C extension's items-view reflected union operation, operand | d.items(), in multidict_itemsview_or2_impl and subtraction operation, d.items() - operand, in multidict_itemsview_sub1_impl fail to release new key-identity and value references returned for each operand element. Applications that perform these operations over attacker-influenced sequences can leak two strong references per element, and garbage collection cannot reclaim them, so repeated operations can cause unbounded process memory growth and denial of service. Forward union, intersection, non-tuple operand elements, and pure-Python builds are not affected by this reference leak. This issue is fixed in version 6.9.1.

Record
EUVD-2026-91800
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Updated
2026-11-02
GitHub AdvisoriesGHSA-54p9-h82j-f925 5.3

Multidict: Reference leak in CIMultiDict/MultiDict items-view union and subtraction

Description

A reference leak in the items-view union and subtraction operators of aio-libs/multidict 6.7.0 through 6.9.0 (C extension) lets a remote client drive unbounded, unreclaimable memory growth by having each operand element leak one key-identity object and one value object. The reflected-union path (operand | d.items(), multidict_itemsview_or2_impl) and the subtraction path (d.items() - operand, multidict_itemsview_sub1_impl) parse each element into new strong references but release only the tuple wrapper, never the identity and value. Servers in the aio-libs stack build these views over attacker-supplied HTTP headers and query strings, so the operand size is under remote control. Forced garbage collection does not recover the leaked objects, so resident memory rises monotonically until the process is killed.


Root Cause

_multidict_itemsview_parse_item() returns new references: a fresh identity via md_calc_identity() and a fresh value via Py_NewRef(). The or2_impl first parse loop requests both but clears only arg:

// views.h:565 — or2_impl first parse loop
while ((st = PyIter_NextItem(iter, &arg)) > 0) {
    int tmp = _multidict_itemsview_parse_item(
        self, arg, &identity, NULL, &value);   // identity + value: new refs
    if (tmp < 0) goto fail;
    else if (tmp > 0) {
        if (_set_add(tmp_set, identity, value) < 0) goto fail;
    }
    Py_CLEAR(arg);                             // :575 clears arg ONLY
}

_set_add() builds its own tuple with PyTuple_Pack and Py_DECREFs it, so it never borrows the loop's identity/value. On the next iteration those variables are overwritten, so the previous references are lost permanently. The sub1_impl first loop (:686-696) has the identical omission.

This is an editing slip, not an ownership contract: every sibling path clears all three references per iteration, and1_impl (:304-306), and2_impl (:389-391), or1_impl (:506-508), and the second (md_next) loops of both functions (:605-607, :726-728). PR #1413 added a Py_DECREF(tpl) to the second loop of or2/sub1, fixing a different temporary-tuple leak; it never touched the first parse loop, so this leak remains on master.

views.h is byte-identical between v6.6.0 and v6.7.0, yet 6.6.4 does not leak and 6.7.0 does. The regression is behavioural, introduced by a key-identity ownership change in hashtable.h across that boundary that made md_calc_identity() return a fresh strong reference the unchanged parse loop never releases.


Reproduction Environment

| Item | Value | |------|-------| | Runtime | CPython 3.14.6 | | multidict | 6.9.0 (PyPI binary wheel, C extension) | | OS | macOS (darwin 25.6.0, arm64) |


Proof of Concept

POC Source Code

poc_refcount.py — leak proof with intersection control

import sys, gc
from multidict import CIMultiDict

def probe(name, run_op):
    d = CIMultiDict(); d["seed"] = "x"
    value = object()                 # unique sentinel
    N = 100000
    operand = [("k%d" % i, value) for i in range(N)]
    gc.collect(); before = sys.getrefcount(value)
    run_op(d, operand)
    gc.collect(); after = sys.getrefcount(value)
    print(f"[{name}] leaked strong refs = {after - before}")

probe("or2  operand | items", lambda d, o: o | d.items())    # reflected union
probe("sub1 items - operand", lambda d, o: d.items() - o)    # subtraction
probe("and2 items & operand", lambda d, o: d.items() & o)    # CONTROL: clears, expect 0

poc_dispatch.py — maps which paths leak

import sys, gc
from multidict import CIMultiDict

def probe(name, fn):
    d = CIMultiDict(); d["seed"] = "x"
    v = object(); N = 50000
    operand = [("k%d" % i, v) for i in range(N)]
    gc.collect(); before = sys.getrefcount(v)
    fn(d, operand); gc.collect(); after = sys.getrefcount(v)
    print(f"{name:35s} leaked={after - before}")

probe("operand | d.items()  (or2)",…
Record
GHSA-54p9-h82j-f925
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-401
Updated
2026-11-02
NVD 5.3

Multidict is an implementation of a multidict data structure. From 6.7.0 until 6.9.1, the C extension's items-view reflected union operation, operand | d.items(), in multidict_itemsview_or2_impl and subtraction operation, d.items() - operand, in multidict_itemsview_sub1_impl fail to release new key-identity and value references returned for each operand element. Applications that perform these operations over attacker-influenced sequences can leak two strong references per element, and garbage collection cannot reclaim them, so repeated operations can cause unbounded process memory growth and denial of service. Forward union, intersection, non-tuple operand elements, and pure-Python builds are not affected by this reference leak. This issue is fixed in version 6.9.1.

Record
CVE-2026-104874
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-401
Updated
2026-11-02
OSV 5.3

Multidict: Reference leak in CIMultiDict/MultiDict items-view union and subtraction

Multidict is an implementation of a multidict data structure. From 6.7.0 until 6.9.1, the C extension's items-view reflected union operation, operand | d.items(), in multidict_itemsview_or2_impl and subtraction operation, d.items() - operand, in multidict_itemsview_sub1_impl fail to release new key-identity and value references returned for each operand element. Applications that perform these operations over attacker-influenced sequences can leak two strong references per element, and garbage collection cannot reclaim them, so repeated operations can cause unbounded process memory growth and denial of service. Forward union, intersection, non-tuple operand elements, and pure-Python builds are not affected by this reference leak. This issue is fixed in version 6.9.1.

Record
CVE-2026-104874
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-401
Updated
2026-11-02
OSVGHSA-54p9-h82j-f925 5.3

Multidict: Reference leak in CIMultiDict/MultiDict items-view union and subtraction

Description

A reference leak in the items-view union and subtraction operators of aio-libs/multidict 6.7.0 through 6.9.0 (C extension) lets a remote client drive unbounded, unreclaimable memory growth by having each operand element leak one key-identity object and one value object. The reflected-union path (operand | d.items(), multidict_itemsview_or2_impl) and the subtraction path (d.items() - operand, multidict_itemsview_sub1_impl) parse each element into new strong references but release only the tuple wrapper, never the identity and value. Servers in the aio-libs stack build these views over attacker-supplied HTTP headers and query strings, so the operand size is under remote control. Forced garbage collection does not recover the leaked objects, so resident memory rises monotonically until the process is killed.


Root Cause

_multidict_itemsview_parse_item() returns new references: a fresh identity via md_calc_identity() and a fresh value via Py_NewRef(). The or2_impl first parse loop requests both but clears only arg:

// views.h:565 — or2_impl first parse loop
while ((st = PyIter_NextItem(iter, &arg)) > 0) {
    int tmp = _multidict_itemsview_parse_item(
        self, arg, &identity, NULL, &value);   // identity + value: new refs
    if (tmp < 0) goto fail;
    else if (tmp > 0) {
        if (_set_add(tmp_set, identity, value) < 0) goto fail;
    }
    Py_CLEAR(arg);                             // :575 clears arg ONLY
}

_set_add() builds its own tuple with PyTuple_Pack and Py_DECREFs it, so it never borrows the loop's identity/value. On the next iteration those variables are overwritten, so the previous references are lost permanently. The sub1_impl first loop (:686-696) has the identical omission.

This is an editing slip, not an ownership contract: every sibling path clears all three references per iteration, and1_impl (:304-306), and2_impl (:389-391), or1_impl (:506-508), and the second (md_next) loops of both functions (:605-607, :726-728). PR #1413 added a Py_DECREF(tpl) to the second loop of or2/sub1, fixing a different temporary-tuple leak; it never touched the first parse loop, so this leak remains on master.

views.h is byte-identical between v6.6.0 and v6.7.0, yet 6.6.4 does not leak and 6.7.0 does. The regression is behavioural, introduced by a key-identity ownership change in hashtable.h across that boundary that made md_calc_identity() return a fresh strong reference the unchanged parse loop never releases.


Reproduction Environment

| Item | Value | |------|-------| | Runtime | CPython 3.14.6 | | multidict | 6.9.0 (PyPI binary wheel, C extension) | | OS | macOS (darwin 25.6.0, arm64) |


Proof of Concept

POC Source Code

poc_refcount.py — leak proof with intersection control

import sys, gc
from multidict import CIMultiDict

def probe(name, run_op):
    d = CIMultiDict(); d["seed"] = "x"
    value = object()                 # unique sentinel
    N = 100000
    operand = [("k%d" % i, value) for i in range(N)]
    gc.collect(); before = sys.getrefcount(value)
    run_op(d, operand)
    gc.collect(); after = sys.getrefcount(value)
    print(f"[{name}] leaked strong refs = {after - before}")

probe("or2  operand | items", lambda d, o: o | d.items())    # reflected union
probe("sub1 items - operand", lambda d, o: d.items() - o)    # subtraction
probe("and2 items & operand", lambda d, o: d.items() & o)    # CONTROL: clears, expect 0

poc_dispatch.py — maps which paths leak

import sys, gc
from multidict import CIMultiDict

def probe(name, fn):
    d = CIMultiDict(); d["seed"] = "x"
    v = object(); N = 50000
    operand = [("k%d" % i, v) for i in range(N)]
    gc.collect(); before = sys.getrefcount(v)
    fn(d, operand); gc.collect(); after = sys.getrefcount(v)
    print(f"{name:35s} leaked={after - before}")

probe("operand | d.items()  (or2)",…
Record
GHSA-54p9-h82j-f925
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-401
Updated
2026-11-02
OSVPYSEC-2026-4244 5.3

Multidict: Reference leak in CIMultiDict/MultiDict items-view union and subtraction

Description

A reference leak in the items-view union and subtraction operators of aio-libs/multidict 6.7.0 through 6.9.0 (C extension) lets a remote client drive unbounded, unreclaimable memory growth by having each operand element leak one key-identity object and one value object. The reflected-union path (operand | d.items(), multidict_itemsview_or2_impl) and the subtraction path (d.items() - operand, multidict_itemsview_sub1_impl) parse each element into new strong references but release only the tuple wrapper, never the identity and value. Servers in the aio-libs stack build these views over attacker-supplied HTTP headers and query strings, so the operand size is under remote control. Forced garbage collection does not recover the leaked objects, so resident memory rises monotonically until the process is killed.


Root Cause

_multidict_itemsview_parse_item() returns new references: a fresh identity via md_calc_identity() and a fresh value via Py_NewRef(). The or2_impl first parse loop requests both but clears only arg:

// views.h:565 — or2_impl first parse loop
while ((st = PyIter_NextItem(iter, &arg)) > 0) {
    int tmp = _multidict_itemsview_parse_item(
        self, arg, &identity, NULL, &value);   // identity + value: new refs
    if (tmp < 0) goto fail;
    else if (tmp > 0) {
        if (_set_add(tmp_set, identity, value) < 0) goto fail;
    }
    Py_CLEAR(arg);                             // :575 clears arg ONLY
}

_set_add() builds its own tuple with PyTuple_Pack and Py_DECREFs it, so it never borrows the loop's identity/value. On the next iteration those variables are overwritten, so the previous references are lost permanently. The sub1_impl first loop (:686-696) has the identical omission.

This is an editing slip, not an ownership contract: every sibling path clears all three references per iteration, and1_impl (:304-306), and2_impl (:389-391), or1_impl (:506-508), and the second (md_next) loops of both functions (:605-607, :726-728). PR #1413 added a Py_DECREF(tpl) to the second loop of or2/sub1, fixing a different temporary-tuple leak; it never touched the first parse loop, so this leak remains on master.

views.h is byte-identical between v6.6.0 and v6.7.0, yet 6.6.4 does not leak and 6.7.0 does. The regression is behavioural, introduced by a key-identity ownership change in hashtable.h across that boundary that made md_calc_identity() return a fresh strong reference the unchanged parse loop never releases.


Reproduction Environment

| Item | Value | |------|-------| | Runtime | CPython 3.14.6 | | multidict | 6.9.0 (PyPI binary wheel, C extension) | | OS | macOS (darwin 25.6.0, arm64) |


Proof of Concept

POC Source Code

poc_refcount.py — leak proof with intersection control

import sys, gc
from multidict import CIMultiDict

def probe(name, run_op):
    d = CIMultiDict(); d["seed"] = "x"
    value = object()                 # unique sentinel
    N = 100000
    operand = [("k%d" % i, value) for i in range(N)]
    gc.collect(); before = sys.getrefcount(value)
    run_op(d, operand)
    gc.collect(); after = sys.getrefcount(value)
    print(f"[{name}] leaked strong refs = {after - before}")

probe("or2  operand | items", lambda d, o: o | d.items())    # reflected union
probe("sub1 items - operand", lambda d, o: d.items() - o)    # subtraction
probe("and2 items & operand", lambda d, o: d.items() & o)    # CONTROL: clears, expect 0

poc_dispatch.py — maps which paths leak

import sys, gc
from multidict import CIMultiDict

def probe(name, fn):
    d = CIMultiDict(); d["seed"] = "x"
    v = object(); N = 50000
    operand = [("k%d" % i, v) for i in range(N)]
    gc.collect(); before = sys.getrefcount(v)
    fn(d, operand); gc.collect(); after = sys.getrefcount(v)
    print(f"{name:35s} leaked={after - before}")

probe("operand | d.items()  (or2)",…
Record
PYSEC-2026-4244
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Updated
2026-11-02
OSVUBUNTU-CVE-2026-104874 5.3

Multidict is an implementation of a multidict data structure. From 6.7.0 until 6.9.1, the C extension's items-view reflected union operation, operand | d.items(), in multidict_itemsview_or2_impl and subtraction operation, d.items() - operand, in multidict_itemsview_sub1_impl fail to release new key-identity and value references returned for each operand element. Applications that perform these operations over attacker-influenced sequences can leak two strong references per element, and garbage collection cannot reclaim them, so repeated operations can cause unbounded process memory growth and denial of service. Forward union, intersection, non-tuple operand elements, and pure-Python builds are not affected by this reference leak. This issue is fixed in version 6.9.1.

Record
UBUNTU-CVE-2026-104874
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Updated
2026-11-02
OSVopenSUSE-SU-2026:12117-1 No score

python313-multidict-7.0.0-1.1 on GA media

These are all security issues fixed in the python313-multidict-7.0.0-1.1 package on the GA media of openSUSE Tumbleweed.

Record
openSUSE-SU-2026:12117-1
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
multidict
PyPI
6.7.0 to 6.9.0
6.9.1
multidict
PyPI
6.7.0 up to 6.9.1, plus listed versions
6.7.16.8.06.9.0
6.9.1
python-multidict
Ubuntu:18.04:LTS
All versions, plus listed versions
3.1.3-1build13.2.0-13.3.2-14.1.0-1
None known
python-multidict
Ubuntu:20.04:LTS
All versions, plus listed versions
4.5.2-14.6.1-14.7.3-14.7.3-1build1
None known
python-multidict
Ubuntu:22.04:LTS
All versions, plus listed versions
5.1.0-15.1.0-1build15.1.0-1build2
None known
python-multidict
Ubuntu:24.04:LTS
All versions, plus listed versions
6.0.4-1build16.0.4-1.16.0.4-1.1build1
None known
python-multidict
Ubuntu:26.10
All versions, plus listed versions
6.4.3-1build26.4.3-2
None known
python-multidict
Ubuntu:26.04:LTS
All versions, plus listed versions
6.4.3-16.4.3-1build16.4.3-1build2
None known
python-multidict
openSUSE:Tumbleweed
before 7.0.0-1.1
7.0.0-1.1

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-104874 is a medium vulnerability (CVSS 5.3) in Aio-libs Multidict, published October 2, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.45%, the 39th percentile. Fixed in multidict 6.9.1.

References

Show 5 moreShow less
This page doesn't list everything

Left out here: the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open