Skip to content

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).

Updated 02 Nov 2026Checked against cli@de9b52a
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:

  1. Netty (Java). A channelRead(ChannelHandlerContext, Object msg) override that consumes the message without releasing it or passing it on: no ReferenceCountUtil.release/safeRelease, msg.release(), retain, ctx.fireChannelRead/write/writeAndFlush, super.channelRead, out.add, and no call that takes msg or the ByteBuf cast from it.
  2. Unsynchronized count (Java). A primitive int/long field named as a reference count (refCount, refCnt, referenceCount, useCount, retainCount …) changed with ++, --, += or -= in a method that is not synchronized, outside a synchronized block and without a lock() above it in the method. volatile does not make ++ atomic.
  3. CPython C/C++ extensions. return Py_None (or Py_True, Py_False, Py_NotImplemented, Py_Ellipsis) without Py_INCREF of it just above, and Py_DECREF/Py_XDECREF/Py_CLEAR of a borrowed reference (from PyList_GetItem, PyTuple_GetItem, PyDict_GetItem/GetItemString/GetItemWithError, PySequence_Fast_GET_ITEM, PyModule_GetDict, PySys_GetObject …) within 30 lines, with no Py_INCREF/Py_NewRef of 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

  1. In Netty, release every message you consume (try { ... } finally { ReferenceCountUtil.release(msg); }), forward the ones you do not, or extend SimpleChannelInboundHandler.
  2. Keep Java reference counts in AtomicInteger/AtomicLong (or an AtomicIntegerFieldUpdater) and act on the value decrementAndGet() returns.
  3. In CPython extensions use Py_RETURN_NONE/Py_RETURN_TRUE/Py_RETURN_FALSE or Py_NewRef, and never Py_DECREF a borrowed reference you did not Py_INCREF.

References

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.

Edit this pageSources: cli/internal/sast/rules/vnx-911.rego, cli/internal/sast/docs/vnx-911.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.