@samitouri / QOSAMI-WSL / commits / 8cb27f14

Fix use-after-free in WSLCContainerImpl exec-process teardown (#40822)

yao-msft committed Jun 17, 2026 at 12:18 UTC 8cb27f14dd2e8f11bff699c32726bc299f95ae04
4 files changed +23 -34
src/windows/wslcsession/WSLCContainer.cpp
+20 -19
@@ -588,7 +588,10 @@ WSLCContainerImpl::~WSLCContainerImpl()
588
589 for (auto& process : processes)
590 {
591 - process->OnContainerReleased();
591 + if (auto control = process.lock())
592 + {
593 + control->OnContainerReleased();
594 + }
595 }
596
597 m_containerEvents.Reset();
@@ -603,16 +606,6 @@ WSLCContainerImpl::~WSLCContainerImpl()
606 }
607 }
608
606 -void WSLCContainerImpl::OnProcessReleased(DockerExecProcessControl* process) noexcept
607 -{
608 - std::lock_guard processesLock{m_processesLock};
609 -
610 - auto remove = std::ranges::remove_if(m_processes, [process](const auto* e) { return e == process; });
611 - WI_ASSERT(remove.size() == 1);
612 -
613 - m_processes.erase(remove.begin(), remove.end());
614 -}
615 -
609 void WSLCContainerImpl::SetExitCode(int ExitCode) noexcept
610 {
611 std::lock_guard processesLock{m_processesLock};
@@ -1225,13 +1218,15 @@ void WSLCContainerImpl::Exec(const WSLCProcessOptions* Options, const WSLCProces
1218 io = CreateRelayedProcessIO(std::move(stream), Options->Flags);
1219 }
1220
1228 - auto control = std::make_unique<DockerExecProcessControl>(*this, result.Id, m_dockerClient, m_eventTracker);
1221 + auto control = std::make_shared<DockerExecProcessControl>(*this, result.Id, m_dockerClient, m_eventTracker);
1222
1223 {
1224 std::lock_guard processesLock{m_processesLock};
1225
1233 - // Store a non owning reference to the process.
1234 - m_processes.push_back(control.get());
1226 + // Drop entries for execs that have since been released, then store a non-owning weak
1227 + // reference. The owning shared_ptr is moved into the COM WSLCProcess returned below.
1228 + std::erase_if(m_processes, [](const auto& weak) { return weak.expired(); });
1229 + m_processes.push_back(control);
1230 }
1231
1232 // Poll for the exec'd process to either be running, or failed.
@@ -2094,16 +2089,22 @@ void WSLCContainerImpl::UnmapPorts()
2089
2090 __requires_exclusive_lock_held(m_lock) void WSLCContainerImpl::ReleaseProcesses()
2091 {
2097 - std::lock_guard processesLock{m_processesLock};
2092 + // Snapshot under the lock, then notify outside it, pinning each control via lock() first
2093 + decltype(m_processes) processes;
2094 + {
2095 + std::lock_guard processesLock{m_processesLock};
2096 + processes = std::exchange(m_processes, {});
2097 + }
2098
2099 // Notify all processes that the container has exited.
2100 // The exec callback isn't always sent to execed processes, so do this to avoid 'stuck' processes.
2101 - for (auto& process : m_processes)
2101 + for (auto& process : processes)
2102 {
2103 - process->OnContainerReleased();
2103 + if (auto control = process.lock())
2104 + {
2105 + control->OnContainerReleased();
2106 + }
2107 }
2105 -
2106 - m_processes.clear();
2108 }
2109
2110 __requires_exclusive_lock_held(m_lock) void WSLCContainerImpl::ReleaseRuntimeResources()
src/windows/wslcsession/WSLCContainer.h
+1 -3
@@ -119,8 +119,6 @@ public:
119
120 __requires_lock_held(m_lock) void Transition(WSLCContainerState State, std::optional<std::uint64_t> stateChangedAt = std::nullopt) noexcept;
121
122 - void OnProcessReleased(DockerExecProcessControl* process) noexcept;
123 -
122 const std::string& ID() const noexcept;
123
124 // Returns the container flags used to decide whether to
@@ -186,7 +184,7 @@ private:
184 WSLCProcessFlags m_initProcessFlags{};
185 WSLCContainerFlags m_containerFlags{};
186 mutable std::mutex m_processesLock;
189 - __guarded_by(m_processesLock) std::vector<DockerExecProcessControl*> m_processes;
187 + __guarded_by(m_processesLock) std::vector<std::weak_ptr<DockerExecProcessControl>> m_processes;
188 __guarded_by(m_processesLock) Microsoft::WRL::ComPtr<IWSLCProcess> m_initProcess;
189 __guarded_by(m_processesLock) DockerContainerProcessControl* m_initProcessControl = nullptr;
190
src/windows/wslcsession/WSLCProcessControl.cpp
+1 -10
@@ -116,15 +116,6 @@ DockerExecProcessControl::DockerExecProcessControl(
116 {
117 }
118
119 -DockerExecProcessControl::~DockerExecProcessControl()
120 -{
121 - std::lock_guard lock{m_lock};
122 - if (m_container != nullptr)
123 - {
124 - m_container->OnProcessReleased(this);
125 - }
126 -}
127 -
119 int DockerExecProcessControl::GetPid() const
120 {
121 std::lock_guard lock{m_lock};
@@ -270,4 +261,4 @@ void VMProcessControl::OnVmTerminated()
261 m_exitedCode = 128 + WSLCSignalSIGKILL;
262 m_exitEvent.SetEvent();
263 }
273 -}
\ No newline at end of file
264 +}
src/windows/wslcsession/WSLCProcessControl.h
+1 -2
@@ -63,7 +63,6 @@ class DockerExecProcessControl : public WSLCProcessControl
63 {
64 public:
65 DockerExecProcessControl(WSLCContainerImpl& Container, const std::string& Id, DockerHTTPClient& DockerClient, DockerEventTracker& EventTracker);
66 - ~DockerExecProcessControl();
66 void Signal(int Signal) override;
67 void ResizeTty(ULONG Rows, ULONG Columns) override;
68 int GetPid() const override;
@@ -103,4 +102,4 @@ private:
102 WSLCVirtualMachine* m_vm{};
103 };
104
106 -} // namespace wsl::windows::service::wslc
\ No newline at end of file
105 +} // namespace wsl::windows::service::wslc