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.
| 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… andcloseagain (double close) - streams:
fprintf,fwrite,fread,fgets,fputs,fflush,fseek,fileno… andfcloseagain
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
- Set the handle to
-1(orNULLfor a stream) immediately after closing it, and check it before every use. - Give each descriptor one owner that closes it exactly once, on every path.
- In C++, wrap descriptors in an RAII type (
unique_fd, a small class with a destructor) instead of closing by hand.
References
- CWE-910: Use of Expired File Descriptor
- CWE-1341: Multiple Releases of Same Resource or Handle
- SEI CERT FIO46-C: Do not access a closed file
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.