VNX-769: Uncontrolled File Descriptor Consumption
Reports Python loops and comprehensions that open a new file or socket handle per element and keep it open, which exhausts the process's descriptor limit on large input.
| Field | Value |
|---|---|
| Rule ID | VNX-769 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | Python |
| CWE | CWE-769 |
| 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 | file-descriptor-exhaustion cwe-769 |
| Source | internal/sast/rules/vnx-769.rego |
Overview
VNX-769 maps to CWE-769: Uncontrolled File Descriptor Consumption (deprecated by MITRE in favour of CWE-774; the number is kept for rule identity). It reports Python code that opens one handle per element of its input and keeps it:
- a handle assigned inside a
for/whilebody (fh = open(p),io.open,codecs.open,gzip.open,bz2.open,lzma.open,tarfile.open,os.open,socket.socket(...),socket.create_connection(...)) that the loop body never closes (fh.close(),os.close(fd),with fh,contextlib.closing(fh)), returns or yields; - handles appended to a collection in a loop (
handles.append(open(p))) with no close in the following lines; - a list, set or dict comprehension that opens a handle per element (
[open(p) for p in paths]).
Not reported: with blocks, handles closed or yielded in the loop, handles chained straight into a read (open(p).read()), generator expressions, handles opened outside a loop (VNX-775 reports a missing close there), comments, string literals and test files.
Why This Matters
Every open handle consumes a file descriptor. When the number of iterations is controlled by input (uploaded archives, a list of hosts, a directory listing), enough elements exhaust the per-process limit and every later open, accept or connect fails with EMFILE.
What Gets Flagged
for path in paths:
fh = open(path, encoding="utf-8") # flagged
records.append(json.load(fh))
files = [open(s) for s in shards] # flagged
Not flagged:
for path in paths:
with open(path, encoding="utf-8") as fh:
records.append(json.load(fh))
Remediation
- Open handles in a
withblock inside the loop so each one closes before the next opens. - When handles must be held together (a k-way merge), bound how many are open at once and close them through
contextlib.ExitStack.
References
- CWE-769: Uncontrolled File Descriptor Consumption
- CWE-774: Allocation of File Descriptors or Handles Without Limits or Throttling
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-769
// nosec vnx-769
Or record an ignore rule: vulnetix ignore add --rule vnx-769 --reason "...". See Suppressing findings.