-
Notifications
You must be signed in to change notification settings - Fork 8.5k
/
Copy pathProcessList.cpp
342 lines (290 loc) · 13.2 KB
/
ProcessList.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "ProcessList.h"
#include "../host/conwinuserrefs.h"
#include "../host/globals.h"
#include "../host/telemetry.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
using namespace Microsoft::Console::Interactivity;
// Routine Description:
// - Allocates and stores in a list the process information given.
// - Will not create a new entry in the list given information matches a known process. Will instead return existing entry.
// Arguments:
// - dwProcessId - Process ID of connecting client
// - dwThreadId - Thread ID of connecting client
// - ulProcessGroupId - Process Group ID from connecting client (sometimes referred to as parent)
// - pParentProcessData - Used to specify parent while locating appropriate scope of sending control messages
// - ppProcessData - Filled on exit with a pointer to the process handle information. Optional.
// - If not used, return code will specify whether this process is known to the list or not.
// Return Value:
// - S_OK if the process was recorded in the list successfully or already existed.
// - E_FAIL if we're running into an LPC port conflict by nature of the process chain.
// - E_OUTOFMEMORY if there wasn't space to allocate a handle or push it into the list.
[[nodiscard]] HRESULT ConsoleProcessList::AllocProcessData(const DWORD dwProcessId,
const DWORD dwThreadId,
const ULONG ulProcessGroupId,
_In_opt_ ConsoleProcessHandle* const pParentProcessData,
_Outptr_opt_ ConsoleProcessHandle** const ppProcessData)
{
FAIL_FAST_IF(!(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked()));
auto pProcessData = FindProcessInList(dwProcessId);
if (nullptr != pProcessData)
{
// In the GenerateConsoleCtrlEvent it's OK for this process to already have a ProcessData object. However, the other case is someone
// connecting to our LPC port and they should only do that once, so we fail subsequent connection attempts.
if (nullptr == pParentProcessData)
{
return E_FAIL;
// TODO: MSFT: 9574803 - This fires all the time. Did it always do that?
//RETURN_HR(E_FAIL);
}
else
{
if (nullptr != ppProcessData)
{
*ppProcessData = pProcessData;
}
RETURN_HR(S_OK);
}
}
try
{
pProcessData = new ConsoleProcessHandle(dwProcessId,
dwThreadId,
ulProcessGroupId);
// Some applications, when reading the process list through the GetConsoleProcessList API, are expecting
// the returned list of attached process IDs to be from newest to oldest.
// As such, we have to put the newest process into the head of the list.
_processes.push_front(pProcessData);
if (nullptr != ppProcessData)
{
*ppProcessData = pProcessData;
}
}
CATCH_RETURN();
RETURN_HR(S_OK);
}
// Routine Description:
// - This routine frees any per-process data allocated by the console.
// Arguments:
// - pProcessData - Pointer to the per-process data structure.
// Return Value:
// - <none>
void ConsoleProcessList::FreeProcessData(_In_ ConsoleProcessHandle* const pProcessData)
{
FAIL_FAST_IF(!(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked()));
// Assert that the item exists in the list. If it doesn't exist, the end/last will be returned.
FAIL_FAST_IF(!(_processes.cend() != std::find(_processes.cbegin(), _processes.cend(), pProcessData)));
_processes.remove(pProcessData);
delete pProcessData;
}
// Routine Description:
// - Locates a process handle in this list.
// - NOTE: Calling FindProcessInList(0) means you want the root process.
// Arguments:
// - dwProcessId - ID of the process to search for or ROOT_PROCESS_ID to find the root process.
// Return Value:
// - Pointer to the process handle information or nullptr if no match was found.
ConsoleProcessHandle* ConsoleProcessList::FindProcessInList(const DWORD dwProcessId) const
{
auto it = _processes.cbegin();
while (it != _processes.cend())
{
const auto pProcessHandleRecord = *it;
if (ROOT_PROCESS_ID != dwProcessId)
{
if (pProcessHandleRecord->dwProcessId == dwProcessId)
{
return pProcessHandleRecord;
}
}
else
{
if (pProcessHandleRecord->fRootProcess)
{
return pProcessHandleRecord;
}
}
it = std::next(it);
}
return nullptr;
}
// Routine Description:
// - Locates a process handle by the group ID reference.
// Arguments:
// - ulProcessGroupId - Group to search for in the list
// Return Value:
// - Pointer to first matching process handle with given group ID. nullptr if no match was found.
ConsoleProcessHandle* ConsoleProcessList::FindProcessByGroupId(_In_ ULONG ulProcessGroupId) const
{
auto it = _processes.cbegin();
while (it != _processes.cend())
{
const auto pProcessHandleRecord = *it;
if (pProcessHandleRecord->_ulProcessGroupId == ulProcessGroupId)
{
return pProcessHandleRecord;
}
it = std::next(it);
}
return nullptr;
}
// Routine Description:
// - Retrieves the entire list of process IDs that is known to this list.
// - Requires caller to allocate space. If not enough space, pcProcessList will be filled with count of array necessary.
// Arguments:
// - pProcessList - Pointer to buffer to store process IDs. Caller allocated.
// - pcProcessList - On the way in, the length of the buffer given. On the way out, the amount of the buffer used.
// - If buffer was insufficient, filled with necessary buffer size (in elements) on the way out.
// Return Value:
// - S_OK if buffer was filled successfully and resulting count of items is in pcProcessList.
// - E_NOT_SUFFICIENT_BUFFER if the buffer given was too small. Refer to pcProcessList for size requirement.
[[nodiscard]] HRESULT ConsoleProcessList::GetProcessList(_Inout_updates_(*pcProcessList) DWORD* const pProcessList,
_Inout_ size_t* const pcProcessList) const
{
auto hr = S_OK;
const auto cProcesses = _processes.size();
// If we can fit inside the given list space, copy out the data.
if (cProcesses <= *pcProcessList)
{
size_t cFilled = 0;
// Loop over the list of processes and fill in the caller's buffer.
auto it = _processes.cbegin();
while (it != _processes.cend() && cFilled < *pcProcessList)
{
pProcessList[cFilled] = (*it)->dwProcessId;
cFilled++;
it = std::next(it);
}
}
else
{
hr = E_NOT_SUFFICIENT_BUFFER;
}
// Return how many items were copied (or how many values we would need to fit).
*pcProcessList = cProcesses;
return hr;
}
// Routine Description
// - Retrieves TERMINATION_RECORD structures for all processes known in the list (limited if necessary by parameter for group ID)
// - This is designed to copy the data so the global lock can be released while sending control information to attached processes.
// Arguments:
// - dwLimitingProcessId - Optional (0 if unused). Will restrict the return to only processes containing this group ID.
// - fCtrlClose - True if we're about to send a Ctrl Close command to the process. Will increment termination attempt count.
// - prgRecords - Pointer to callee allocated array of termination records. CALLER MUST FREE.
// - pcRecords - Length of records in prgRecords.
// Return Value:
// - S_OK if prgRecords was filled successfully or if no records were found that matched.
// - E_OUTOFMEMORY in a low memory situation.
[[nodiscard]] HRESULT ConsoleProcessList::GetTerminationRecordsByGroupId(const DWORD dwLimitingProcessId,
const bool fCtrlClose,
_Outptr_result_buffer_all_(*pcRecords) ConsoleProcessTerminationRecord** prgRecords,
_Out_ size_t* const pcRecords) const
{
*pcRecords = 0;
try
{
std::deque<std::unique_ptr<ConsoleProcessTerminationRecord>> TermRecords;
// Dig through known processes looking for a match
auto it = _processes.cbegin();
while (it != _processes.cend())
{
const auto pProcessHandleRecord = *it;
// If no limit was specified OR if we have a match, generate a new termination record.
if (0 == dwLimitingProcessId ||
pProcessHandleRecord->_ulProcessGroupId == dwLimitingProcessId)
{
auto pNewRecord = std::make_unique<ConsoleProcessTerminationRecord>();
// If the duplicate failed, the best we can do is to skip including the process in the list and hope it goes away.
LOG_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(),
pProcessHandleRecord->_hProcess.get(),
GetCurrentProcess(),
&pNewRecord->hProcess,
0,
0,
DUPLICATE_SAME_ACCESS));
pNewRecord->dwProcessID = pProcessHandleRecord->dwProcessId;
// If we're hard closing the window, increment the counter.
if (fCtrlClose)
{
pProcessHandleRecord->_ulTerminateCount++;
}
pNewRecord->ulTerminateCount = pProcessHandleRecord->_ulTerminateCount;
TermRecords.push_back(std::move(pNewRecord));
}
it = std::next(it);
}
// From all found matches, convert to C-style array to return
const auto cchRetVal = TermRecords.size();
auto pRetVal = new ConsoleProcessTerminationRecord[cchRetVal];
for (size_t i = 0; i < cchRetVal; i++)
{
pRetVal[i] = *TermRecords.at(i);
}
*prgRecords = pRetVal;
*pcRecords = cchRetVal;
}
CATCH_RETURN();
return S_OK;
}
// Routine Description:
// - Gets the first process in the list.
// - Used for reassigning a new root process.
// TODO: MSFT 9450737 - encapsulate root process logic. https://osgvsowi/9450737
// Arguments:
// - <none>
// Return Value:
// - Pointer to the first item in the list or nullptr if there are no items.
ConsoleProcessHandle* ConsoleProcessList::GetFirstProcess() const
{
if (!_processes.empty())
{
return _processes.front();
}
return nullptr;
}
// Routine Description:
// - Requests that the OS change the process priority for the console and all attached client processes
// Arguments:
// - fForeground - True if console is in foreground and related processes should be prioritized. False if they can be backgrounded/deprioritized.
// Return Value:
// - <none>
// - NOTE: Will attempt to request a change, but it's non fatal if it doesn't work. Failures will be logged to debug channel.
void ConsoleProcessList::ModifyConsoleProcessFocus(const bool fForeground)
{
auto it = _processes.cbegin();
while (it != _processes.cend())
{
const auto pProcessHandle = *it;
if (pProcessHandle->_hProcess)
{
_ModifyProcessForegroundRights(pProcessHandle->_hProcess.get(), fForeground);
}
it = std::next(it);
}
// Do this for conhost.exe itself, too.
_ModifyProcessForegroundRights(GetCurrentProcess(), fForeground);
}
// Routine Description:
// - Specifies that there are no remaining processes
// TODO: This should not be exposed, most likely. Whomever is calling it should join this class.
// Arguments:
// - <none>
// Return Value:
// - True if the list is empty. False if we have known processes.
bool ConsoleProcessList::IsEmpty() const
{
return _processes.empty();
}
// Routine Description:
// - Requests the OS allow the console to set one of its child processes as the foreground window
// Arguments:
// - hProcess - Handle to the process to modify
// - fForeground - True if we're allowed to set it as the foreground window. False otherwise.
// Return Value:
// - <none>
void ConsoleProcessList::_ModifyProcessForegroundRights(const HANDLE hProcess, const bool fForeground) const
{
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->SetForeground(hProcess, fForeground));
}