Repository: denoland/deno
Pull request: fix(ext/node): fix perm checks in TCPWrap


Changed files:
filename: ext/node/ops/libuv_stream.rs
patch: @@ -20,6 +20,7 @@ use deno_core::uv_compat::UvStream;
 use deno_core::uv_compat::UvTcp;
 use deno_core::uv_compat::UvWrite;
 use deno_core::v8;
+use deno_permissions::PermissionsContainer;
 use socket2::SockAddr as Socket2SockAddr;
 
 use super::handle_wrap::AsyncId;
@@ -371,63 +372,81 @@ impl TCP {
     }
   }
 
-  #[fast]
-  fn bind(&self, #[string] address: &str, #[smi] port: i32) -> i32 {
+  #[nofast]
+  fn bind(
+    &self,
+    state: &mut OpState,
+    #[string] address: &str,
+    #[smi] port: i32,
+  ) -> Result<i32, deno_permissions::PermissionCheckError> {
+    state
+      .borrow_mut::<PermissionsContainer>()
+      .check_net(&(address, Some(port as u16)), "node:net.listen()")?;
+
     let addr_str = format!("{}:{}", address, port);
     let socket_addr = match addr_str.to_socket_addrs() {
       Ok(mut addrs) => match addrs.next() {
         Some(addr) => addr,
-        None => return -1,
+        None => return Ok(-1),
       },
-      Err(_) => return -1,
+      Err(_) => return Ok(-1),
     };
 
     // SAFETY: tcp handle is valid; socket2 SockAddr is properly initialized
     unsafe {
       let tcp = self.raw();
       if tcp.is_null() {
-        return -1;
+        return Ok(-1);
       }
       let sock_addr = Socket2SockAddr::from(socket_addr);
-      uv_compat::uv_tcp_bind(
+      Ok(uv_compat::uv_tcp_bind(
         tcp,
         sock_addr.as_ptr() as *const _,
         #[allow(clippy::unnecessary_cast)]
         {
           sock_addr.len() as u32
         },
         0,
-      )
+      ))
     }
   }
 
-  #[fast]
-  fn bind6(&self, #[string] address: &str, #[smi] port: i32) -> i32 {
+  #[nofast]
+  fn bind6(
+    &self,
+    state: &mut OpState,
+    #[string] address: &str,
+    #[smi] port: i32,
+  ) -> Result<i32, deno_permissions::PermissionCheckError> {
+    state
+      .borrow_mut::<PermissionsContainer>()
+      .check_net(&(address, Some(port as u16)), "node:net.listen()")?;
+
     let addr_str = format!("{}:{}", address, port);
     let socket_addr = match addr_str.to_socket_addrs() {
       Ok(mut addrs) => match addrs.next() {
         Some(addr) => addr,
-        None => return -1,
+        None => return Ok(-1),
       },
-      Err(_) => return -1,
+      Err(_) => return Ok(-1),
     };
 
     // SAFETY: tcp handle is valid; socket2 SockAddr is properly initialized
     unsafe {
       let tcp = self.raw();
       if tcp.is_null() {
-        return -1;
+        return Ok(-1);
       }
       let sock_addr = Socket2SockAddr::from(socket_addr);
-      uv_compat::uv_tcp_bind(
+      Ok(uv_compat::uv_tcp_bind(
         tcp,
         sock_addr.as_ptr() as *const _,
         #[allow(clippy::unnecessary_cast)]
         {
           sock_addr.len() as u32
         },
         0,
-      )
+      ))
     }
   }
 
@@ -546,22 +565,31 @@ impl TCP {
     }
   }
 
-  #[fast]
-  fn connect(&self, #[string] address: &str, #[smi] port: i32) -> i32 {
+  #[nofast]
+  fn connect(
+    &self,
+    state: &mut OpState,
+    #[string] address: &str,
+    #[smi] port: i32,
+  ) -> Result<i32, deno_permissions::PermissionCheckError> {
+    state
+      .borrow_mut::<PermissionsContainer>()
+      .check_net(&(address, Some(port as u16)), "node:net.connect()")?;
+
     let addr_str = format!("{}:{}", address, port);
     let socket_addr = match addr_str.to_socket_addrs() {
       Ok(mut addrs) => match addrs.next() {
         Some(addr) => addr,
-        None => return -1,
+        None => return Ok(-1),
       },
-      Err(_) => return -1,
+      Err(_) => return Ok(-1),
     };
 
     // SAFETY: tcp handle is valid; ConnectReq freed in connect_cb
     unsafe {
       let tcp = self.raw();
       if tcp.is_null() {
-        return -1;
+        return Ok(-1);
       }
       let sock_addr = Socket2SockAddr::from(socket_addr);
       let mut connect_req = Box::new(ConnectReq {
@@ -579,7 +607,7 @@ impl TCP {
         // Failed, reclaim the ConnectReq
         let _ = Box::from_raw(req_ptr as *mut ConnectReq);
       }
-      ret
+      Ok(ret)
     }
   }
 

filename: ext/node/polyfills/net.ts
patch: @@ -593,10 +593,15 @@ function _internalConnect(
     req.localAddress = localAddress;
     req.localPort = localPort;
 
-    if (addressType === 4) {
-      err = (socket._handle as TCP).connect(req, address, port);
-    } else {
-      err = (socket._handle as TCP).connect6(req, address, port);
+    try {
+      if (addressType === 4) {
+        err = (socket._handle as TCP).connect(req, address, port);
+      } else {
+        err = (socket._handle as TCP).connect6(req, address, port);
+      }
+    } catch (e) {
+      socket.destroy(e);
+      return;
     }
   } else {
     const req = new PipeConnectWrap();

filename: tests/unit_node/http2_test.ts
patch: @@ -7,7 +7,7 @@ import { Buffer } from "node:buffer";
 import { readFile } from "node:fs/promises";
 import { join } from "node:path";
 import * as net from "node:net";
-import { assert, assertEquals } from "@std/assert";
+import { assert, assertEquals, assertRejects } from "@std/assert";
 import { curlRequest } from "../unit/test_util.ts";
 import { createRequire } from "node:module";
 const require = createRequire(import.meta.url);
@@ -513,3 +513,17 @@ Deno.test("[node/http2] Server.address() includes family property", async () =>
     await promise;
   }
 });
+
+Deno.test("[node/http2 client] connect without net permission", {
+  permissions: { net: false },
+}, async () => {
+  await assertRejects(
+    () => {
+      return new Promise((_resolve, reject) => {
+        const client = http2.connect("http://127.0.0.1:4246");
+        client.on("error", reject);
+      });
+    },
+    Deno.errors.NotCapable,
+  );
+});