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