GitHub Security Advisory: GHSA-6q4w-9x56-rmwq
Package: deno (RUST)
Severity: HIGH (CVSS 8.2)
CWE: CWE-863: Incorrect Authorization
Vulnerable versions: = 1.39.0
Patched in: 1.39.1

Summary: Deno arbitrary file descriptor close via `op_node_ipc_pipe()` leading to permission prompt bypass

Details:
### Summary

Use of raw file descriptors in `op_node_ipc_pipe()` leads to premature close of arbitrary file descriptors, allowing standard input to be re-opened as a different resource resulting in permission prompt bypass.


### Details

Node child_process IPC relies on the JS side to pass the raw IPC file descriptor to `op_node_ipc_pipe()`, which returns a `IpcJsonStreamResource` ID associated with the file descriptor. On closing the resource, the raw file descriptor is closed together.

Although closing a file descriptor is seemingly a harmless task, this has been known to be exploitable:
- With `--allow-read` and `--allow-write` permissions, one can open `/dev/ptmx` as stdin. This device happily accepts TTY ioctls and pipes anything written into it back to the reader. 
  - This has been presented in a hacking competition (WACON 2023 Quals "dino jail").
  - However, the precondition of this challenge was heavily contrived: fd 0 has manually been closed by FFI and `setuid()` was used to drop permissions and deny access to `/proc` since global write permissions are usually equivalent to arbitrary code execution (`/proc/self/mem`).

As this vulnerability conveniently allows us to close stdin (fd 0) without any FFI, we can open any resource that when read returns `y`, `Y` or `A` as its first character ([runtimes/permissions/prompter.rs](https://github.com/denoland/deno/blob/v1.39.0/runtime/permissions/prompter.rs#L265)) to bypass the prompt.

There is a caveat however - all stdio/stdin/stderr streams are [locked](https://github.com/denoland/deno/blob/v1.39.0/runtime/permissions/prompter.rs#L214), after which [`clear_stdin()`](https://github.com/denoland/deno/blob/v1.39.0/runtime/permissions/prompter.rs#L220) is called. This invokes [`libc::tcflush(0, libc::TCIFLUSH)`](https://github.com/denoland/deno/blob/v1.39.0/runtime/permissions/prompter.rs#L99) which fails on a non-TTY file descriptor.

This can be exploited by widening the race window between `clear_stdin()` and the next [`stdin_lock.read_line()`](https://github.com/denoland/deno/blob/v1.39.0/runtime/permissions/prompter.rs#L256). Notably, the prompt message contains the requested resource name (path) which is filtered by [`strip_ansi_codes_and_ascii_control()`](https://github.com/denoland/deno/blob/v1.39.0/runtime/permissions/prompter.rs#L225). This is also concatenated by [`write!()`](https://github.com/denoland/deno/blob/v1.39.0/runtime/permissions/prompter.rs#L241) to make a single buffer printed out to stderr. Thus, if we request a very long resource name, the window will widen allowing us to easily and stably race another Worker that closes fd 0 and opens a resource starting with an `A\n` within the race window.

Note that attacker does not need any permissions to exploit this bug to a full permission prompt bypass, as Cache API can be used to create and open files with controlled content without any permissions. Refer to the Impact section for more details.


### PoC

Testing environm