Skip to content

Commit f38768f

Browse files
christianaurichzmaduh95
authored andcommitted
fs: close fd 0 on discarded FileHandle transfer
`FileHandle::TransferData` uses `-1` to indicate that it no longer owns a file descriptor. However, its destructor only closes descriptors greater than 0. If a transferred `FileHandle` owns fd 0 and the message is discarded, the descriptor is left open. Treat fd 0 like any other valid descriptor and only skip `-1`. Signed-off-by: Christian Aurich <christian.aurichzm@gmail.com> PR-URL: #66095 Reviewed-By: Xuguang Mei <meixuguang@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 46a8a5d commit f38768f

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

‎src/node_file.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ std::unique_ptr<worker::TransferData> FileHandle::TransferForMessaging() {
328328
FileHandle::TransferData::TransferData(int fd) : fd_(fd) {}
329329

330330
FileHandle::TransferData::~TransferData() {
331-
if (fd_ > 0) {
331+
if (fd_ >= 0) {
332332
uv_fs_t close_req;
333333
CHECK_NE(fd_, -1);
334334
FS_SYNC_TRACE_BEGIN(close);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
// A FileHandle whose transfer is discarded has to close its file descriptor.
4+
// Descriptor 0 is an ordinary descriptor once stdin has been closed, so the
5+
// scenario runs in a child process that can afford to lose its stdin.
6+
7+
const common = require('../common');
8+
9+
if (common.isWindows)
10+
common.skip('descriptor numbering after closing stdin is POSIX-specific');
11+
12+
const assert = require('assert');
13+
const fs = require('fs');
14+
15+
if (process.argv[2] === 'child') {
16+
const vm = require('vm');
17+
const { MessageChannel, moveMessagePortToContext } =
18+
require('worker_threads');
19+
20+
(async function() {
21+
fs.closeSync(0);
22+
const fh = await fs.promises.open(__filename);
23+
assert.strictEqual(fh.fd, 0);
24+
25+
const { port1, port2 } = new MessageChannel();
26+
// A port living in another context cannot receive the handle, so the
27+
// message is discarded on delivery and takes the descriptor with it.
28+
const moved = moveMessagePortToContext(port2, vm.createContext());
29+
const discarded = new Promise((resolve) => {
30+
moved.onmessageerror = resolve;
31+
});
32+
moved.start();
33+
34+
port1.postMessage(fh, [ fh ]);
35+
await discarded;
36+
37+
assert.throws(() => fs.fstatSync(0), { code: 'EBADF' });
38+
port1.close();
39+
})().then(common.mustCall());
40+
return;
41+
}
42+
43+
const { spawnSync } = require('child_process');
44+
const result = spawnSync(process.execPath, [ __filename, 'child' ],
45+
{ stdio: [ 'ignore', 'inherit', 'inherit' ] });
46+
assert.strictEqual(result.status, 0);

0 commit comments

Comments
 (0)