Race conditions between checking and using a file

Race conditions between checking and using a file

Description

Checking whether a filename exists or is accessible, then reopening or writing the same path, leaves time for an attacker to replace the file. In temporary or shared directories, a symbolic link or a file created first by another process may cause an unintended file to be overwritten.

Potential impact

  • An attacker may replace the checked path with a symbolic link, causing a sensitive file to be overwritten.
  • Another process may create the file between the existence check and file creation.
  • The file state may change after a read-access check, leading to information disclosure or an authorization bypass.

Remediation

  • Use file-descriptor APIs instead of checking the same filename again.
  • Create new files atomically with flags such as O_CREAT | O_EXCL.
  • Create temporary files with dedicated APIs in private directories.

Examples

Before

javascript
const fs = require("fs");
const os = require("os");
const path = require("path");

const targetPath = path.join(os.tmpdir(), "report.txt");
if (!fs.existsSync(targetPath)) {
  fs.writeFileSync(targetPath, "report");
}

After

javascript
const fs = require("fs");
const os = require("os");
const path = require("path");

const targetPath = path.join(os.tmpdir(), "report.txt");
const fd = fs.openSync(
  targetPath,
  fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY,
  0o600,
);
try {
  fs.writeFileSync(fd, "report");
} finally {
  fs.closeSync(fd);
}

Explanation:

  • Before: The target path can change between existsSync and writeFileSync.
  • After: Atomic creation flags combine the check and creation in one filesystem operation. An existing path causes the operation to fail, so the calling code must handle that error.

References