Skip to content

VNX-910: Use of Expired File Descriptor

A C or C++ file descriptor, socket or FILE stream is read, written, queried or closed again after close/fclose in the same function, with no reassignment or exit in between.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-910
Severity Low
SARIF level note
Kind sast
Languages C, C++
CWE CWE-910
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 use-of-expired-file-descriptor cwe-910 use-after-close double-close
Source internal/sast/rules/vnx-910.rego

Overview

VNX-910 maps to CWE-910: Use of Expired File Descriptor. After close(fd), ::close(fd), closesocket(s), fclose(fp) or pclose(fp) (including a close on a single-line if), it reports a later line within 25 lines that passes the same handle (fd, conn->fd, sock_, fp) to a descriptor or stream call:

  • descriptors: read, write, send, recv, fstat, fsync, lseek, ioctl, fcntl, dup, mmap, setsockopt, shutdown, accept … and close again (double close)
  • streams: fprintf, fwrite, fread, fgets, fputs, fflush, fseek, fileno … and fclose again

Not reported: the handle reassigned in between (fd = -1, fd = open(...), fp = NULL) or on the use line before it is used (if ((fd = open(p, O_RDONLY)) < 0 || fstat(fd, &st))), a close followed by return/goto/break/continue/exit before the use, a use in the else branch of the close, a goto label in between, the close and the use in different functions, other handles (pipefd[0] vs pipefd[1]), comments and strings. A close reached again on the next loop iteration, and closes through a wrapper function, are not followed.

Why This Matters

The kernel reuses the lowest free descriptor number. Once fd is closed, the next open, socket or accept anywhere in the process (another thread included) can receive the same number, so a write(fd, ...) after the close writes into someone else’s file or socket and a second close(fd) closes a descriptor that another part of the program is still using. A FILE * after fclose is freed memory.

What Gets Flagged

write(fd, data, n);
close(fd);
return fsync(fd);                     // FLAGGED

if (write(fd, buf, n) < 0) {
    close(fd);
}
close(fd);                            // FLAGGED (double close on the error path)

fclose(log);
fprintf(log, "wrote %d rows\n", rows); // FLAGGED

Not flagged: close(fd); fd = open(path, O_WRONLY); write(fd, ...), if (fstat(fd, &st) < 0) { close(fd); return -1; } read(fd, ...), close(c->fd); c->fd = -1;.

Remediation

  1. Set the handle to -1 (or NULL for a stream) immediately after closing it, and check it before every use.
  2. Give each descriptor one owner that closes it exactly once, on every path.
  3. In C++, wrap descriptors in an RAII type (unique_fd, a small class with a destructor) instead of closing by hand.

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

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

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