fs: cpSync copyDir fast path can terminate on filesystem errors

Đang mở
#63,970 1 bình luận 2 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
64/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Ít trao đổi
Công nghệ
cpp, javascript, node.js
Lĩnh vực
backend, operating-systems

Hướng nghiên cứu

Tái hiện lỗi Windows bằng script trong issue, sau đó kiểm tra src/node_file.cc và phần triển khai CpSyncCopyDir. Theo dõi quá trình lặp qua thư mục, chuẩn hóa đường dẫn và kiểm tra loại tệp, đồng thời xác minh rằng các lỗi của hệ thống tệp trở thành các ngoại lệ JavaScript có thể bắt được thay vì khiến tiến trình kết thúc.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

fs
Version

v22.17.0 and later; verified against v27.0.0-pre built from current main.

Platform
Microsoft Windows NT 10.0.26100.0 x64
Subsystem

fs

What steps will reproduce the bug?

Run this script on Windows:

'use strict';

const assert = require('node:assert');
const { execFileSync, spawnSync } = require('node:child_process');
const {
  cpSync,
  existsSync,
  mkdirSync,
  rmSync,
  writeFileSync,
} = require('node:fs');
const { tmpdir } = require('node:os');
const { join } = require('node:path');

function run(command, args) {
  return execFileSync(command, args, {
    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
  });
}

function currentWindowsUser() {
  return run('whoami', []).trim();
}

function restrictDirectory(dir) {
  run('icacls', [dir, '/deny', `${currentWindowsUser()}:(OI)(CI)(RX)`]);
}

function restoreDirectory(dir) {
  if (existsSync(dir)) {
    run('icacls', [dir, '/remove:d', currentWindowsUser()]);
  }
}

if (process.argv[2] === 'child') {
  assert.throws(() => {
    cpSync(process.argv[3], process.argv[4], { recursive: true });
  });
  process.exit(0);
}

const root = join(tmpdir(), `node-cpsync-${process.pid}`);
const src = join(root, 'src');
const dest = join(root, 'dest');
const restrictedDir = join(src, 'restricted');

mkdirSync(restrictedDir, { recursive: true });
writeFileSync(join(src, 'readable.txt'), 'readable\n');
writeFileSync(join(restrictedDir, 'blocked.txt'), 'blocked\n');

restrictDirectory(restrictedDir);

try {
  const child = spawnSync(process.execPath, [__filename, 'child', src, dest], {
    encoding: 'utf8',
  });
  console.log({
    status: child.status,
    signal: child.signal,
    stdout: child.stdout,
    stderr: child.stderr,
  });
} finally {
  restoreDirectory(restrictedDir);
  rmSync(root, { recursive: true, force: true });
}

The important part is that fs.cpSync(src, dest, { recursive: true }) is called without a filter option, so it uses the native copyDir fast path.

How often does it reproduce? Is there a required condition?

It reproduces when the native fs.cpSync() copyDir fast path encounters a filesystem error during directory iteration, path canonicalization, or file type checks.

The fast path is used for recursive directory copies when no filter option is provided. Passing filter: () => true avoids this path and falls back to the JavaScript implementation.

What is the expected behavior? Why is that the expected behavior?

fs.cpSync() should report the filesystem failure as a JavaScript exception that callers can catch with try/catch or assert.throws().

Filesystem APIs should convert native filesystem failures into JavaScript errors instead of terminating the process.

What do you see instead?

Some std::filesystem calls in the native copyDir implementation use throwing overloads. When those operations fail, the C++ exception can bypass Node's normal error conversion.

Instead of a catchable JavaScript exception, the process can terminate in the native layer.

Additional information

This appears to come from src/node_file.cc's CpSyncCopyDir implementation. Some calls already use std::error_code, but others use throwing std::filesystem overloads, including directory iteration and file type/path checks.

The fix is to use non-throwing std::filesystem overloads throughout the copyDir path and convert each failure with ThrowStdErrException.

Ngôn ngữ chính
JavaScript
Star
122k
Fork
37.4k
Merge trung bình
4 ngày 4 giờ
Pull request đã merge (30 ngày)
276

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của nodejs/node

Tất cả issue của nodejs/node

Issue tương tự

Thêm issue về JavaScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.