Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- A `wss://` dial that got through the TLS handshake and then failed frees its TLS state. The state and its two 64 KiB buffers were freed only by errdefers inside the block that created them, which had already exited, so a relay that answered the websocket upgrade with anything but 101, or a dial cancelled while waiting for that answer, leaked all three. A client that retries such a relay leaked on every retry.

## [0.14.3] - 2026-09-23

### Added
Expand Down
8 changes: 8 additions & 0 deletions src/relay.zig
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,14 @@ pub fn dial(gpa: std.mem.Allocator, io: std.Io, url: []const u8) !*Relay {
// No TLS layer, so `reader` already IS the transport reader and the
// loop's own `bufferedLen` check covers it.
};
// The TLS state outlives the block that made it, so its errdefers are gone
// by now. A wss dial that got through TLS and then failed the websocket
// upgrade, or was cancelled waiting for it, leaked all of it.
errdefer if (transport.tls_state) |ts| {
gpa.free(ts.read_buffer);
gpa.free(ts.write_buffer);
gpa.destroy(ts);
};

const relay = try gpa.create(Relay);
errdefer gpa.destroy(relay);
Expand Down
Loading