Skip to content

VNX-832: Unlock of a Resource that is not Locked

Reports a lock release that runs on a path where the lock may not be held: Java unlock() after a discarded tryLock(), in a finally whose try acquires with lockInterruptibly()/tryLock(), or of a lock the file never acquires; Python release() after a discarded non-blocking acquire(); pthread unlock after a discarded trylock; C++ std::mutex unlock() after a discarded try_lock().

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-832
Severity Low
SARIF level note
Kind sast
Languages Java, Python, C, C++
CWE CWE-832
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L
Tags unlock-not-locked cwe-832 concurrency locking
Source internal/sast/rules/vnx-832.rego

Overview

VNX-832 maps to CWE-832: Unlock of a Resource that is not Locked. It reports the release line in these shapes:

  • Java (java.util.concurrent locks, including rw.readLock()/writeLock()):
    1. unlock() of a lock whose tryLock()/tryLock(timeout) result was discarded as a bare statement in the 40 lines above;
    2. unlock() in a finally block whose try acquires the same lock with lockInterruptibly() or tryLock(), so an interrupt, a failed tryLock or the early return after it still reaches the unlock;
    3. unlock() of a lock field or variable the file declares but never acquires and never passes to other code.
  • Python (threading): release() after a bare acquire(blocking=False), acquire(False) or acquire(timeout=...) whose result was discarded.
  • C/C++ (pthreads): pthread_mutex_unlock/pthread_rwlock_unlock after a bare pthread_mutex_trylock, pthread_mutex_timedlock or pthread_rwlock_try*lock/timed*lock whose return value was discarded. C++: m.unlock() after a bare m.try_lock(), try_lock_for() or try_lock_until() whose result was discarded.

Not reported: lock() before try with unlock() in finally; if (lock.tryLock()) { try ... finally unlock }; an unlock guarded by an if on the same or the preceding line (if (acquired), isHeldByCurrentThread()); a tryLock/acquire whose result is assigned or tested; blocking acquisition; begin/end method pairs in the same file; lock parameters, locks handed to another object or returned from a getter; comments and string literals. Double unlocks are VNX-765.

Why This Matters

Releasing a lock the thread does not hold throws IllegalMonitorStateException in Java (often from a finally, where it replaces the original exception), raises RuntimeError in Python, and is undefined behaviour for a default pthread mutex. Worse, the code between the failed acquisition and the release ran without the lock, so the data it protects was modified concurrently.

What Gets Flagged

lock.tryLock();
try {
    reload();
} finally {
    lock.unlock();              // FLAGGED: tryLock() result ignored
}

try {
    lock.lockInterruptibly();
    data.put(k, v);
} finally {
    lock.unlock();              // FLAGGED: interrupt before acquisition still unlocks
}
self.lock.acquire(blocking=False)
try:
    self._tick()
finally:
    self.lock.release()         # FLAGGED

Remediation

  1. Acquire before try: lock.lock(); try { ... } finally { lock.unlock(); }.
  2. Test the result of non-blocking or timed acquisition and release only when it succeeded: if (lock.tryLock()) { try { ... } finally { lock.unlock(); } }.
  3. In Python prefer with lock:; in C check pthread_mutex_trylock(&m) == 0 before unlocking.

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-832
// nosec vnx-832

Or record an ignore rule: vulnetix ignore add --rule vnx-832 --reason "...". See Suppressing findings.

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