Skip to content

AudioMediaPort callback teardown race and stale detached frame metadata #5308

Closed

Description

@arch7tect

Describe the bug

AudioMediaPort clears the callback back-pointer under its group lock in the base destructor. That is too late to protect callback-visible members of a derived class during teardown: a callback may still be in progress, or dispatch may still be possible, while derived state is being destroyed. There is no exposed callback fence for a C++ subclass to drain callbacks before teardown begins.

A related problem exists after the back-pointer is cleared: get_frame() returns PJ_SUCCESS without setting the caller's frame->type or frame->size. Stale AUDIO/nonzero-size metadata can therefore survive the detached read.

Source: AudioMediaPort implementation at the tested master.

Steps to reproduce

The lifetime ordering to guard against is:

  1. Create a derived AudioMediaPort whose get/put callback uses derived state.
  2. Hold an in-flight callback while another thread initiates teardown.
  3. Derived-member teardown occurs before AudioMediaPort::~AudioMediaPort() reaches its callback-detachment lock.

For the detached-frame problem, invoke the raw port's get_frame() after the back-pointer has been cleared, with an input frame seeded as AUDIO and nonzero size. The current NULL-back-pointer branch leaves both fields unchanged.

Draft #5307 includes an isolated regression test using the actual raw media port, without a sound device or SIP call. It covers held get and put callbacks, an explicit pre-teardown fence, completion ordering, repeated detach, and stale-frame sentinels. Restoring the old frame-return behavior makes the new test fail. Removing the fence lock also makes it fail. These are targeted negative controls; an unpatched sanitizer-detected use-after-free is not claimed.

Expected: an explicit way to fence callbacks while the complete object/state is valid, and detached reads that return NONE with size zero. Recursive lock owners cannot wait for themselves; this is not a request for generic concurrent destructor safety.

PJSIP version

Upstream master a67b8e81b0024b993f47e463c01c67c25cda116f.

Context

Validated on macOS arm64 with Apple Clang 21. GNU make and CMake shared PJSUA2 tests pass with #5307. The production change is limited to an additive protected C++ fence and detached frame metadata; no application-specific code is involved.

Log, call stack, etc

The stale-frame negative control fails at:

AudioMediaPort: frame.type == PJMEDIA_FRAME_TYPE_NONE

The proposed fix and detailed validation are in #5307.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions