Skip to content

Cherry-pick upstream PR #5045: fix AudioMediaPlayer use-after-free on async EOF callback - #17

Merged
darshan-verkada merged 1 commit into
intercom/release-2-15from
dev/darshan.patel/cherry-pick-pr-5045
Jul 15, 2026
Merged

Cherry-pick upstream PR #5045: fix AudioMediaPlayer use-after-free on async EOF callback#17
darshan-verkada merged 1 commit into
intercom/release-2-15from
dev/darshan.patel/cherry-pick-pr-5045

Conversation

@darshan-verkada

Copy link
Copy Markdown

Cherry-picks upstream pjproject PR pjsip#5045 onto intercom/release-2-15.

Upstream PR: pjsip#5045

Fixes an AudioMediaPlayer use-after-free where the async EOF event
(eof_cb) could be dispatched on the event-worker thread against an
already-freed C++ object. The destructor now cancels the EOF subscription
synchronously (unsubscribe drains any in-flight callback via the event
manager cb_mutex) before teardown.

The conflict in pjsip/src/pjsua2/media.cpp was resolved to keep the
fork-local id = PJSUA_INVALID_ID; guard (prevents double conf-port
removal) alongside the upstream EOF-cancel logic.

Destroying an AudioMediaPlayer (e.g. mid-playback or right after EOF) can
crash in eof_cb() on a freed object. file_get_frame() publishes an async
PJMEDIA_EVENT_CALLBACK at EOF, dispatched later on the event-worker thread
via file_on_event() -> cb2 (= AudioMediaPlayer::eof_cb, usr_data = this).
~AudioMediaPlayer() -> pjsua_player_destroy() -> pjmedia_port_destroy() only
dec_ref's the port's group lock (the conference bridge still holds a ref and
removes the port asynchronously), so file_on_destroy() -- and its
pjmedia_event_unsubscribe() -- run long after the C++ object is freed. The
queued/in-flight callback then dereferences freed memory.

The faulting address differs every time (jump through a freed vtable),
sharing this stack:

  #0  <freed>
  #1  libpjsua2   AudioMediaPlayer::eof_cb()         media.cpp
  #2  libpjmedia  file_on_event()                    wav_player.c
  #3  libpjmedia  event_mgr_distribute_events()      event.c
  #4  libpjmedia  event_worker_thread()              event.c

Cancel the subscription synchronously while the object is still alive:
clearing the eof cb via set_eof_cb2(port, NULL, NULL) now unsubscribes from
the media event, which blocks on the event manager cb_mutex until any
in-flight callback drains and prevents future dispatch. ~AudioMediaPlayer()
calls it before teardown. cb2 is cleared before the unsubscribe so a
concurrent file_get_frame() at EOF cannot re-arm the subscription in the
gap. cb_mutex is recursive, so destroying from inside onEof2() is safe;
destroying from another thread while holding a lock onEof2() also takes can
deadlock (documented on the destructor).

Applies to both wav_player.c and wav_playlist.c.

Addresses pjsip#4812.
@darshan-verkada
darshan-verkada merged commit 8574fa1 into intercom/release-2-15 Jul 15, 2026
19 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants