From ef3ef4e457d390e452827a1f02a8a52a8b9e1763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20S=C3=B8rensen?= Date: Tue, 27 Oct 2026 12:06:47 +0100 Subject: [PATCH] dgram: reject null bytes in address before queueing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a socket that is not bound yet, `send()` and `connect()` queue the operation until the implicit bind completes. Since `dns.lookup()` started rejecting hostnames with null bytes, the queued lookup throws from inside the 'listening' event. The error then bypasses the caller's try/catch, the callback and the 'error' listener, and the process exits. Validate the address for null bytes before anything is queued, so the error is thrown synchronously whether or not the socket is bound. Refs: https://github.com/nodejs/node/commit/7dafafa24247 Signed-off-by: Philip Sørensen --- lib/dgram.js | 5 +- .../parallel/test-dgram-address-null-bytes.js | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-dgram-address-null-bytes.js diff --git a/lib/dgram.js b/lib/dgram.js index 653cf6a26305..273580961a66 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -61,6 +61,7 @@ const { validateAbortSignal, validateObject, validateString, + validateStringWithoutNullBytes, validateNumber, validatePort, validateUint32, @@ -487,7 +488,7 @@ Socket.prototype.connect = function(port, address, callback) { address = ''; } - validateString(address, 'address'); + validateStringWithoutNullBytes(address, 'address'); const state = this[kStateSymbol]; @@ -796,7 +797,7 @@ Socket.prototype.send = function(buffer, callback = address; address = undefined; } else if (address != null) { - validateString(address, 'address'); + validateStringWithoutNullBytes(address, 'address'); } healthCheck(this); diff --git a/test/parallel/test-dgram-address-null-bytes.js b/test/parallel/test-dgram-address-null-bytes.js new file mode 100644 index 000000000000..6e35002f8ce6 --- /dev/null +++ b/test/parallel/test-dgram-address-null-bytes.js @@ -0,0 +1,49 @@ +'use strict'; +const common = require('../common'); + +// An address with a null byte must be rejected synchronously by send() and +// connect(), also on a socket that is not bound yet. Previously the operation +// was queued until the implicit bind completed, and the lookup then threw from +// inside the 'listening' event, where the caller could not catch it. + +const assert = require('assert'); +const dgram = require('dgram'); + +const PORT = 12345; +const address = '127.0.0.1\u0000.example.com'; +const expectedError = { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' }; + +{ + const socket = dgram.createSocket('udp4'); + socket.on('error', common.mustNotCall()); + + assert.throws(() => { + socket.send('hello', PORT, address, common.mustNotCall()); + }, expectedError); + + // The socket is still usable. + const receiver = dgram.createSocket('udp4'); + receiver.on('message', common.mustCall((msg) => { + assert.strictEqual(msg.toString(), 'hello'); + receiver.close(); + socket.close(); + })); + receiver.bind(0, '127.0.0.1', common.mustCall(() => { + socket.send('hello', receiver.address().port, '127.0.0.1', + common.mustSucceed()); + })); +} + +{ + const socket = dgram.createSocket('udp4'); + socket.on('error', common.mustNotCall()); + + assert.throws(() => { + socket.connect(PORT, address, common.mustNotCall()); + }, expectedError); + + // The failed connect() does not leave the socket in the connecting state. + socket.connect(PORT, '127.0.0.1', common.mustCall(() => { + socket.close(); + })); +}