Skip to content

Commit de8e8c9

Browse files
committed
Fix ALPN resolver being collected while OpenSSL still holds it
`SSLContext.alpn_set_resolver` hands the resolver to OpenSSL as ALPN select callback data. OpenSSL keeps a raw pointer to it and reads it live from the `SSL_CTX` on every server handshake. Nothing on the Pony side kept the Pony `SSLContext` alive once the caller dropped it: a session held only the raw `SSL_CTX` pointer, which keeps the C context alive through a refcount but is invisible to the garbage collector. So the context, and the resolver it holds, could be collected while a session made from it was still handshaking, and a peer connecting would drive a server handshake into freed memory. A session now holds its `SSLContext`. The context holds the current resolver, so one live session keeps both alive, and the same fix covers setting the resolver after a session exists and replacing it. To hold the context, a session needs a sendable reference to it, and it has to be `val` rather than `tag` because the garbage collector does not trace a `tag`'s fields, so `client` and `server` take a `val` receiver. That also makes reconfiguring a context after making sessions from it impossible to write. `alpn_set_resolver` and the `ALPNProtocolResolver` interface take `val` for the same sharing reason. Closes #69 Closes #83
1 parent 06a6527 commit de8e8c9

7 files changed

Lines changed: 416 additions & 55 deletions

File tree

.release-notes/next-release.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,44 @@ This affected 32-bit builds using OpenSSL 3.x or 4.x. 64-bit builds, LibreSSL bu
5252

5353
These methods now change only the protocol version they name.
5454

55+
## Fix ALPN resolver being collected while still in use
56+
57+
Setting an ALPN protocol resolver on an `SSLContext` and then dropping your own reference to it could crash a server. `SSLContext.alpn_set_resolver` hands the resolver to OpenSSL, which keeps a raw pointer to it and calls it during every incoming connection's handshake. Nothing on the Pony side kept the resolver alive, so the garbage collector was free to collect it while OpenSSL still held the pointer. A peer opening a TLS connection then drove the handshake into freed memory.
58+
59+
The resolver now stays alive on its own. The context keeps it alive, and every session made from the context keeps the context alive, so the resolver lives for as long as any session that can use it. There is nothing you have to hold onto by hand.
60+
61+
## Require a val resolver for alpn_set_resolver
62+
63+
`SSLContext.alpn_set_resolver` now takes an `ALPNProtocolResolver val` where it took an `ALPNProtocolResolver box` before, and the `ALPNProtocolResolver` interface is now `val`. An `SSLContext` is shared across actors, so the resolver can run on any of them, and it has to be immutable and safe to share.
64+
65+
`ALPNStandardProtocolResolver` is already `val`, so code using it needs no change. Code that passes a resolver of its own class must pass it as `val`:
66+
67+
```pony
68+
// Before
69+
ctx.alpn_set_resolver(MyResolver)
70+
71+
// After
72+
ctx.alpn_set_resolver(recover val MyResolver end)
73+
```
74+
75+
## Require a val context for SSLContext.client and server
76+
77+
`SSLContext.client` and `SSLContext.server` now need a `val` context where they worked on a mutable one before. Making a session is what keeps the context, and the ALPN resolver it installed with OpenSSL, alive, and a session can only hold the context if it is `val`.
78+
79+
You configure a context and then make sessions from it, so freeze it to `val` once configuration is done:
80+
81+
```pony
82+
// Before
83+
let ctx = SSLContext
84+
ctx.set_authority(auth_file)?
85+
let session = ctx.client(hostname)?
86+
87+
// After
88+
let ctx =
89+
recover val
90+
SSLContext .> set_authority(auth_file)?
91+
end
92+
let session = ctx.client(hostname)?
93+
```
94+
95+
`SSLContext.server` changes the same way. Configuration methods like `set_authority` still need a mutable context, so do all configuration before freezing. A `val` context cannot be disposed, so a context you make sessions from is freed when the garbage collector collects it rather than when you call `dispose`.

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ All notable changes to this project will be documented in this file. This projec
99
- Fix crash when using a disposed SSL session ([PR #68](https://github.com/ponylang/ssl/pull/68))
1010
- Fix crashes when using a disposed SSL context ([PR #73](https://github.com/ponylang/ssl/pull/73))
1111
- Fix allow_tls_v1, allow_tls_v1_1 and allow_tls_v1_2 on 32-bit platforms ([PR #79](https://github.com/ponylang/ssl/pull/79))
12+
- Fix ALPN resolver being collected while still in use ([PR #81](https://github.com/ponylang/ssl/pull/81))
1213

1314
### Added
1415

1516

1617
### Changed
1718

1819
- Add SSLDisposed to SSLState ([PR #68](https://github.com/ponylang/ssl/pull/68))
20+
- Require a val resolver for alpn_set_resolver ([PR #81](https://github.com/ponylang/ssl/pull/81))
21+
- Require a val context for SSLContext.client and server ([PR #81](https://github.com/ponylang/ssl/pull/81))
1922

2023
## [2.1.0] - 2026-04-20
2124

examples/ssl-client-server-example/ssl-client-server-example.pony

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ actor Main
1414
try
1515
// paths need to be adjusted to a absolute location or you need to run
1616
// the example from a location where this relative path will be valid
17-
recover
17+
recover val
1818
SSLContext
1919
.> set_authority(
2020
FilePath(file_auth, "assets/cert.pem"))?

0 commit comments

Comments
 (0)