Commit b1554b4
committed
Fix use-after-free in IOCP ASIO system
Two related races in the IOCP token mechanism allowed the event to be
freed while something still held a raw pointer to it.
Race 1 — callback vs destroy: pony_asio_event_destroy freed the event
immediately via POOL_FREE regardless of whether IOCP callbacks were
still in flight on thread pool threads. A callback could check the
token's dead flag, see the event alive, and then have the event freed
and pool-recycled before accessing it. The freed memory gets recycled
by the pool allocator, the callback reads garbage, and eventually the
scheduler jumps through a corrupted function pointer (DEP violation).
Race 2 — message vs destroy: when a callback passes the dead check and
calls pony_asio_event_send, the message carries a raw asio_event_t*
pointer. The callback then releases its refcount via iocp_destroy,
which can free the event if it's the last releaser after destroy marked
the token dead. The message is now in the actor's queue with a dangling
pointer. When the actor processes it, _event_notify dereferences freed
memory.
Fix: the token refcount now tracks all outstanding references to the
event, not just in-flight IOCP operations:
- The event itself: refcount starts at 1 (was 0). Destroy releases
this reference instead of freeing the event directly.
- Each in-flight IOCP operation: incremented when posted, decremented
when the callback completes (unchanged).
- Each in-flight message: incremented in pony_asio_event_send,
decremented in handle_message after the behavior dispatch returns.
Whoever decrements the refcount to zero (whether destroy, the last
callback, or the last message dispatch) frees both the event and the
token. The event stays alive until every callback has finished and
every message has been processed.
The previous token mechanism (dead flag + refcount) was designed to
prevent exactly this class of bug but had TOCTOU flaws: checking dead
and then accessing the event was not atomic, and the message pointer
was not covered by the refcount at all.
Surfaced by the Windows TCP open/close stress test (run
https://github.com/ponylang/ponyc/actions/runs/23730792545/job/69123934918)
— release-compiled + --ponynoblock only, ~1 in 10 daily runs.
Closes #50921 parent 0033898 commit b1554b4
File tree
5 files changed
+67
-13
lines changed- .release-notes
- src/libponyrt
- actor
- asio
- lang
5 files changed
+67
-13
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
472 | 473 | | |
473 | 474 | | |
474 | 475 | | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
475 | 500 | | |
476 | 501 | | |
477 | 502 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
39 | 42 | | |
40 | 43 | | |
41 | 44 | | |
| |||
72 | 75 | | |
73 | 76 | | |
74 | 77 | | |
75 | | - | |
76 | 78 | | |
77 | 79 | | |
78 | 80 | | |
| |||
90 | 92 | | |
91 | 93 | | |
92 | 94 | | |
93 | | - | |
94 | | - | |
95 | 95 | | |
96 | | - | |
97 | | - | |
98 | | - | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
99 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
100 | 107 | | |
101 | 108 | | |
102 | 109 | | |
| |||
162 | 169 | | |
163 | 170 | | |
164 | 171 | | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
165 | 179 | | |
166 | 180 | | |
167 | 181 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
19 | 24 | | |
20 | 25 | | |
21 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
228 | 228 | | |
229 | 229 | | |
230 | 230 | | |
231 | | - | |
| 231 | + | |
232 | 232 | | |
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
236 | | - | |
| 236 | + | |
237 | 237 | | |
| 238 | + | |
| 239 | + | |
238 | 240 | | |
| 241 | + | |
239 | 242 | | |
240 | 243 | | |
241 | 244 | | |
242 | 245 | | |
243 | 246 | | |
244 | 247 | | |
| 248 | + | |
245 | 249 | | |
246 | 250 | | |
247 | 251 | | |
248 | | - | |
| 252 | + | |
249 | 253 | | |
250 | 254 | | |
251 | 255 | | |
| |||
265 | 269 | | |
266 | 270 | | |
267 | 271 | | |
| 272 | + | |
268 | 273 | | |
269 | 274 | | |
270 | 275 | | |
271 | | - | |
| 276 | + | |
272 | 277 | | |
273 | 278 | | |
274 | 279 | | |
| |||
0 commit comments