Skip to content
File

Blob: src/workerd/api/system-streams-test.c++

2.2 KB
1// Copyright (c) 2017-2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "system-streams.h"
6 
7#include <workerd/io/io-context.h>
8#include <workerd/tests/test-fixture.h>
9 
10#include <kj/test.h>
11 
12namespace workerd::api {
13namespace {
14 
15KJ_TEST("EncodedAsyncInputStream cancel with pending read on AsyncPipe") {
16 // This test reproduces a use-after-free crash that occurred when:
17 // 1. A read operation is started on an EncodedAsyncInputStream backed by an AsyncPipe
18 // 2. The stream is cancelled (e.g., via Socket::close())
19 // 3. The AsyncPipe is destroyed while the read is still pending
20 //
21 // Without the fix (kj::Canceler in EncodedAsyncInputStream), the BlockedRead destructor
22 // would try to access the freed AsyncPipe, causing a use-after-free.
23 
24 TestFixture fixture;
25 fixture.runInIoContext([](const TestFixture::Environment& env) -> kj::Promise<void> {
26 // Create an in-memory pipe (AsyncPipe)
27 auto pipe = kj::newTwoWayPipe();
28 
29 // Create an EncodedAsyncInputStream wrapping one end of the pipe
30 kj::Own<kj::AsyncInputStream> inputStream = kj::mv(pipe.ends[0]);
31 auto stream = newSystemStream(kj::mv(inputStream), StreamEncoding::IDENTITY, env.context);
32 
33 // Start a read operation - this will block because no data has been written to the pipe
34 kj::byte buffer[100];
35 auto readPromise = stream->tryRead(buffer, 1, sizeof(buffer));
36 
37 // Cancel the stream - this simulates what Socket::close() does
38 stream->cancel(KJ_EXCEPTION(DISCONNECTED, "stream cancelled"));
39 
40 // Now destroy the other end of the pipe - this destroys the AsyncPipe
41 // Without the fix, this would cause a use-after-free when the BlockedRead
42 // destructor tries to access the freed pipe.
43 pipe.ends[1] = nullptr;
44 
45 // The read promise should be cancelled - try to wait for it
46 // It should reject with the cancellation exception
47 return readPromise.then(
48 [](size_t) { KJ_FAIL_ASSERT("read should have been cancelled"); }, [](kj::Exception&& e) {
49 // Expected the read to be cancelled
50 });
51 });
52}
53 
54} // namespace
55} // namespace workerd::api