mjbommar · GitHub

Bug report

Bug description:

Summary

Modules/_zstd/decompressor.c is missing a Py_DECREF(value) before
the early return when PyLong_AsInt(key) fails. The identical code in
Modules/_zstd/compressor.c has the fix.

Likely easy to miss other than very long running services that slowly
leak.

decompressor.c (buggy):

Py_INCREF(key);                          // line 109
Py_INCREF(value);                        // line 110
int key_v = PyLong_AsInt(key);           // line 111
Py_DECREF(key);                          // line 112
if (key_v == -1 && PyErr_Occurred()) {
    return -1;                           // line 114 -- leaks value
}

compressor.c (correct):

Py_INCREF(key);                          // line 150
Py_INCREF(value);                        // line 151
int key_v = PyLong_AsInt(key);           // line 152
Py_DECREF(key);                          // line 153
if (key_v == -1 && PyErr_Occurred()) {
    Py_DECREF(value);                    // line 158 -- present here
    return -1;
}

Reproducer

import _zstd, sys, gc
class Marker:
    pass
val = Marker()
baseline = sys.getrefcount(val)
for _ in range(100):
    try:
        _zstd.ZstdDecompressor(options={"bad_key": val})
    except Exception:
        pass
gc.collect()
after = sys.getrefcount(val)
print(f"baseline refcount: {baseline}")
print(f"after 100 error triggers: {after}")
print(f"leaked refs: {after - baseline}")

Output:

baseline refcount: 2
after 100 error triggers: 102
leaked refs: 100

Each call leaks exactly one reference to value.

Related

Proposed Fix

Just add Py_DECREF(value); before return -1 at decompressor.c line 114. Happy to submit that as a PR if you want.

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Linked PRs

Read the original on github.com ↗