mcp-pacemaker 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/CONTRIBUTING.md +39 -13
  3. package/README.md +255 -8
  4. package/THIRD_PARTY_NOTICES.txt +1 -28
  5. package/autostart/windows/account-task.ps1 +167 -0
  6. package/autostart/windows/inspect-task.ps1 +42 -0
  7. package/autostart/windows/inspect-worker-process-sessions.ps1 +155 -0
  8. package/autostart/windows/legacy-task.ps1 +301 -0
  9. package/autostart/windows/register-task.ps1 +11 -2
  10. package/bin/account-task-adapter.mjs +47 -0
  11. package/bin/account-upgrade-controller.mjs +198 -0
  12. package/bin/account-upgrade-worker.mjs +134 -0
  13. package/bin/account-worker-session.mjs +215 -0
  14. package/bin/cli-dispatch.mjs +154 -0
  15. package/bin/cli-main.mjs +1007 -0
  16. package/bin/cli-selection.mjs +43 -0
  17. package/bin/cli.mjs +7 -876
  18. package/bin/legacy-1.3.json +32 -0
  19. package/bin/legacy-installation.mjs +217 -0
  20. package/bin/legacy-task.mjs +52 -0
  21. package/bin/legacy-upgrade.mjs +471 -0
  22. package/bin/managed-runtime.mjs +170 -0
  23. package/bin/managed-state.mjs +257 -0
  24. package/bin/managed-upgrade.mjs +491 -0
  25. package/bin/mcp-bridge.mjs +132 -4
  26. package/bin/pooling-batches.mjs +13 -0
  27. package/bin/pooling-writer.mjs +4 -0
  28. package/bin/prewarm.mjs +33 -21
  29. package/bin/runtime-context.mjs +100 -0
  30. package/bin/service-control.mjs +27 -4
  31. package/bin/stable-launcher.mjs +54 -0
  32. package/bin/task-only-controller.mjs +226 -0
  33. package/bin/task-scope.mjs +65 -0
  34. package/bin/task-transaction-protocol.mjs +173 -0
  35. package/bin/top.mjs +22 -10
  36. package/bin/update-check.mjs +13 -2
  37. package/bin/upgrade-autostart.mjs +47 -0
  38. package/bin/upgrade-capability.json +8 -0
  39. package/bin/upgrade-dependencies.mjs +122 -0
  40. package/bin/upgrade-package.mjs +203 -0
  41. package/bin/windows-legacy/LegacyProcessBroker.build.json +42 -0
  42. package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
  43. package/bin/windows-legacy/src/AssemblyInfo.cs +8 -0
  44. package/bin/windows-legacy/src/LegacyNative.cs +783 -0
  45. package/bin/windows-legacy/src/LegacyProcessBroker.cs +841 -0
  46. package/bin/windows-legacy-process.mjs +241 -0
  47. package/bin/windows-lifetime/ProcessLifetimeHelper.build.json +38 -0
  48. package/bin/windows-lifetime/ProcessLifetimeHelper.exe +0 -0
  49. package/bin/windows-lifetime/src/AssemblyInfo.cs +8 -0
  50. package/bin/windows-lifetime/src/ProcessLifetimeHelper.cs +468 -0
  51. package/bin/windows-process-lifetime.mjs +218 -0
  52. package/bin/windows-task-channel/TaskChannelGuard.build.json +45 -0
  53. package/bin/windows-task-channel/TaskChannelGuard.exe +0 -0
  54. package/bin/windows-task-channel/src/AssemblyInfo.cs +8 -0
  55. package/bin/windows-task-channel/src/ChannelCliContext.cs +210 -0
  56. package/bin/windows-task-channel/src/ChannelFiles.cs +231 -0
  57. package/bin/windows-task-channel/src/ChannelNative.cs +477 -0
  58. package/bin/windows-task-channel/src/ChannelSession.cs +203 -0
  59. package/bin/windows-task-channel/src/TaskChannelGuard.cs +686 -0
  60. package/bin/windows-task-channel.mjs +335 -0
  61. package/docs/account-upgrades.md +205 -0
  62. package/docs/execution-context.md +113 -0
  63. package/package.json +12 -5
  64. package/supervisor/supervise.mjs +226 -40
  65. package/tools/account-upgrade/qualify-two-account.ps1 +66 -0
  66. package/tools/windows-legacy/README.md +225 -0
  67. package/tools/windows-legacy/build.ps1 +122 -0
  68. package/tools/windows-legacy/inventory.mjs +90 -0
  69. package/tools/windows-process-lifetime/README.md +119 -0
  70. package/tools/windows-process-lifetime/build.ps1 +122 -0
  71. package/tools/windows-process-lifetime/inventory.mjs +86 -0
  72. package/tools/windows-task-channel/README.md +329 -0
  73. package/tools/windows-task-channel/build.ps1 +107 -0
  74. package/tools/windows-task-channel/inventory.mjs +117 -0
  75. package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -28
  76. package/ui/dist/assets/{index-CgPruWYi.js → index-CcwMo2sI.js} +1 -1
  77. package/ui/dist/assets/index-PVcuDNrN.css +1 -0
  78. package/ui/dist/index.html +2 -2
  79. package/ui/dist/third-party-manifest.json +37 -25
  80. package/ui/dist/assets/index-CaLPg0c-.css +0 -1
@@ -0,0 +1,783 @@
1
+ using System;
2
+ using System.Collections.Concurrent;
3
+ using System.Collections.Generic;
4
+ using System.ComponentModel;
5
+ using System.Diagnostics;
6
+ using System.Globalization;
7
+ using System.IO;
8
+ using System.Management;
9
+ using System.Runtime.InteropServices;
10
+ using System.Security.Cryptography;
11
+ using System.Security.Principal;
12
+ using System.Text;
13
+ using System.Threading;
14
+ using System.Web.Script.Serialization;
15
+ using Microsoft.Win32.SafeHandles;
16
+
17
+ internal sealed class LegacyNative
18
+ {
19
+ internal string Hash(byte[] bytes)
20
+ {
21
+ using (var hash = SHA256.Create())
22
+ {
23
+ return BitConverter.ToString(hash.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant();
24
+ }
25
+ }
26
+
27
+ internal string HashFile(string path)
28
+ {
29
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
30
+ using (var hash = SHA256.Create())
31
+ {
32
+ return BitConverter.ToString(hash.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
33
+ }
34
+ }
35
+
36
+ internal long Creation(SafeProcess process)
37
+ {
38
+ long created;
39
+ long exit;
40
+ long kernel;
41
+ long user;
42
+ Require(Api.GetProcessTimes(process, out created, out exit, out kernel, out user), LegacyFailureApi.GetProcessTimes);
43
+ return created;
44
+ }
45
+
46
+ internal bool Alive(SafeProcess process)
47
+ {
48
+ uint result = Api.WaitForSingleObject(process, 0);
49
+ Require(result != 0xffffffff, LegacyFailureApi.WaitForSingleObject);
50
+ return result == 0x102;
51
+ }
52
+
53
+ internal string Image(SafeProcess process)
54
+ {
55
+ var value = new StringBuilder(32768);
56
+ int size = value.Capacity;
57
+ Require(Api.QueryFullProcessImageNameW(process, 0, value, ref size), LegacyFailureApi.QueryFullProcessImageName);
58
+ return value.ToString();
59
+ }
60
+
61
+ internal string Owner(SafeProcess process)
62
+ {
63
+ SafeProcess token;
64
+ Require(Api.OpenProcessToken(process, 8, out token), LegacyFailureApi.OpenProcessToken);
65
+ using (token)
66
+ {
67
+ int size;
68
+ Api.GetTokenInformation(token, 1, IntPtr.Zero, 0, out size);
69
+ if (size <= 0 ||
70
+ size > 65536) throw new InvalidOperationException("Token bounds");
71
+ var buffer = Marshal.AllocHGlobal(size);
72
+ try
73
+ {
74
+ Require(Api.GetTokenInformation(token, 1, buffer, size, out size), LegacyFailureApi.GetTokenInformation);
75
+ return new SecurityIdentifier(Marshal.ReadIntPtr(buffer)).Value;
76
+ }
77
+ finally { Marshal.FreeHGlobal(buffer); }
78
+ }
79
+ }
80
+
81
+ internal int Parent(SafeProcess process)
82
+ {
83
+ var information = new Api.BasicInformation();
84
+ int returned;
85
+ int status = Api.NtQueryInformationProcess(process, 0, out information,
86
+ Marshal.SizeOf(information), out returned);
87
+ if (status != 0)
88
+ {
89
+ var error = new Win32Exception(status);
90
+ TagApi(error, LegacyFailureApi.NtQueryInformationProcess);
91
+ throw error;
92
+ }
93
+ return checked((int)information.ParentId.ToInt64());
94
+ }
95
+
96
+ internal string[] Arguments(int processId, SafeProcess held)
97
+ {
98
+ using (var search = Search("ProcessId = " + processId.ToString(CultureInfo.InvariantCulture),
99
+ "ProcessId,CreationDate,CommandLine"))
100
+ using (var rows = search.Get())
101
+ {
102
+ if (rows.Count != 1) throw new InvalidOperationException("Process query changed");
103
+ foreach (ManagementObject row in rows)
104
+ using (row)
105
+ {
106
+ var command = row["CommandLine"] as string;
107
+ var date = row["CreationDate"] as string;
108
+ if (string.IsNullOrEmpty(command) ||
109
+ command.Length > 32768 ||
110
+ string.IsNullOrEmpty(date)) throw new InvalidOperationException("Process query unavailable");
111
+ long queried = ManagementDateTimeConverter.ToDateTime(date).ToUniversalTime().ToFileTimeUtc();
112
+ if (Math.Abs(queried - Creation(held)) > 10 ||
113
+ !Alive(held)) throw new InvalidOperationException("Process query generation changed");
114
+ int count;
115
+ var pointer = Api.CommandLineToArgvW(command, out count);
116
+ Require(pointer != IntPtr.Zero);
117
+ try
118
+ {
119
+ if (count < 1 ||
120
+ count > 1024) throw new InvalidOperationException("Argument bounds");
121
+ var args = new string[count];
122
+ for (int index = 0; index < count; index++)
123
+ args[index] = Marshal.PtrToStringUni(Marshal.ReadIntPtr(pointer, index * IntPtr.Size));
124
+ return args;
125
+ }
126
+ finally { Api.LocalFree(pointer); }
127
+ }
128
+ }
129
+ throw new InvalidOperationException("Process query missing");
130
+ }
131
+
132
+ internal ManagementObjectSearcher Search(string filter, string fields)
133
+ {
134
+ var options = new EnumerationOptions { Timeout = TimeSpan.FromSeconds(2), ReturnImmediately = false };
135
+ return new ManagementObjectSearcher(@"root\cimv2",
136
+ "SELECT " + fields + " FROM Win32_Process WHERE " + filter, options);
137
+ }
138
+
139
+ internal int Listener(int port)
140
+ {
141
+ int size = 0;
142
+ uint result = Api.GetExtendedTcpTable(IntPtr.Zero, ref size, false, 2, 3, 0);
143
+ if (result != 122 ||
144
+ size < 4 ||
145
+ size > 16 * 1024 * 1024)
146
+ {
147
+ var error = new Win32Exception(unchecked((int)result));
148
+ TagApi(error, LegacyFailureApi.GetExtendedTcpTableSize);
149
+ throw error;
150
+ }
151
+ var buffer = Marshal.AllocHGlobal(size);
152
+ try
153
+ {
154
+ result = Api.GetExtendedTcpTable(buffer, ref size, false, 2, 3, 0);
155
+ if (result != 0)
156
+ {
157
+ var error = new Win32Exception(unchecked((int)result));
158
+ TagApi(error, LegacyFailureApi.GetExtendedTcpTableRows);
159
+ throw error;
160
+ }
161
+ int count = Marshal.ReadInt32(buffer);
162
+ if (count < 0 ||
163
+ count > (size - 4) / 24) throw new InvalidOperationException("TCP bounds");
164
+ int found = 0;
165
+ for (int index = 0; index < count; index++)
166
+ {
167
+ var row = IntPtr.Add(buffer, 4 + index * 24);
168
+ int rowPort = (Marshal.ReadByte(row, 8) << 8) | Marshal.ReadByte(row, 9);
169
+ uint address = unchecked((uint)Marshal.ReadInt32(row, 4));
170
+ if (rowPort != port ||
171
+ (address != 0x0100007f && address != 0)) continue;
172
+ if (address == 0 ||
173
+ found != 0) throw new InvalidOperationException("Listener ambiguity");
174
+ found = Marshal.ReadInt32(row, 20);
175
+ }
176
+ if (found <= 0) throw new InvalidOperationException("Loopback listener missing");
177
+ return found;
178
+ }
179
+ finally { Marshal.FreeHGlobal(buffer); }
180
+ }
181
+
182
+ internal void Require(bool value)
183
+ {
184
+ if (!value) throw new Win32Exception(Marshal.GetLastWin32Error());
185
+ }
186
+
187
+ internal void Require(bool value, LegacyFailureApi api)
188
+ {
189
+ if (value) return;
190
+ int code = Marshal.GetLastWin32Error();
191
+ var error = new Win32Exception(code);
192
+ TagApi(error, api);
193
+ throw error;
194
+ }
195
+
196
+ internal void TagApi(Exception error, LegacyFailureApi api)
197
+ {
198
+ try
199
+ {
200
+ if (!error.Data.Contains("LegacyFailureApi")) error.Data["LegacyFailureApi"] = api;
201
+ }
202
+ catch { /* Diagnostic metadata must never replace the original failure. */ }
203
+ }
204
+
205
+ internal void TagFailure(Exception error, LegacyFailureStage substage, int? pid = null,
206
+ long? creationTime = null, int? actualParentPid = null, int? expectedParentPid = null,
207
+ string expectedParentCreationTime = null, bool? alive = null, bool? parentAlive = null,
208
+ bool? ownerMatched = null)
209
+ {
210
+ try
211
+ {
212
+ if (error.Data.Contains("LegacyFailureDetails")) return;
213
+ var api = error.Data["LegacyFailureApi"] is LegacyFailureApi
214
+ ? (LegacyFailureApi)error.Data["LegacyFailureApi"] : LegacyFailureApi.Unknown;
215
+ error.Data["LegacyFailureDetails"] = new LegacyFailureDetails(api, substage, pid, creationTime,
216
+ actualParentPid, expectedParentPid, expectedParentCreationTime, alive, parentAlive, ownerMatched);
217
+ }
218
+ catch { /* Use only previously observed values; no recovery query or fallback. */ }
219
+ }
220
+
221
+ internal LegacyFailureDetails FailureDetails(Exception error)
222
+ {
223
+ try
224
+ {
225
+ var details = error.Data["LegacyFailureDetails"] as LegacyFailureDetails;
226
+ if (details != null) return details;
227
+ if (error.Data["LegacyFailureApi"] is LegacyFailureApi)
228
+ return new LegacyFailureDetails((LegacyFailureApi)error.Data["LegacyFailureApi"],
229
+ LegacyFailureStage.Unknown, null, null, null, null, null, null, null, null);
230
+ }
231
+ catch { /* Reporting cannot change refusal behavior. */ }
232
+ return null;
233
+ }
234
+
235
+ internal object Memory()
236
+ {
237
+ var counters = new Api.MemoryCounters();
238
+ uint size = (uint)Marshal.SizeOf(counters);
239
+ if (!Api.GetProcessMemoryInfo(new IntPtr(-1), out counters, size))
240
+ return new { verified = false, nativeError = Marshal.GetLastWin32Error() };
241
+ return new { verified = true, privateBytes = counters.PrivateUsage.ToUInt64(),
242
+ peakPrivateCommitBytes = counters.PeakPagefileUsage.ToUInt64() };
243
+ }
244
+ }
245
+
246
+ internal enum LegacyFailureApi
247
+ {
248
+ Unknown, OpenProcessQuery, OpenProcessTerminate, GetProcessTimes, WaitForSingleObject,
249
+ QueryFullProcessImageName, OpenProcessToken, GetTokenInformation, NtQueryInformationProcess,
250
+ GetExtendedTcpTableSize, GetExtendedTcpTableRows, CreateToolhelp32Snapshot,
251
+ FileOpenRead, GetFileInformationByHandle, WmiConnect, WmiQuery, CommandLineToArgv
252
+ }
253
+
254
+ internal enum LegacyFailureStage
255
+ {
256
+ Unknown, CaptureOpenQuery, CaptureIdentity, CaptureParent, CaptureParentLiveness, CaptureOwner, CaptureOpenTerminate,
257
+ CaptureImage, CaptureImageFacts, Snapshot, ArgumentConnect,
258
+ ArgumentQuery, ArgumentIdentity
259
+ }
260
+
261
+ [Serializable]
262
+ internal sealed class LegacyFailureDetails
263
+ {
264
+ public string api { get; private set; }
265
+ public string substage { get; private set; }
266
+ public int? requestedPid { get; private set; }
267
+ public string creationTime { get; private set; }
268
+ public bool generationKnown { get; private set; }
269
+ public int? actualParentPid { get; private set; }
270
+ public int? expectedParentPid { get; private set; }
271
+ public string expectedParentCreationTime { get; private set; }
272
+ public bool? alive { get; private set; }
273
+ public bool? parentAlive { get; private set; }
274
+ public bool? ownerMatched { get; private set; }
275
+ public string observation { get { return "stored-observations-not-fresh-proof"; } }
276
+
277
+ internal LegacyFailureDetails(LegacyFailureApi api, LegacyFailureStage substage, int? pid,
278
+ long? creationTime, int? actualParentPid, int? expectedParentPid, string expectedParentCreationTime,
279
+ bool? alive, bool? parentAlive, bool? ownerMatched)
280
+ {
281
+ this.api = api.ToString();
282
+ this.substage = substage.ToString();
283
+ requestedPid = pid;
284
+ this.creationTime = creationTime.HasValue ? creationTime.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : null;
285
+ generationKnown = creationTime.HasValue;
286
+ this.actualParentPid = actualParentPid;
287
+ this.expectedParentPid = expectedParentPid;
288
+ this.expectedParentCreationTime = expectedParentCreationTime;
289
+ this.alive = alive;
290
+ this.parentAlive = parentAlive;
291
+ this.ownerMatched = ownerMatched;
292
+ }
293
+ }
294
+
295
+ internal sealed class SafeProcess : SafeHandleZeroOrMinusOneIsInvalid
296
+ {
297
+ public SafeProcess() : base(true) { }
298
+ protected override bool ReleaseHandle() { return Api.CloseHandle(handle); }
299
+ }
300
+
301
+ internal sealed class LegacySnapshot
302
+ {
303
+ private readonly LegacyNative diagnostic = new LegacyNative();
304
+ private readonly Dictionary<uint, List<uint>> children = new Dictionary<uint, List<uint>>();
305
+ private readonly Dictionary<uint, uint> parents = new Dictionary<uint, uint>();
306
+
307
+ internal void Read(Action check)
308
+ {
309
+ int size = Marshal.SizeOf(typeof(Api.ProcessEntryLayout));
310
+ int pidOffset = Marshal.OffsetOf(typeof(Api.ProcessEntryLayout), "ProcessId").ToInt32();
311
+ int parentOffset = Marshal.OffsetOf(typeof(Api.ProcessEntryLayout), "ParentId").ToInt32();
312
+ if (size != (IntPtr.Size == 8 ? 568 : 556) ||
313
+ pidOffset != 8 ||
314
+ parentOffset != (IntPtr.Size == 8 ? 32 : 24))
315
+ throw new InvalidOperationException("Snapshot schema unavailable");
316
+ var cleared = new byte[size];
317
+ var buffer = Marshal.AllocHGlobal(size);
318
+ SafeSnapshot snapshot = null;
319
+ try
320
+ {
321
+ check();
322
+ snapshot = Api.CreateToolhelp32Snapshot(2, 0);
323
+ if (snapshot.IsInvalid)
324
+ {
325
+ var error = new Win32Exception(Marshal.GetLastWin32Error());
326
+ diagnostic.TagApi(error, LegacyFailureApi.CreateToolhelp32Snapshot);
327
+ diagnostic.TagFailure(error, LegacyFailureStage.Snapshot);
328
+ throw error;
329
+ }
330
+ Marshal.Copy(cleared, 0, buffer, size);
331
+ Marshal.WriteInt32(buffer, size);
332
+ bool found = Api.Process32FirstW(snapshot, buffer);
333
+ if (!found) throw new InvalidOperationException("Snapshot enumeration failed");
334
+ int entries = 0;
335
+ while (found)
336
+ {
337
+ check();
338
+ if (++entries > 16384) throw new InvalidOperationException("Snapshot entry bound");
339
+ if (Marshal.ReadInt32(buffer) != size) throw new InvalidOperationException("Snapshot schema unavailable");
340
+ uint pid = unchecked((uint)Marshal.ReadInt32(buffer, pidOffset));
341
+ uint parent = unchecked((uint)Marshal.ReadInt32(buffer, parentOffset));
342
+ // The API fills other fields too. Never marshal names; retain only PID pairs.
343
+ Marshal.Copy(cleared, 0, buffer, size);
344
+ if (parents.ContainsKey(pid)) throw new InvalidOperationException("Snapshot duplicate entry");
345
+ parents.Add(pid, parent);
346
+ List<uint> group;
347
+ if (!children.TryGetValue(parent, out group))
348
+ {
349
+ group = new List<uint>();
350
+ children.Add(parent, group);
351
+ }
352
+ group.Add(pid);
353
+ Marshal.WriteInt32(buffer, size);
354
+ found = Api.Process32NextW(snapshot, buffer);
355
+ if (!found &&
356
+ Marshal.GetLastWin32Error() != 18) throw new InvalidOperationException("Snapshot enumeration failed");
357
+ }
358
+ }
359
+ finally
360
+ {
361
+ Marshal.Copy(cleared, 0, buffer, size);
362
+ Marshal.FreeHGlobal(buffer);
363
+ if (snapshot != null) snapshot.Dispose();
364
+ }
365
+ if (!snapshot.Closed) throw new InvalidOperationException("Snapshot close failed");
366
+ }
367
+
368
+ internal List<int> Children(int parent)
369
+ {
370
+ var result = new List<int>();
371
+ List<uint> group;
372
+ if (children.TryGetValue(checked((uint)parent), out group))
373
+ {
374
+ foreach (uint pid in group)
375
+ {
376
+ if (pid == 0 ||
377
+ pid > int.MaxValue) throw new InvalidOperationException("Snapshot identity unavailable");
378
+ result.Add((int)pid);
379
+ if (result.Count > 512) throw new InvalidOperationException("Child bound");
380
+ }
381
+ }
382
+ return result;
383
+ }
384
+
385
+ internal bool Contains(int pid, int parent)
386
+ {
387
+ uint actual;
388
+ return parents.TryGetValue(checked((uint)pid), out actual) &&
389
+ actual == checked((uint)parent);
390
+ }
391
+ }
392
+
393
+ internal sealed class SafeSnapshot : SafeHandleZeroOrMinusOneIsInvalid
394
+ {
395
+ internal bool Closed;
396
+ public SafeSnapshot() : base(true) { }
397
+ protected override bool ReleaseHandle()
398
+ {
399
+ Closed = Api.CloseHandle(handle);
400
+ return Closed;
401
+ }
402
+ }
403
+
404
+ internal sealed class LegacyImageCache : IDisposable
405
+ {
406
+ private readonly LegacyNative diagnostic = new LegacyNative();
407
+ private readonly Dictionary<string, LegacyImagePin> files = new Dictionary<string, LegacyImagePin>();
408
+ private readonly Action check;
409
+ internal long Bytes { get; private set; }
410
+ internal int Count { get { return files.Count; } }
411
+
412
+ internal LegacyImageCache(Action check) { this.check = check; }
413
+
414
+ internal string Hash(string path)
415
+ {
416
+ check();
417
+ FileStream stream;
418
+ try { stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); }
419
+ catch (Exception error) { diagnostic.TagApi(error, LegacyFailureApi.FileOpenRead); throw; }
420
+ bool retained = false;
421
+ try
422
+ {
423
+ Api.FileInformation identity;
424
+ if (!Api.GetFileInformationByHandle(stream.SafeFileHandle, out identity))
425
+ {
426
+ var error = new Win32Exception(Marshal.GetLastWin32Error());
427
+ diagnostic.TagApi(error, LegacyFailureApi.GetFileInformationByHandle);
428
+ throw error;
429
+ }
430
+ ulong number = ((ulong)identity.IndexHigh << 32) | identity.IndexLow;
431
+ if (number == 0) throw new InvalidOperationException("Image file identity unavailable");
432
+ var key = identity.Volume.ToString("x8", CultureInfo.InvariantCulture) + ":" +
433
+ number.ToString("x16", CultureInfo.InvariantCulture);
434
+ LegacyImagePin prior;
435
+ if (files.TryGetValue(key, out prior))
436
+ {
437
+ if (prior.Stream.SafeFileHandle.IsClosed ||
438
+ prior.Stream.Length != stream.Length) throw new InvalidOperationException("Image file identity changed");
439
+ return prior.Digest;
440
+ }
441
+ if (files.Count >= 64 ||
442
+ stream.Length < 1 ||
443
+ stream.Length > 256L * 1024 * 1024 ||
444
+ Bytes + stream.Length > 512L * 1024 * 1024) throw new InvalidOperationException("Image hash bound");
445
+ Bytes += stream.Length;
446
+ var digest = "";
447
+ using (var hash = SHA256.Create())
448
+ digest = BitConverter.ToString(hash.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
449
+ check();
450
+ files.Add(key, new LegacyImagePin { Stream = stream, Digest = digest });
451
+ retained = true;
452
+ return digest;
453
+ }
454
+ finally { if (!retained) stream.Dispose(); }
455
+ }
456
+
457
+ public void Dispose()
458
+ {
459
+ foreach (var file in files.Values) file.Stream.Dispose();
460
+ files.Clear();
461
+ }
462
+ }
463
+
464
+ internal sealed class LegacyImagePin
465
+ {
466
+ internal FileStream Stream;
467
+ internal string Digest;
468
+ }
469
+
470
+ internal sealed class LegacyArgumentQueries : IDisposable
471
+ {
472
+ private readonly Action check;
473
+ private readonly List<LegacyQueryWorker> workers = new List<LegacyQueryWorker>();
474
+ private volatile bool cancelled;
475
+ private int count;
476
+ private int maximum;
477
+ private bool disposed;
478
+ internal int Count { get { return count; } }
479
+
480
+ internal LegacyArgumentQueries(Action check) { this.check = check; }
481
+
482
+ internal void BeginPhase(int maximum)
483
+ {
484
+ this.maximum = maximum;
485
+ count = 0;
486
+ }
487
+
488
+ internal void Read(List<Held> processes, bool capture)
489
+ {
490
+ if (processes.Count == 0) return;
491
+ EnsureWorkers();
492
+ for (int offset = 0; offset < processes.Count; offset += 128)
493
+ {
494
+ Check();
495
+ var wave = new List<LegacyArgumentBatch>();
496
+ try
497
+ {
498
+ for (int index = 0; index < 4 && offset + index * 32 < processes.Count; index++)
499
+ {
500
+ int start = offset + index * 32;
501
+ var batch = new LegacyArgumentBatch {
502
+ Processes = processes.GetRange(start, Math.Min(32, processes.Count - start)), Capture = capture
503
+ };
504
+ wave.Add(batch);
505
+ workers[index].Queue.Add(batch);
506
+ }
507
+ foreach (var batch in wave)
508
+ while (!batch.Done.Wait(25)) Check();
509
+ foreach (var batch in wave)
510
+ if (batch.Error != null) throw batch.Error;
511
+ Check();
512
+ if (capture)
513
+ {
514
+ foreach (var batch in wave)
515
+ foreach (var result in batch.Results)
516
+ {
517
+ result.Process.Info.argvSha256 = result.Digest;
518
+ result.Process.Arguments = result.Arguments;
519
+ }
520
+ }
521
+ }
522
+ catch { cancelled = true; Dispose(); throw; }
523
+ finally
524
+ {
525
+ foreach (var batch in wave) batch.Done.Dispose();
526
+ }
527
+ }
528
+ }
529
+
530
+ private void EnsureWorkers()
531
+ {
532
+ if (workers.Count != 0) return;
533
+ for (int index = 0; index < 4; index++)
534
+ {
535
+ var worker = new LegacyQueryWorker();
536
+ worker.Thread = new Thread(() =>
537
+ {
538
+ ManagementScope scope = null;
539
+ var native = new LegacyNative();
540
+ var json = new JavaScriptSerializer { MaxJsonLength = 262144, RecursionLimit = 20 };
541
+ try
542
+ {
543
+ scope = new ManagementScope(@"root\cimv2");
544
+ scope.Connect();
545
+ }
546
+ catch (Exception error)
547
+ {
548
+ native.TagApi(error, LegacyFailureApi.WmiConnect);
549
+ native.TagFailure(error, LegacyFailureStage.ArgumentConnect);
550
+ worker.Error = error;
551
+ cancelled = true;
552
+ }
553
+ finally { worker.Ready.Set(); }
554
+ foreach (var batch in worker.Queue.GetConsumingEnumerable())
555
+ {
556
+ try
557
+ {
558
+ Check();
559
+ if (worker.Error != null) throw worker.Error;
560
+ Query(scope, native, json, batch);
561
+ }
562
+ catch (Exception error)
563
+ {
564
+ native.TagFailure(error, LegacyFailureStage.ArgumentQuery);
565
+ batch.Error = error;
566
+ cancelled = true;
567
+ }
568
+ finally { batch.Done.Set(); }
569
+ }
570
+ });
571
+ worker.Thread.SetApartmentState(ApartmentState.MTA);
572
+ workers.Add(worker);
573
+ worker.Thread.Start();
574
+ }
575
+ foreach (var worker in workers)
576
+ {
577
+ while (!worker.Ready.Wait(25)) Check();
578
+ if (worker.Error != null) throw worker.Error;
579
+ }
580
+ }
581
+
582
+ private void Query(ManagementScope scope, LegacyNative native, JavaScriptSerializer json, LegacyArgumentBatch batch)
583
+ {
584
+ if (batch.Processes.Count < 1 ||
585
+ batch.Processes.Count > 32 ||
586
+ Interlocked.Increment(ref count) > maximum) throw new InvalidOperationException("Argument query bound");
587
+ var clauses = new List<string>();
588
+ var expected = new Dictionary<int, Held>();
589
+ foreach (var process in batch.Processes)
590
+ {
591
+ expected.Add(process.Info.pid, process);
592
+ clauses.Add("ProcessId=" + process.Info.pid.ToString(CultureInfo.InvariantCulture));
593
+ }
594
+ var seen = new HashSet<int>();
595
+ var options = new EnumerationOptions { Timeout = TimeSpan.FromSeconds(2), ReturnImmediately = false };
596
+ using (var search = new ManagementObjectSearcher(scope, new ObjectQuery(
597
+ "SELECT ProcessId,CreationDate,CommandLine FROM Win32_Process WHERE " + string.Join(" OR ", clauses)), options))
598
+ using (var rows = QueryRows(search, native))
599
+ {
600
+ foreach (ManagementObject row in rows)
601
+ using (row)
602
+ {
603
+ Check();
604
+ int id = Convert.ToInt32(row["ProcessId"], CultureInfo.InvariantCulture);
605
+ Held process;
606
+ if (!expected.TryGetValue(id, out process) ||
607
+ !seen.Add(id)) throw new InvalidOperationException("Process query changed");
608
+ try
609
+ {
610
+ var command = row["CommandLine"] as string;
611
+ var date = row["CreationDate"] as string;
612
+ if (string.IsNullOrEmpty(command) ||
613
+ command.Length > 32768 ||
614
+ string.IsNullOrEmpty(date)) throw new InvalidOperationException("Process query unavailable");
615
+ long queried = ManagementDateTimeConverter.ToDateTime(date).ToUniversalTime().ToFileTimeUtc();
616
+ if (!native.Alive(process.Handle) ||
617
+ native.Creation(process.Handle) != process.Created ||
618
+ Math.Abs(queried - process.Created) > 10 ||
619
+ native.Parent(process.Handle) != process.Info.parentPid ||
620
+ native.Owner(process.Handle) != process.Info.ownerSid ||
621
+ !string.Equals(native.Image(process.Handle), process.Info.imagePath, StringComparison.OrdinalIgnoreCase))
622
+ throw new InvalidOperationException("Process query generation changed");
623
+ int argumentCount;
624
+ var pointer = Api.CommandLineToArgvW(command, out argumentCount);
625
+ native.Require(pointer != IntPtr.Zero, LegacyFailureApi.CommandLineToArgv);
626
+ try
627
+ {
628
+ if (argumentCount < 1 ||
629
+ argumentCount > 1024) throw new InvalidOperationException("Argument bounds");
630
+ var arguments = new string[argumentCount];
631
+ for (int index = 0; index < argumentCount; index++)
632
+ arguments[index] = Marshal.PtrToStringUni(Marshal.ReadIntPtr(pointer, index * IntPtr.Size));
633
+ var digest = native.Hash(Encoding.UTF8.GetBytes(json.Serialize(arguments)));
634
+ if (!batch.Capture &&
635
+ digest != process.Info.argvSha256) throw new InvalidOperationException("Arguments changed");
636
+ batch.Results.Add(new LegacyArgumentResult { Process = process, Digest = digest,
637
+ Arguments = batch.Capture && process.KeepArguments ? arguments : null });
638
+ }
639
+ finally { Api.LocalFree(pointer); }
640
+ }
641
+ catch (Exception error)
642
+ {
643
+ native.TagFailure(error, LegacyFailureStage.ArgumentIdentity, process.Info.pid,
644
+ process.Created, process.Info.parentPid, ownerMatched: true);
645
+ throw;
646
+ }
647
+ }
648
+ }
649
+ if (seen.Count != expected.Count) throw new InvalidOperationException("Process query changed");
650
+ Check();
651
+ }
652
+
653
+ private ManagementObjectCollection QueryRows(ManagementObjectSearcher search, LegacyNative native)
654
+ {
655
+ try { return search.Get(); }
656
+ catch (Exception error)
657
+ {
658
+ native.TagApi(error, LegacyFailureApi.WmiQuery);
659
+ native.TagFailure(error, LegacyFailureStage.ArgumentQuery);
660
+ throw;
661
+ }
662
+ }
663
+
664
+ private void Check()
665
+ {
666
+ if (cancelled) throw new InvalidOperationException("Argument query cancelled");
667
+ check();
668
+ }
669
+
670
+ public void Dispose()
671
+ {
672
+ if (disposed) return;
673
+ cancelled = true;
674
+ foreach (var worker in workers) worker.Queue.CompleteAdding();
675
+ foreach (var worker in workers) worker.Thread.Join();
676
+ foreach (var worker in workers) { worker.Queue.Dispose(); worker.Ready.Dispose(); }
677
+ disposed = true;
678
+ }
679
+ }
680
+
681
+ internal sealed class LegacyQueryWorker
682
+ {
683
+ internal Thread Thread;
684
+ internal Exception Error;
685
+ internal ManualResetEventSlim Ready = new ManualResetEventSlim(false);
686
+ internal BlockingCollection<LegacyArgumentBatch> Queue = new BlockingCollection<LegacyArgumentBatch>(1);
687
+ }
688
+
689
+ internal sealed class LegacyArgumentBatch
690
+ {
691
+ internal List<Held> Processes;
692
+ internal bool Capture;
693
+ internal Exception Error;
694
+ internal ManualResetEventSlim Done = new ManualResetEventSlim(false);
695
+ internal List<LegacyArgumentResult> Results = new List<LegacyArgumentResult>();
696
+ }
697
+
698
+ internal sealed class LegacyArgumentResult
699
+ {
700
+ internal Held Process;
701
+ internal string Digest;
702
+ internal string[] Arguments;
703
+ }
704
+
705
+ internal static class Api
706
+ {
707
+ [StructLayout(LayoutKind.Sequential)]
708
+ internal struct MemoryCounters
709
+ {
710
+ internal uint Size, PageFaultCount;
711
+ internal UIntPtr PeakWorkingSet, WorkingSet, PeakPagedPool, PagedPool, PeakNonpagedPool, NonpagedPool;
712
+ internal UIntPtr PagefileUsage, PeakPagefileUsage, PrivateUsage;
713
+ }
714
+ [DllImport("psapi.dll", SetLastError = true)]
715
+ [return: MarshalAs(UnmanagedType.Bool)]
716
+ internal static extern bool GetProcessMemoryInfo(IntPtr process, out MemoryCounters counters, uint size);
717
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
718
+ internal struct ProcessEntryLayout
719
+ {
720
+ internal uint Size, Usage, ProcessId;
721
+ internal UIntPtr HeapId;
722
+ internal uint ModuleId, Threads, ParentId;
723
+ internal int Priority;
724
+ internal uint Flags;
725
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260, ArraySubType = UnmanagedType.U2)]
726
+ internal ushort[] IgnoredName;
727
+ }
728
+ [StructLayout(LayoutKind.Sequential)]
729
+ internal struct FileTime { internal uint Low, High; }
730
+ [StructLayout(LayoutKind.Sequential)]
731
+ internal struct FileInformation
732
+ {
733
+ internal uint Attributes;
734
+ internal FileTime Creation, Access, Write;
735
+ internal uint Volume, SizeHigh, SizeLow, Links, IndexHigh, IndexLow;
736
+ }
737
+ [DllImport("kernel32.dll", SetLastError = true)]
738
+ internal static extern SafeSnapshot CreateToolhelp32Snapshot(uint flags, uint processId);
739
+ [DllImport("kernel32.dll", SetLastError = true)]
740
+ [return: MarshalAs(UnmanagedType.Bool)]
741
+ internal static extern bool Process32FirstW(SafeSnapshot snapshot, IntPtr entry);
742
+ [DllImport("kernel32.dll", SetLastError = true)]
743
+ [return: MarshalAs(UnmanagedType.Bool)]
744
+ internal static extern bool Process32NextW(SafeSnapshot snapshot, IntPtr entry);
745
+ [DllImport("kernel32.dll", SetLastError = true)]
746
+ [return: MarshalAs(UnmanagedType.Bool)]
747
+ internal static extern bool GetFileInformationByHandle(SafeFileHandle file, out FileInformation information);
748
+ [StructLayout(LayoutKind.Sequential)]
749
+ internal struct BasicInformation
750
+ {
751
+ internal IntPtr Reserved1, Peb, Reserved2, Reserved3, ProcessId, ParentId;
752
+ }
753
+ [DllImport("kernel32.dll", SetLastError = true)]
754
+ internal static extern SafeProcess OpenProcess(uint access, [MarshalAs(UnmanagedType.Bool)] bool inherit, int id);
755
+ [DllImport("kernel32.dll", SetLastError = true)]
756
+ [return: MarshalAs(UnmanagedType.Bool)]
757
+ internal static extern bool CloseHandle(IntPtr handle);
758
+ [DllImport("kernel32.dll", SetLastError = true)]
759
+ internal static extern uint WaitForSingleObject(SafeProcess handle, uint milliseconds);
760
+ [DllImport("kernel32.dll", SetLastError = true)]
761
+ [return: MarshalAs(UnmanagedType.Bool)]
762
+ internal static extern bool GetProcessTimes(SafeProcess process, out long creation, out long exit, out long kernel, out long user);
763
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
764
+ [return: MarshalAs(UnmanagedType.Bool)]
765
+ internal static extern bool QueryFullProcessImageNameW(SafeProcess process, uint flags, StringBuilder name, ref int size);
766
+ [DllImport("advapi32.dll", SetLastError = true)]
767
+ [return: MarshalAs(UnmanagedType.Bool)]
768
+ internal static extern bool OpenProcessToken(SafeProcess process, uint access, out SafeProcess token);
769
+ [DllImport("advapi32.dll", SetLastError = true)]
770
+ [return: MarshalAs(UnmanagedType.Bool)]
771
+ internal static extern bool GetTokenInformation(SafeProcess token, int kind, IntPtr info, int size, out int returned);
772
+ [DllImport("ntdll.dll")]
773
+ internal static extern int NtQueryInformationProcess(SafeProcess process, int kind, out BasicInformation info, int size, out int returned);
774
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
775
+ internal static extern IntPtr CommandLineToArgvW(string command, out int count);
776
+ [DllImport("kernel32.dll")]
777
+ internal static extern IntPtr LocalFree(IntPtr memory);
778
+ [DllImport("iphlpapi.dll", SetLastError = true)]
779
+ internal static extern uint GetExtendedTcpTable(IntPtr table, ref int size, [MarshalAs(UnmanagedType.Bool)] bool sort, int family, int kind, uint reserved);
780
+ [DllImport("kernel32.dll", SetLastError = true)]
781
+ [return: MarshalAs(UnmanagedType.Bool)]
782
+ internal static extern bool TerminateProcess(SafeProcess process, uint code);
783
+ }