1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-23 15:49:44 -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:
Yusuke Tanaka 2022-10-18 11:28:27 +09:00 committed by GitHub
parent 74be01273c
commit 44a89dd6dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 154 additions and 4 deletions

View file

@ -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
*/
export function serveHttp(conn: Conn): HttpConn;

View file

@ -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 {
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
const tp = new TextProtoReader(r);

View file

@ -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(
{ permissions: { read: true, net: true } },
async function dialAndListenTLS() {

View file

@ -291,6 +291,12 @@ impl ResourceTable {
/// 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
/// `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> {
let resource = self.get::<T>(rid)?;
self.index.remove(&rid);
@ -299,6 +305,12 @@ impl ResourceTable {
/// Removes a resource from the resource table and returns it. Note that the
/// 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(
&mut self,
rid: ResourceId,

View file

@ -253,9 +253,18 @@ declare namespace Deno {
* this function requires that the other end of the connection is prepared for
* 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
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
* 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" });
* ```
*

View file

@ -812,12 +812,16 @@ where
.borrow::<DefaultTlsOptions>()
.root_cert_store
.clone();
let resource_rc = state
.borrow_mut()
.resource_table
.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)
.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 tcp_stream = read_half.reunite(write_half)?;

View file

@ -1,6 +1,7 @@
use std::cell::RefCell;
use std::rc::Rc;
use deno_core::error::bad_resource;
use deno_core::error::bad_resource_id;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
@ -44,8 +45,11 @@ fn op_http_start(
.resource_table
.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)
.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 tcp_stream = read_half.reunite(write_half)?;
let addr = tcp_stream.local_addr()?;
@ -56,8 +60,11 @@ fn op_http_start(
.resource_table
.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)
.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 tls_stream = read_half.reunite(write_half);
let addr = tls_stream.get_ref().0.local_addr()?;
@ -71,8 +78,11 @@ fn op_http_start(
{
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)
.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 unix_stream = read_half.reunite(write_half)?;
let addr = unix_stream.local_addr()?;