Skip to content

Fix mstscax control instance and TS property-set reference leaks - #193

Merged
Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
masterfrom
copilot/fix-rdp-control-leak
Sep 24, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
masterfrom
copilot/fix-rdp-control-leak

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Summary

This closes the primary root cause of RDMW-24263 / the forum report ("Embedded RDP sessions leak the ActiveX control after the tab is closed"): the wrapped mstscax control instance itself was never released, so every closed embedded session permanently leaked 2 native threads (mstscax!CSND::SND_Main, mstscax!CRCV::RCVMain) and ~100 handles.

It also fixes a refcount-imbalance bug that is a strong suspect for the intermittent native heap-corruption crash (ntdll.dll 0xC0000374) observed during teardown/forced-GC testing on the RDM side.

Bugs fixed

  1. CClassFactory::CreateInstance leaked the real control's reference (primary leak).
    IClassFactory::CreateInstance returns the real mstscax control object with refcount 1, owned by the caller. CMsRdpClient's constructor takes its own AddRef() on that pointer, but the factory never released its copy after wrapping — so the real control's refcount never reached 0 when the CMsRdpClient wrapper was later destroyed. Nothing ever drove the control's core shutdown, matching the reported symptom exactly (send/receive threads parked forever in CTSThread::ThreadMsgLoop).

  2. CMsRdpClient destructor released its wrapper objects after the control interfaces.
    m_pMsRdpExtendedSettings and m_pMsRdpExInstance hold references into control-internal objects whose lifetime is owned by the control. Reordered so wrappers are released first, before the control interfaces.

  3. CMsRdpPropertySet released a reference it never took.
    It stored the internal ITSPropertySet pointer (for core/base/transport TS property sets) without an AddRef, but its destructor unconditionally called Release() on it anyway — decrementing a refcount it never incremented, which could prematurely free a control-internal object still referenced elsewhere. Added the missing AddRef() in the constructor to balance the destructor's Release().

Verification

How to verify the primary leak fix manually

Open and close N embedded RDP sessions, take a full-memory dump, count threads whose stack contains mstscax!CSND::SND_Main. Before this fix: N threads remain. After: 0.

Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com

- CClassFactory::CreateInstance held onto the reference IClassFactory::
  CreateInstance returns for the real mstscax control after wrapping it
  in CMsRdpClient, which already takes its own AddRef in its
  constructor. The factory never released its copy, so the real
  control's refcount never reached zero when the wrapper was
  destroyed. This is the primary reported leak: mstscax!CSND::SND_Main
  and mstscax!CRCV::RCVMain threads (and ~100 handles) parked forever
  per closed embedded RDP session.

- CMsRdpClient destructor released its wrapper objects
  (m_pMsRdpExtendedSettings, m_pMsRdpExInstance) after releasing the
  underlying control interfaces, even though those wrappers hold
  references into control-internal objects owned by the control.
  Reordered to release wrappers first.

- CMsRdpPropertySet stored the internal ITSPropertySet/TS core-props
  IUnknown it was given without taking its own reference, but its
  destructor unconditionally released it anyway, decrementing a
  refcount it never incremented. This could prematurely free
  control-internal property-set objects still in use elsewhere,
  producing heap corruption during teardown. Added the missing AddRef
  to balance the destructor Release.

Verified: full x64 build, ctest 31/31 passing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 24, 2026 18:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The changes correctly balance COM ownership and preserve dependent-object lifetimes during teardown.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes COM reference-count imbalances that prevented embedded RDP controls and internal property sets from being released correctly.

Changes:

  • Balances CMsRdpPropertySet ownership with AddRef.
  • Releases wrapper objects before control interfaces.
  • Releases the factory-owned control reference after wrapping.
File Description
dll/​RdpSettings.cpp Balances property-set reference ownership.
dll/​MsRdpClient.cpp Corrects control teardown order and factory reference release.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit a7aa301 into master Sep 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants