Got UnexpectedEof when reading from a TlsConn
Version: deno 1.43.6 (release, x86_64-unknown-linux-gnu) The following code throws UnexpectedEof after all html was printed to stdout.
import { writeAll } from "jsr:@std/io";
const conn = await Deno.connectTls({ hostname: "www.google.com", port: 443 });
await writeAll(conn, new TextEncoder().encode("GET / HTTP/1.0\r\n\r\n"));
await conn.readable.pipeTo(Deno.stdout.writable);
The callstack is:
error: Uncaught (in promise) UnexpectedEof: unexpected end of file
at Object.pull (ext:deno_web/06_streams.js:996:30)
at Module.invokeCallbackFunction (ext:deno_webidl/00_webidl.js:946:16)
at ReadableByteStreamController.pullAlgorithm (ext:deno_web/06_streams.js:3563:14)
at readableByteStreamControllerCallPullIfNeeded (ext:deno_web/06_streams.js:1242:49)
at ReadableByteStreamController.[[[PullSteps]]] (ext:deno_web/06_streams.js:5983:5)
at readableStreamDefaultReaderRead (ext:deno_web/06_streams.js:2513:36)
at ext:deno_web/06_streams.js:2742:9
at new Promise (<anonymous>)
at ext:deno_web/06_streams.js:2741:14
at eventLoopTick (ext:core/01_core.js:168:7)
The problem doesn't exist when working with normal Tcp socket. The following code works fine.
import { writeAll } from "jsr:@std/io";
const conn = await Deno.connect({ hostname: "www.google.com", port: 80 });
await writeAll(conn, new TextEncoder().encode("GET / HTTP/1.0\r\n\r\n"));
await conn.readable.pipeTo(Deno.stdout.writable);
Instead of using pipeTo API, I decide to go with reader API with the following code because it gives me a chance to handle EOF. But it seems EOF is not signaled by returning done=true.
import { writeAll } from "jsr:@std/io";
const conn = await Deno.connectTls({ hostname: "www.google.com", port: 443 });
await writeAll(conn, new TextEncoder().encode("GET / HTTP/1.0\r\n\r\n"));
// await conn.readable.pipeTo(Deno.stdout.writable);
const reader = conn.readable.getReader(/*{ mode: "byob" }*/);
while (true) {
const {done, value} = await reader.read(); // throws UnexpectedEof instead of returning done as true.
if (done) {
if (value) {
await writeAll(Deno.stdout, value);
}
console.log(`done`);
break;
}
await writeAll(Deno.stdout, value);
}
Callstack:
error: Uncaught (in promise) UnexpectedEof: unexpected end of file
const {done, value} = await reader.read();
^
at Object.pull (ext:deno_web/06_streams.js:996:30)
at Module.invokeCallbackFunction (ext:deno_webidl/00_webidl.js:946:16)
at ReadableByteStreamController.pullAlgorithm (ext:deno_web/06_streams.js:3563:14)
at readableByteStreamControllerCallPullIfNeeded (ext:deno_web/06_streams.js:1242:49)
at ReadableByteStreamController.[[[PullSteps]]] (ext:deno_web/06_streams.js:5983:5)
at readableStreamDefaultReaderRead (ext:deno_web/06_streams.js:2513:36)
at ReadableStreamDefaultReader.read (ext:deno_web/06_streams.js:5480:5)
at file:///home/wyf/deno/main.ts:10:38
at eventLoopTick (ext:core/01_core.js:168:7)
I tried to reproduce it, but I didn't get this error. Try it at version 1.44.0 it seems that it resolved. @1yefuwang1 @marvinhagemeister
I can still reproduce it with 1.44.1. I'm using wsl btw.
I can reproduce - this indeed looks like a bug.
Unfortunately, this issue still exists in deno 2
deno 2.0.0 (stable, release, x86_64-unknown-linux-gnu)
v8 12.9.202.13-rusty
typescript 5.6.2
with a slightly different error message:
error: Uncaught (in promise) UnexpectedEof: peer closed connection without sending TLS close_notify: https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof
at Object.pull (ext:deno_web/06_streams.js:988:30)
at Module.invokeCallbackFunction (ext:deno_webidl/00_webidl.js:982:16)
at ReadableByteStreamController.pullAlgorithm (ext:deno_web/06_streams.js:3559:14)
at readableByteStreamControllerCallPullIfNeeded (ext:deno_web/06_streams.js:1234:49)
at ReadableByteStreamController.[[[PullSteps]]] (ext:deno_web/06_streams.js:5987:5)
at readableStreamDefaultReaderRead (ext:deno_web/06_streams.js:2509:36)
at ext:deno_web/06_streams.js:2738:9
at new Promise (<anonymous>)
at ext:deno_web/06_streams.js:2737:14
at eventLoopTick (ext:core/01_core.js:175:7)
Following the link in the error message is a detailed explanation of the error from rustls:
TLS has a close_notify mechanism to prevent truncation attacks[2](https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#fn2). According to the TLS RFCs, each party is required to send a close_notify message before closing the write side of the connection. However, some implementations don’t send it. So long as the application layer protocol (for instance HTTP/2) has message length framing and can reject truncated messages, this is not a security problem.
Rustls treats an EOF without close_notify as an error of type std::io::Error with ErrorKind::UnexpectedEof. In some situations it’s appropriate for the application to handle this error the same way it would handle a normal EOF (a read returning Ok(0)). In particular if UnexpectedEof occurs on an idle connection it is appropriate to treat it the same way as a clean shutdown. And if an application always uses messages with length framing (in other words, messages are never delimited by the close of the TCP connection), it can unconditionally ignore UnexpectedEof errors from rustls.
It seems deno doesn't handle ErrorKind::UnexpectedEof caused by EOF without close_notify properly.
I am also facing it. Any solution