From dc9265091a3a0ae33c4b975ce442cb686581e5e1 Mon Sep 17 00:00:00 2001
From: James M Snell <jasnell@gmail.com>
Date: Thu, 23 Aug 2018 10:38:31 -0700
Subject: [PATCH] http2: throw better error when accessing unbound socket proxy

Fixes: https://github.com/nodejs/node/issues/22268
---
 doc/api/errors.md                             |  6 ++
 lib/internal/errors.js                        |  2 +
 lib/internal/http2/core.js                    | 13 +++-
 .../test-http2-unbound-socket-proxy.js        | 69 +++++++++++++++++++
 4 files changed, 88 insertions(+), 2 deletions(-)
 create mode 100644 test/parallel/test-http2-unbound-socket-proxy.js

diff --git a/doc/api/errors.md b/doc/api/errors.md
index 72e61c49bb970a..61bf5a9dbdc09f 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1031,6 +1031,12 @@ The `Http2Session` settings canceled.
 An attempt was made to connect a `Http2Session` object to a `net.Socket` or
 `tls.TLSSocket` that had already been bound to another `Http2Session` object.
 
+<a id="ERR_HTTP2_SOCKET_UNBOUND"></a>
+### ERR_HTTP2_SOCKET_UNBOUND
+
+An attempt was made to use the `socket` property of an `Http2Session` that
+has already been closed.
+
 <a id="ERR_HTTP2_STATUS_101"></a>
 ### ERR_HTTP2_STATUS_101
 
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 0520fbbc6f6668..899979e5cc39e8 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -588,6 +588,8 @@ E('ERR_HTTP2_SESSION_ERROR', 'Session closed with error code %s', Error);
 E('ERR_HTTP2_SETTINGS_CANCEL', 'HTTP2 session settings canceled', Error);
 E('ERR_HTTP2_SOCKET_BOUND',
   'The socket is already bound to an Http2Session', Error);
+E('ERR_HTTP2_SOCKET_UNBOUND',
+  'The socket has been disconnected from the Http2Session', Error);
 E('ERR_HTTP2_STATUS_101',
   'HTTP status code 101 (Switching Protocols) is forbidden in HTTP/2', Error);
 E('ERR_HTTP2_STATUS_INVALID', 'Invalid status code: %s', RangeError);
diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js
index c0c452a35f175f..61dd06b5edec76 100644
--- a/lib/internal/http2/core.js
+++ b/lib/internal/http2/core.js
@@ -60,6 +60,7 @@ const {
     ERR_HTTP2_SESSION_ERROR,
     ERR_HTTP2_SETTINGS_CANCEL,
     ERR_HTTP2_SOCKET_BOUND,
+    ERR_HTTP2_SOCKET_UNBOUND,
     ERR_HTTP2_STATUS_101,
     ERR_HTTP2_STATUS_INVALID,
     ERR_HTTP2_STREAM_CANCEL,
@@ -685,12 +686,17 @@ const proxySocketHandler = {
         throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
       default:
         const socket = session[kSocket];
+        if (socket === undefined)
+          throw new ERR_HTTP2_SOCKET_UNBOUND();
         const value = socket[prop];
         return typeof value === 'function' ? value.bind(socket) : value;
     }
   },
   getPrototypeOf(session) {
-    return Reflect.getPrototypeOf(session[kSocket]);
+    const socket = session[kSocket];
+    if (socket === undefined)
+      throw new ERR_HTTP2_SOCKET_UNBOUND();
+    return Reflect.getPrototypeOf(socket);
   },
   set(session, prop, value) {
     switch (prop) {
@@ -706,7 +712,10 @@ const proxySocketHandler = {
       case 'write':
         throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
       default:
-        session[kSocket][prop] = value;
+        const socket = session[kSocket];
+        if (socket === undefined)
+          throw new ERR_HTTP2_SOCKET_UNBOUND();
+        socket[prop] = value;
         return true;
     }
   }
diff --git a/test/parallel/test-http2-unbound-socket-proxy.js b/test/parallel/test-http2-unbound-socket-proxy.js
new file mode 100644
index 00000000000000..44f113bac962f7
--- /dev/null
+++ b/test/parallel/test-http2-unbound-socket-proxy.js
@@ -0,0 +1,69 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+const http2 = require('http2');
+const net = require('net');
+
+const server = http2.createServer();
+server.on('stream', common.mustCall((stream) => {
+  stream.respond();
+  stream.end('ok');
+}));
+
+server.listen(0, common.mustCall(() => {
+  const client = http2.connect(`http://localhost:${server.address().port}`);
+  const socket = client.socket;
+  const req = client.request();
+  req.resume();
+  req.on('close', common.mustCall(() => {
+    client.close();
+    server.close();
+
+    // Tests to make sure accessing the socket proxy fails with an
+    // informative error.
+    setImmediate(common.mustCall(() => {
+      common.expectsError(() => {
+        socket.example;
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+      common.expectsError(() => {
+        socket.example = 1;
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+      common.expectsError(() => {
+        socket instanceof net.Socket;
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+      common.expectsError(() => {
+        socket.ref();
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+      common.expectsError(() => {
+        socket.unref();
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+      common.expectsError(() => {
+        socket.setEncoding();
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+      common.expectsError(() => {
+        socket.setKeepAlive();
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+      common.expectsError(() => {
+        socket.setNoDelay();
+      }, {
+        code: 'ERR_HTTP2_SOCKET_UNBOUND'
+      });
+    }));
+  }));
+}));