VNX-911: Improper Update of Reference Count
A Netty channelRead that neither releases nor forwards its message, a Java reference-count field updated with ++/-- outside synchronization, or a CPython extension that returns Py_None without Py_INCREF or releases a borrowed reference (including one PyArg_ParseTuple stored).
| Field | Value |
|---|---|
| Rule ID | VNX-911 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | C, C++, Java |
| CWE | CWE-911 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | reference-count cwe-911 netty cpython race-condition |
| Source | internal/sast/rules/vnx-911.rego |
Overview
VNX-911 maps to CWE-911: Improper Update of Reference Count. It reports three concrete shapes:
- Netty (Java). A
channelRead(ChannelHandlerContext, Object msg)override that consumes the message without releasing it or passing it on: noReferenceCountUtil.release/safeRelease,msg.release(),retain,ctx.fireChannelRead/write/writeAndFlush,super.channelRead,out.add, and no call that takesmsgor theByteBufcast from it. - Unsynchronized count (Java). A primitive
int/longfield named as a reference count (refCount,refCnt,referenceCount,useCount,retainCount…) changed with++,--,+=or-=in a method that is notsynchronized, outside asynchronizedblock and without alock()above it in the method.volatiledoes not make++atomic. - CPython C/C++ extensions.
return Py_None(orPy_True,Py_False,Py_NotImplemented,Py_Ellipsis) withoutPy_INCREFof it just above, andPy_DECREF/Py_XDECREF/Py_CLEARof a borrowed reference (fromPyList_GetItem,PyTuple_GetItem,PyDict_GetItem/GetItemString/GetItemWithError,PySequence_Fast_GET_ITEM,PyModule_GetDict,PySys_GetObject…) within 30 lines, with noPy_INCREF/Py_NewRefof it or reassignment in between.
Writing a different buffer back as a reply, or logging the message, does not count as releasing it. Not reported: SimpleChannelInboundHandler.channelRead0 (Netty releases the message), Atomic* counters, synchronized or locked updates, constructors, new references (PyObject_Str, PyObject_GetAttrString …) released with Py_DECREF, test sources, comments and strings. Counts managed through helper methods or across files are not followed.
Why This Matters
A count that is too high leaks the resource: an unreleased pooled Netty buffer is gone from the pool for good, and a busy server runs out of direct memory. A count that is too low frees the resource while something still uses it: a borrowed Python object released by the extension is deallocated while its list still points at it (use-after-free), and a lost ++ under contention frees a shared object early.
What Gets Flagged
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // FLAGGED
ByteBuf in = (ByteBuf) msg;
bytes += in.readableBytes();
}
public SharedBuffer retain() {
refCount++; // FLAGGED
return this;
}
static PyObject *spam_reset(PyObject *self, PyObject *args)
{
return Py_None; // FLAGGED
}
PyObject *item = PyList_GetItem(list, 0);
PyObject *s = PyObject_Str(item);
Py_DECREF(item); // FLAGGED (borrowed)
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj)) return NULL;
Py_DECREF(obj); // FLAGGED (borrowed from args)
Not flagged: ReferenceCountUtil.release(msg) in a finally, ctx.fireChannelRead(msg), public synchronized void retain() { refCount++; }, references.decrementAndGet(), Py_RETURN_NONE;, Py_INCREF(item) before the Py_DECREF(item).
Remediation
- In Netty, release every message you consume (
try { ... } finally { ReferenceCountUtil.release(msg); }), forward the ones you do not, or extendSimpleChannelInboundHandler. - Keep Java reference counts in
AtomicInteger/AtomicLong(or anAtomicIntegerFieldUpdater) and act on the valuedecrementAndGet()returns. - In CPython extensions use
Py_RETURN_NONE/Py_RETURN_TRUE/Py_RETURN_FALSEorPy_NewRef, and neverPy_DECREFa borrowed reference you did notPy_INCREF.
References
- CWE-911: Improper Update of Reference Count
- Netty: Reference counted objects
- Python C API: Reference Counting
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-911
// nosec vnx-911
Or record an ignore rule: vulnetix ignore add --rule vnx-911 --reason "...". See Suppressing findings.