VNX-775: Missing Release of File Descriptor or Handle after Effective Lifetime
Reports Python functions that assign a file or socket handle to a local name and never close it, return it or hand it off in that function.
| Field | Value |
|---|---|
| Rule ID | VNX-775 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | Python |
| CWE | CWE-775 |
| 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 | handle-leak cwe-775 |
| Source | internal/sast/rules/vnx-775.rego |
Overview
VNX-775 maps to CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime. It reports a Python function that assigns a handle to a local name (x = open(...), io.open, codecs.open, gzip.open, bz2.open, lzma.open, tarfile.open, os.open, socket.socket(...), socket.create_connection(...)) and never releases it in that function: no x.close(), os.close(x), with x, contextlib.closing(x) or enter_context(x).
Not reported: handles opened in a with statement; handles the function returns, yields or wraps (return x, yield from x, return Wrapper(x), ctx.wrap_socket(x)); webbrowser.open(url), which returns a flag rather than a handle; handles stored on an object or in a collection (self.sock = ..., self.files.append(x)); handles chained straight into a read (open(p).read()); module-level handles (program lifetime); handles opened inside a loop (VNX-769); comments, string literals and test files.
Why This Matters
The descriptor stays open until the garbage collector finalises the object, which is not immediate on PyPy, is delayed by reference cycles and tracebacks held by an exception handler, and never happens for the integer os.open returns. A request handler that leaks one handle per call eventually fails every request with EMFILE.
What Gets Flagged
def fetch_banner(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # flagged
s.connect((host, port))
return s.recv(1024).decode()
def load_config(path):
fh = open(path, encoding="utf-8") # flagged
return json.load(fh)
Not flagged:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
def make_socket(host, port):
s = socket.socket()
s.connect((host, port))
return s
Remediation
Open handles with with, or close them in a finally block; use contextlib.closing for objects that only have close().
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-775
// nosec vnx-775
Or record an ignore rule: vulnetix ignore add --rule vnx-775 --reason "...". See Suppressing findings.