Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion deps/uv/src/unix/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,23 @@ void uv__io_poll_check(uv_loop_t* loop, sigset_t* pset) {
}

int uv__fd_exists(uv_loop_t* loop, int fd) {
return (unsigned) fd < loop->nwatchers && loop->watchers[fd] != NULL;
if ((unsigned) fd < loop->nwatchers && loop->watchers[fd] != NULL)
return 1;

/* Also treat the loop's private fds as in-use. Their write ends are not
* registered in watchers[] but adopting them via uv_*_open() corrupts the
* loop and can abort later in uv__io_poll().
*/
if (fd != -1 && loop->backend_fd == fd)
return 1;
if (fd != -1 &&
(loop->signal_pipefd[0] == fd || loop->signal_pipefd[1] == fd))
return 1;
if (fd != -1 &&
(loop->async_io_watcher.fd == fd || loop->async_wfd == fd))
return 1;

return 0;
}


Expand Down
32 changes: 31 additions & 1 deletion deps/uv/src/unix/linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,13 @@ static void uv__epoll_ctl_flush(int epollfd,
if (op != EPOLL_CTL_ADD)
abort();

/* EEXIST: already watched — retry as MOD.
* EBADF/ENOENT/EPERM: fd was closed or is not epoll-able between
* uv__io_start() and here. Ignore rather than abort the process.
*/
if (cqe->res == -EBADF || cqe->res == -ENOENT || cqe->res == -EPERM)
continue;

if (cqe->res != -EEXIST)
abort();

Expand Down Expand Up @@ -1423,12 +1430,35 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
if (!epoll_ctl(epollfd, op, fd, &e))
continue;

/* fd may have been closed after uv__io_start() queued this update.
* That is a recoverable application mistake; do not abort the process.
*/
if (errno == EBADF || errno == ENOENT || errno == EPERM) {
w->events = 0;
if ((unsigned) fd < loop->nwatchers && loop->watchers[fd] == w) {
loop->watchers[fd] = NULL;
assert(loop->nfds > 0);
loop->nfds--;
}
continue;
}

assert(op == EPOLL_CTL_ADD);
assert(errno == EEXIST);

/* File descriptor that's been watched before, update event mask. */
if (epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &e))
if (epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &e)) {
if (errno == EBADF || errno == ENOENT || errno == EPERM) {
w->events = 0;
if ((unsigned) fd < loop->nwatchers && loop->watchers[fd] == w) {
loop->watchers[fd] = NULL;
assert(loop->nfds > 0);
loop->nfds--;
}
continue;
}
abort();
}
}

inv.events = events;
Expand Down
10 changes: 10 additions & 0 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const { getOptionValue } = require('internal/options');
// Lazy loaded to improve startup performance.
let cluster;
let dns;
let fs;
let BlockList;
let SocketAddress;
let netPromises;
Expand Down Expand Up @@ -198,6 +199,15 @@ function getFlags(options) {

function createHandle(fd, is_server) {
validateInt32(fd, 'fd', 0);
// Validate the descriptor is open before handing it to libuv. An invalid fd
// must become a JS exception (not a process abort inside uv__io_poll when a
// later write races with close). See https://github.com/nodejs/node/issues/63308.
fs ??= require('fs');
try {
fs.fstatSync(fd);
} catch (err) {
throw new ErrnoException(err.errno, 'fstat');
}
const type = guessHandleType(fd);
if (type === 'PIPE') {
return new Pipe(
Expand Down
61 changes: 61 additions & 0 deletions test/parallel/test-net-socket-invalid-fd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const net = require('net');

// Invalid fds must throw a JS exception from the Socket constructor rather than
// aborting the process inside libuv (see https://github.com/nodejs/node/issues/63308).

assert.throws(
() => new net.Socket({ fd: -1 }),
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
}
);

common.runWithInvalidFD((fd) => {
assert.throws(
() => {
new net.Socket({
fd,
readable: false,
writable: true,
});
},
{
code: 'EBADF',
syscall: 'fstat',
}
);
});

// Wrapping arbitrary existing fds is unsupported on Windows.
if (common.isWindows)
return;

// Iterating arbitrary fds must not abort the process. Unsupported fds throw;
// in-use libuv fds return EEXIST from open; others may open and emit errors.
{
let fd = 3;
while (fd < 64) {
try {
const stream = new net.Socket({
fd,
readable: false,
writable: true,
});
stream.on('error', () => {});
stream.write('might crash');
stream.destroy();
} catch {
// Expected for unsupported / invalid / already-watched descriptors.
}
fd += 1;
}
}

setImmediate(common.mustCall(() => {
// If libuv aborted, we never reach here.
}));