Where
bindings/typescript/host-support/src/mln_abi.c, mln_abi_queue_push.
What
The notifier and its user data are read under the queue lock, then called
outside it:
notify = owner->notify;
notify_data = owner->notify_data;
}
mln_abi_queue_release();
...
/* Outside the lock: the notifier reaches the host's runtime, which must not
* be able to deadlock against a producer. */
if (notify != NULL) {
notify(notify_data);
Calling outside the lock is deliberate and correct — a notifier reaches the
host's runtime and must not be able to deadlock against a producer. The gap is
that nothing keeps notify_data alive across that window.
A host that stops notifications between the read and the call drops the state
notify_data points at while a producer is about to dereference it. On the
Node-API transport that state is a boxed threadsafe function; the window is
small but a producer thread is MapLibre's, not the host's, so nothing
serialises them.
Impact
Use-after-free reachable from ordinary teardown: clearing a callback, closing
a Maplibre context, or a worker exiting while MapLibre is still delivering.
Not observed in the suite — found by reading during the owner-token work
(#PR). It predates that change and has the same shape now, per owner rather
than process-global.
Scope
TypeScript only. This queue lives in the TypeScript host-support shim; the
shared src/c_api/callback_adapter.cpp has no equivalent notifier, so Dart and
the other bindings that use the C API directly are unaffected.
Fixing it
Needs the notifier to outlive the call — refcount the registration and release
it after notify returns, or hold a grace period on stop that waits for
in-flight pushes. Out of scope for the change that found it.
Where
bindings/typescript/host-support/src/mln_abi.c,mln_abi_queue_push.What
The notifier and its user data are read under the queue lock, then called
outside it:
Calling outside the lock is deliberate and correct — a notifier reaches the
host's runtime and must not be able to deadlock against a producer. The gap is
that nothing keeps
notify_dataalive across that window.A host that stops notifications between the read and the call drops the state
notify_datapoints at while a producer is about to dereference it. On theNode-API transport that state is a boxed threadsafe function; the window is
small but a producer thread is MapLibre's, not the host's, so nothing
serialises them.
Impact
Use-after-free reachable from ordinary teardown: clearing a callback, closing
a
Maplibrecontext, or a worker exiting while MapLibre is still delivering.Not observed in the suite — found by reading during the owner-token work
(#PR). It predates that change and has the same shape now, per owner rather
than process-global.
Scope
TypeScript only. This queue lives in the TypeScript host-support shim; the
shared
src/c_api/callback_adapter.cpphas no equivalent notifier, so Dart andthe other bindings that use the C API directly are unaffected.
Fixing it
Needs the notifier to outlive the call — refcount the registration and release
it after
notifyreturns, or hold a grace period on stop that waits forin-flight pushes. Out of scope for the change that found it.