mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
fix(ext/net): return an error from startTls
and serveHttp
if the original connection is captured elsewhere (#16242)
This commit removes the calls to `expect()` on `std::rc::Rc`, which caused Deno to panic under certain situations. We now return an error if `Rc` is referenced by other variables. Fixes #9360 Fixes #13345 Fixes #13926 Fixes #16241 Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
74be01273c
commit
44a89dd6dc
7 changed files with 154 additions and 4 deletions
8
cli/dts/lib.deno.ns.d.ts
vendored
8
cli/dts/lib.deno.ns.d.ts
vendored
|
@ -4017,6 +4017,14 @@ declare namespace Deno {
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* Note that this function *consumes* the given connection passed to it, thus the
|
||||||
|
* original connection will be unusable after calling this. Additionally, you
|
||||||
|
* need to ensure that the connection is not being used elsewhere when calling
|
||||||
|
* this function in order for the connection to be consumed properly.
|
||||||
|
* For instance, if there is a `Promise` that is waiting for read operation on
|
||||||
|
* the connection to complete, it is considered that the connection is being
|
||||||
|
* used elsewhere. In such a case, this function will fail.
|
||||||
|
*
|
||||||
* @category HTTP Server
|
* @category HTTP Server
|
||||||
*/
|
*/
|
||||||
export function serveHttp(conn: Conn): HttpConn;
|
export function serveHttp(conn: Conn): HttpConn;
|
||||||
|
|
|
@ -2373,6 +2373,86 @@ Deno.test(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{
|
||||||
|
permissions: { net: true },
|
||||||
|
},
|
||||||
|
async function httpServerWithoutExclusiveAccessToTcp() {
|
||||||
|
const port = 4506;
|
||||||
|
const listener = Deno.listen({ port });
|
||||||
|
|
||||||
|
const [clientConn, serverConn] = await Promise.all([
|
||||||
|
Deno.connect({ port }),
|
||||||
|
listener.accept(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const buf = new Uint8Array(128);
|
||||||
|
const readPromise = serverConn.read(buf);
|
||||||
|
assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource);
|
||||||
|
|
||||||
|
clientConn.close();
|
||||||
|
listener.close();
|
||||||
|
await readPromise;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{
|
||||||
|
permissions: { net: true, read: true },
|
||||||
|
},
|
||||||
|
async function httpServerWithoutExclusiveAccessToTls() {
|
||||||
|
const hostname = "localhost";
|
||||||
|
const port = 4507;
|
||||||
|
const listener = Deno.listenTls({
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
certFile: "cli/tests/testdata/tls/localhost.crt",
|
||||||
|
keyFile: "cli/tests/testdata/tls/localhost.key",
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCerts = [
|
||||||
|
await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"),
|
||||||
|
];
|
||||||
|
const [clientConn, serverConn] = await Promise.all([
|
||||||
|
Deno.connectTls({ hostname, port, caCerts }),
|
||||||
|
listener.accept(),
|
||||||
|
]);
|
||||||
|
await Promise.all([clientConn.handshake(), serverConn.handshake()]);
|
||||||
|
|
||||||
|
const buf = new Uint8Array(128);
|
||||||
|
const readPromise = serverConn.read(buf);
|
||||||
|
assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource);
|
||||||
|
|
||||||
|
clientConn.close();
|
||||||
|
listener.close();
|
||||||
|
await readPromise;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{
|
||||||
|
ignore: Deno.build.os === "windows",
|
||||||
|
permissions: { read: true, write: true },
|
||||||
|
},
|
||||||
|
async function httpServerWithoutExclusiveAccessToUnixSocket() {
|
||||||
|
const filePath = await Deno.makeTempFile();
|
||||||
|
const listener = Deno.listen({ path: filePath, transport: "unix" });
|
||||||
|
|
||||||
|
const [clientConn, serverConn] = await Promise.all([
|
||||||
|
Deno.connect({ path: filePath, transport: "unix" }),
|
||||||
|
listener.accept(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const buf = new Uint8Array(128);
|
||||||
|
const readPromise = serverConn.read(buf);
|
||||||
|
assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource);
|
||||||
|
|
||||||
|
clientConn.close();
|
||||||
|
listener.close();
|
||||||
|
await readPromise;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
|
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
|
||||||
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
|
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
|
||||||
const tp = new TextProtoReader(r);
|
const tp = new TextProtoReader(r);
|
||||||
|
|
|
@ -147,6 +147,33 @@ Deno.test(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { net: true } },
|
||||||
|
async function startTlsWithoutExclusiveAccessToTcpConn() {
|
||||||
|
const hostname = "localhost";
|
||||||
|
const port = getPort();
|
||||||
|
|
||||||
|
const tcpListener = Deno.listen({ hostname, port });
|
||||||
|
const [serverConn, clientConn] = await Promise.all([
|
||||||
|
tcpListener.accept(),
|
||||||
|
Deno.connect({ hostname, port }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const buf = new Uint8Array(128);
|
||||||
|
const readPromise = clientConn.read(buf);
|
||||||
|
// `clientConn` is being used by a pending promise (`readPromise`) so
|
||||||
|
// `Deno.startTls` cannot consume the connection.
|
||||||
|
await assertRejects(
|
||||||
|
() => Deno.startTls(clientConn, { hostname }),
|
||||||
|
Deno.errors.BadResource,
|
||||||
|
);
|
||||||
|
|
||||||
|
serverConn.close();
|
||||||
|
tcpListener.close();
|
||||||
|
await readPromise;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { read: true, net: true } },
|
{ permissions: { read: true, net: true } },
|
||||||
async function dialAndListenTLS() {
|
async function dialAndListenTLS() {
|
||||||
|
|
|
@ -291,6 +291,12 @@ impl ResourceTable {
|
||||||
/// If a resource with the given `rid` exists but its type does not match `T`,
|
/// If a resource with the given `rid` exists but its type does not match `T`,
|
||||||
/// it is not removed from the resource table. Note that the resource's
|
/// it is not removed from the resource table. Note that the resource's
|
||||||
/// `close()` method is *not* called.
|
/// `close()` method is *not* called.
|
||||||
|
///
|
||||||
|
/// Also note that there might be a case where
|
||||||
|
/// the returned `Rc<T>` is referenced by other variables. That is, we cannot
|
||||||
|
/// assume that `Rc::strong_count(&returned_rc)` is always equal to 1 on success.
|
||||||
|
/// In particular, be really careful when you want to extract the inner value of
|
||||||
|
/// type `T` from `Rc<T>`.
|
||||||
pub fn take<T: Resource>(&mut self, rid: ResourceId) -> Result<Rc<T>, Error> {
|
pub fn take<T: Resource>(&mut self, rid: ResourceId) -> Result<Rc<T>, Error> {
|
||||||
let resource = self.get::<T>(rid)?;
|
let resource = self.get::<T>(rid)?;
|
||||||
self.index.remove(&rid);
|
self.index.remove(&rid);
|
||||||
|
@ -299,6 +305,12 @@ impl ResourceTable {
|
||||||
|
|
||||||
/// Removes a resource from the resource table and returns it. Note that the
|
/// Removes a resource from the resource table and returns it. Note that the
|
||||||
/// resource's `close()` method is *not* called.
|
/// resource's `close()` method is *not* called.
|
||||||
|
///
|
||||||
|
/// Also note that there might be a
|
||||||
|
/// case where the returned `Rc<T>` is referenced by other variables. That is,
|
||||||
|
/// we cannot assume that `Rc::strong_count(&returned_rc)` is always equal to 1
|
||||||
|
/// on success. In particular, be really careful when you want to extract the
|
||||||
|
/// inner value of type `T` from `Rc<T>`.
|
||||||
pub fn take_any(
|
pub fn take_any(
|
||||||
&mut self,
|
&mut self,
|
||||||
rid: ResourceId,
|
rid: ResourceId,
|
||||||
|
|
9
ext/net/lib.deno_net.d.ts
vendored
9
ext/net/lib.deno_net.d.ts
vendored
|
@ -253,9 +253,18 @@ declare namespace Deno {
|
||||||
* this function requires that the other end of the connection is prepared for
|
* this function requires that the other end of the connection is prepared for
|
||||||
* a TLS handshake.
|
* a TLS handshake.
|
||||||
*
|
*
|
||||||
|
* Note that this function *consumes* the TCP connection passed to it, thus the
|
||||||
|
* original TCP connection will be unusable after calling this. Additionally,
|
||||||
|
* you need to ensure that the TCP connection is not being used elsewhere when
|
||||||
|
* calling this function in order for the TCP connection to be consumed properly.
|
||||||
|
* For instance, if there is a `Promise` that is waiting for read operation on
|
||||||
|
* the TCP connection to complete, it is considered that the TCP connection is
|
||||||
|
* being used elsewhere. In such a case, this function will fail.
|
||||||
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
|
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
|
||||||
* const caCert = await Deno.readTextFile("./certs/my_custom_root_CA.pem");
|
* const caCert = await Deno.readTextFile("./certs/my_custom_root_CA.pem");
|
||||||
|
* // `conn` becomes unusable after calling `Deno.startTls`
|
||||||
* const tlsConn = await Deno.startTls(conn, { caCerts: [caCert], hostname: "localhost" });
|
* const tlsConn = await Deno.startTls(conn, { caCerts: [caCert], hostname: "localhost" });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
|
|
@ -812,12 +812,16 @@ where
|
||||||
.borrow::<DefaultTlsOptions>()
|
.borrow::<DefaultTlsOptions>()
|
||||||
.root_cert_store
|
.root_cert_store
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let resource_rc = state
|
let resource_rc = state
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.resource_table
|
.resource_table
|
||||||
.take::<TcpStreamResource>(rid)?;
|
.take::<TcpStreamResource>(rid)?;
|
||||||
|
// This TCP connection might be used somewhere else. If it's the case, we cannot proceed with the
|
||||||
|
// process of starting a TLS connection on top of this TCP connection, so we just return a bad
|
||||||
|
// resource error. See also: https://github.com/denoland/deno/pull/16242
|
||||||
let resource = Rc::try_unwrap(resource_rc)
|
let resource = Rc::try_unwrap(resource_rc)
|
||||||
.expect("Only a single use of this resource should happen");
|
.map_err(|_| bad_resource("TCP stream is currently in use"))?;
|
||||||
let (read_half, write_half) = resource.into_inner();
|
let (read_half, write_half) = resource.into_inner();
|
||||||
let tcp_stream = read_half.reunite(write_half)?;
|
let tcp_stream = read_half.reunite(write_half)?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use deno_core::error::bad_resource;
|
||||||
use deno_core::error::bad_resource_id;
|
use deno_core::error::bad_resource_id;
|
||||||
use deno_core::error::custom_error;
|
use deno_core::error::custom_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
|
@ -44,8 +45,11 @@ fn op_http_start(
|
||||||
.resource_table
|
.resource_table
|
||||||
.take::<TcpStreamResource>(tcp_stream_rid)
|
.take::<TcpStreamResource>(tcp_stream_rid)
|
||||||
{
|
{
|
||||||
|
// This TCP connection might be used somewhere else. If it's the case, we cannot proceed with the
|
||||||
|
// process of starting a HTTP server on top of this TCP connection, so we just return a bad
|
||||||
|
// resource error. See also: https://github.com/denoland/deno/pull/16242
|
||||||
let resource = Rc::try_unwrap(resource_rc)
|
let resource = Rc::try_unwrap(resource_rc)
|
||||||
.expect("Only a single use of this resource should happen");
|
.map_err(|_| bad_resource("TCP stream is currently in use"))?;
|
||||||
let (read_half, write_half) = resource.into_inner();
|
let (read_half, write_half) = resource.into_inner();
|
||||||
let tcp_stream = read_half.reunite(write_half)?;
|
let tcp_stream = read_half.reunite(write_half)?;
|
||||||
let addr = tcp_stream.local_addr()?;
|
let addr = tcp_stream.local_addr()?;
|
||||||
|
@ -56,8 +60,11 @@ fn op_http_start(
|
||||||
.resource_table
|
.resource_table
|
||||||
.take::<TlsStreamResource>(tcp_stream_rid)
|
.take::<TlsStreamResource>(tcp_stream_rid)
|
||||||
{
|
{
|
||||||
|
// This TLS connection might be used somewhere else. If it's the case, we cannot proceed with the
|
||||||
|
// process of starting a HTTP server on top of this TLS connection, so we just return a bad
|
||||||
|
// resource error. See also: https://github.com/denoland/deno/pull/16242
|
||||||
let resource = Rc::try_unwrap(resource_rc)
|
let resource = Rc::try_unwrap(resource_rc)
|
||||||
.expect("Only a single use of this resource should happen");
|
.map_err(|_| bad_resource("TLS stream is currently in use"))?;
|
||||||
let (read_half, write_half) = resource.into_inner();
|
let (read_half, write_half) = resource.into_inner();
|
||||||
let tls_stream = read_half.reunite(write_half);
|
let tls_stream = read_half.reunite(write_half);
|
||||||
let addr = tls_stream.get_ref().0.local_addr()?;
|
let addr = tls_stream.get_ref().0.local_addr()?;
|
||||||
|
@ -71,8 +78,11 @@ fn op_http_start(
|
||||||
{
|
{
|
||||||
super::check_unstable(state, "Deno.serveHttp");
|
super::check_unstable(state, "Deno.serveHttp");
|
||||||
|
|
||||||
|
// This UNIX socket might be used somewhere else. If it's the case, we cannot proceed with the
|
||||||
|
// process of starting a HTTP server on top of this UNIX socket, so we just return a bad
|
||||||
|
// resource error. See also: https://github.com/denoland/deno/pull/16242
|
||||||
let resource = Rc::try_unwrap(resource_rc)
|
let resource = Rc::try_unwrap(resource_rc)
|
||||||
.expect("Only a single use of this resource should happen");
|
.map_err(|_| bad_resource("UNIX stream is currently in use"))?;
|
||||||
let (read_half, write_half) = resource.into_inner();
|
let (read_half, write_half) = resource.into_inner();
|
||||||
let unix_stream = read_half.reunite(write_half)?;
|
let unix_stream = read_half.reunite(write_half)?;
|
||||||
let addr = unix_stream.local_addr()?;
|
let addr = unix_stream.local_addr()?;
|
||||||
|
|
Loading…
Reference in a new issue