Skip to content

test(ext/node): regression test for empty buffer TLS write panic#34412

Merged
littledivy merged 1 commit into
denoland:mainfrom
divybot:orch/divybot-241
May 27, 2026
Merged

test(ext/node): regression test for empty buffer TLS write panic#34412
littledivy merged 1 commit into
denoland:mainfrom
divybot:orch/divybot-241

Conversation

@divybot
Copy link
Copy Markdown
Contributor

@divybot divybot commented May 27, 2026

Summary

Fixes the Deno Sandbox panic reported in #34404 by adding regression
coverage for the underlying crash.

Running:

import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create({});
await sandbox.sh`ls -lh /`;

panicked with:

thread 'main' panicked at ext\node\ops\tls_wrap.rs:2000:31:
called `Option::unwrap()` on a `None` value

Root cause

@deno/sandbox connects via npm:ws. The ws Sender batches frame
writes through the socket with cork() + write(header) +
write(data) + uncork(), and control / empty frames write
Buffer.alloc(0). That produces a node:tls writev containing a
zero-length chunk.

A zero-length ArrayBuffer has a null backing-store data pointer, so
v8::ArrayBuffer::data() returns None. The writev op used to do
ab.data().unwrap(), panicking on that None.

This was fixed in #33737, which changed the TLSWrap write ops to skip
chunks whose data() is None. The existing
tests/specs/node/tls_write_detached_buffer spec test covers the
detached-buffer trigger via direct handle calls. This PR adds
coverage for the empty / zero-length buffer trigger — the one
@deno/sandbox actually hits — through both direct handle calls and the
natural socket.write() + cork()/uncork() path.

Verification

  • With the data() guard reverted to the old ab.data().unwrap(), the
    new test reproduces the original panic (called Option::unwrap() on a None value, same column :31).
  • With the guard in place (current main), the test passes (ok).

No runtime code change — main already contains the fix; this locks it
in against regression for the reported scenario.

Closes denoland/orchid#241

Writing an empty (zero-length) Uint8Array through node:tls's TLSWrap
used to panic with `called Option::unwrap() on a None value` at
`ext/node/ops/tls_wrap.rs` (the `ab.data().unwrap()` in `writev`).
A zero-length ArrayBuffer has a null backing-store data pointer, so
`ArrayBuffer::data()` returns `None`.

This is the path hit by `@deno/sandbox`, which connects via `npm:ws`;
the `ws` Sender batches frame writes with `socket.cork()` +
`socket.write(header)` + `socket.write(data)` + `socket.uncork()`, and
control/empty frames write `Buffer.alloc(0)`, producing a `writev` with
a zero-length chunk.

The write ops were fixed in denoland#33737 to skip chunks whose `data()` is
`None`. The existing `tls_write_detached_buffer` spec test covers the
detached-buffer trigger via direct handle calls; this adds coverage for
the empty/zero-length buffer trigger through both direct handle calls
and the natural `socket.write()`/cork path that the sandbox uses.

Verified the test reproduces the original panic (same
`Option::unwrap() on a None value`, same column) when the guard is
reverted, and passes with the guard in place.

Closes denoland/orchid#241

Co-Authored-By: Divy Srivastava <me@littledivy.com>
@littledivy littledivy merged commit a3c4b40 into denoland:main May 27, 2026
136 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants