Repository: denoland/deno
Pull request: fix(ext/http2): fix oob buffer reads in http2 requests

Fixes https://github.com/denoland/deno/issues/29956

Changed files:
filename: ext/node/polyfills/http2.ts
patch: @@ -1041,7 +1041,7 @@ export class ClientHttp2Stream extends Duplex {
       data = ENCODER.encode(chunk);
     } else if (encoding === "buffer") {
       this.#encoding = encoding;
-      data = chunk.buffer;
+      data = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
     }
 
     this.#requestPromise

filename: tests/unit_node/http2_test.ts
patch: @@ -71,6 +71,7 @@ for (const url of ["http://localhost:4246", "https://localhost:4247"]) {
       "abc": "def",
       "opr": "stv",
       "foo": "bar",
+      "req_body_len": "5",
     });
   });
 }
@@ -121,6 +122,64 @@ Deno.test(`[node/http2 client createConnection]`, {
   assertEquals(receivedData, "hello world\n");
 });
 
+// https://github.com/denoland/deno/issues/29956
+Deno.test(`[node/http2 client body overflow]`, {
+  ignore: Deno.build.os === "windows",
+}, async () => {
+  const url = "http://127.0.0.1:4246";
+  const createConnDeferred = Promise.withResolvers<void>();
+  // Create a server to respond to the HTTP2 requests
+  const client = http2.connect(url, {
+    createConnection() {
+      const socket = net.connect({ host: "127.0.0.1", port: 4246 });
+
+      socket.on("connect", () => {
+        createConnDeferred.resolve();
+      });
+
+      return socket;
+    },
+  });
+  client.on("error", (err) => console.error(err));
+
+  const req = client.request({ ":method": "POST", ":path": "/" });
+
+  let receivedData = "";
+  let receivedTrailers;
+
+  const ab = new ArrayBuffer(100);
+  const view = new Uint8Array(ab, 0, 5);
+
+  req.write(view);
+  req.setEncoding("utf8");
+
+  req.on("data", (chunk) => {
+    receivedData += chunk;
+  });
+
+  req.on("trailers", (trailers, _flags) => {
+    receivedTrailers = trailers;
+  });
+
+  req.end();
+
+  const endPromise = Promise.withResolvers<void>();
+  setTimeout(() => {
+    try {
+      client.close();
+    } catch (_) {
+      // pass
+    }
+    endPromise.resolve();
+  }, 2000);
+
+  await createConnDeferred.promise;
+  await endPromise.promise;
+  assertEquals(receivedData, "hello world\n");
+
+  assertEquals(receivedTrailers?.["req_body_len"], "5");
+});
+
 Deno.test("[node/http2 client GET https://www.example.com]", async () => {
   const clientSession = http2.connect("https://www.example.com");
   const req = clientSession.request({

filename: tests/util/server/src/servers/grpc.rs
patch: @@ -52,9 +52,11 @@ pub async fn h2_grpc_server(h2_grpc_port: u16, h2s_grpc_port: u16) {
     mut respond: h2::server::SendResponse<bytes::Bytes>,
   ) -> Result<(), anyhow::Error> {
     let body = request.body_mut();
+    let mut len = 0;
     while let Some(data) = body.data().await {
       let data = data?;
       let _ = body.flow_control().release_capacity(data.len());
+      len += data.len();
     }
 
     let maybe_recv_trailers = body.trailers().await?;
@@ -72,6 +74,10 @@ pub async fn h2_grpc_server(h2_grpc_port: u16, h2s_grpc_port: u16) {
       HeaderName::from_static("opr"),
       HeaderValue::from_static("stv"),
     );
+    trailers.insert(
+      HeaderName::from_static("req_body_len"),
+      HeaderValue::from(len),
+    );
     if let Some(recv_trailers) = maybe_recv_trailers {
       for (key, value) in recv_trailers {
         trailers.insert(key.unwrap(), value);