Repository: nodejs/node
Pull request: net: validate host name for server listen

Fixes: https://github.com/nodejs/node/issues/54441

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/net.js
patch: @@ -35,6 +35,8 @@ const {
   NumberParseInt,
   ObjectDefineProperty,
   ObjectSetPrototypeOf,
+  RegExp,
+  RegExpPrototypeExec,
   Symbol,
   SymbolAsyncDispose,
   SymbolDispose,
@@ -143,6 +145,8 @@ const { kTimeout } = require('internal/timers');
 const DEFAULT_IPV4_ADDR = '0.0.0.0';
 const DEFAULT_IPV6_ADDR = '::';
 
+const HOST_REGEXP = new RegExp('^[a-zA-Z0-9-:%.]+$');
+
 const noop = () => {};
 
 const kPerfHooksNetConnectContext = Symbol('kPerfHooksNetConnectContext');
@@ -2020,6 +2024,10 @@ Server.prototype.listen = function(...args) {
     toNumber(args.length > 2 && args[2]);  // (port, host, backlog)
 
   options = options._handle || options.handle || options;
+  if (typeof options.host === 'string' && RegExpPrototypeExec(HOST_REGEXP, options.host) === null) {
+    throw new ERR_INVALID_ARG_VALUE('host', options.host);
+  }
+
   const flags = getFlags(options.ipv6Only);
   //  Refresh the id to make the previous call invalid
   this._listeningId++;

filename: test/parallel/test-net-server-listen-options.js
patch: @@ -15,6 +15,10 @@ function close() { this.close(); }
   // Test listen({port})
   net.createServer().listen({ port: 0 })
     .on('listening', common.mustCall(close));
+  // Test listen(host, port}) on ipv4
+  net.createServer().listen({ host: '127.0.0.1', port: '3000' }).on('listening', common.mustCall(close));
+  // Test listen(host, port}) on ipv6
+  net.createServer().listen({ host: '::', port: '3001' }).on('listening', common.mustCall(close));
 }
 
 // Test listen(port, cb) and listen({ port }, cb) combinations
@@ -66,6 +70,13 @@ const listenOnPort = [
                       name: 'TypeError',
                       message: /^The argument 'options' must have the property "port" or "path"\. Received .+$/,
                     });
+    } else if (typeof options.host === 'string' && !options.host.match(/^[a-zA-Z0-9-:%.]+$/)) {
+      assert.throws(fn,
+                    {
+                      code: 'ERR_INVALID_ARG_VALUE',
+                      name: 'TypeError',
+                      message: /^The argument 'host' is invalid\. Received .+$/,
+                    });
     } else {
       assert.throws(fn,
                     {
@@ -91,4 +102,5 @@ const listenOnPort = [
   shouldFailToListen({ host: 'localhost:3000' });
   shouldFailToListen({ host: { port: 3000 } });
   shouldFailToListen({ exclusive: true });
+  shouldFailToListen({ host: '[::]', port: 3000 });
 }