diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c index 73027cbc52ab..9ce977e425c2 100644 --- a/deps/uv/src/unix/core.c +++ b/deps/uv/src/unix/core.c @@ -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; } diff --git a/deps/uv/src/unix/linux.c b/deps/uv/src/unix/linux.c index 110964884ddc..4fcb3ec4beca 100644 --- a/deps/uv/src/unix/linux.c +++ b/deps/uv/src/unix/linux.c @@ -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(); @@ -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; diff --git a/lib/net.js b/lib/net.js index 445a7d59f8cb..9241bf885da2 100644 --- a/lib/net.js +++ b/lib/net.js @@ -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; @@ -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( diff --git a/test/parallel/test-net-socket-invalid-fd.js b/test/parallel/test-net-socket-invalid-fd.js new file mode 100644 index 000000000000..aa565933bd59 --- /dev/null +++ b/test/parallel/test-net-socket-invalid-fd.js @@ -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. +}));