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.
- package/CHANGELOG.md +75 -1
- package/CONTRIBUTING.md +39 -13
- package/README.md +255 -8
- package/THIRD_PARTY_NOTICES.txt +1 -28
- package/autostart/windows/account-task.ps1 +167 -0
- package/autostart/windows/inspect-task.ps1 +42 -0
- package/autostart/windows/inspect-worker-process-sessions.ps1 +155 -0
- package/autostart/windows/legacy-task.ps1 +301 -0
- package/autostart/windows/register-task.ps1 +11 -2
- package/bin/account-task-adapter.mjs +47 -0
- package/bin/account-upgrade-controller.mjs +198 -0
- package/bin/account-upgrade-worker.mjs +134 -0
- package/bin/account-worker-session.mjs +215 -0
- package/bin/cli-dispatch.mjs +154 -0
- package/bin/cli-main.mjs +1007 -0
- package/bin/cli-selection.mjs +43 -0
- package/bin/cli.mjs +7 -876
- package/bin/legacy-1.3.json +32 -0
- package/bin/legacy-installation.mjs +217 -0
- package/bin/legacy-task.mjs +52 -0
- package/bin/legacy-upgrade.mjs +471 -0
- package/bin/managed-runtime.mjs +170 -0
- package/bin/managed-state.mjs +257 -0
- package/bin/managed-upgrade.mjs +491 -0
- package/bin/mcp-bridge.mjs +132 -4
- package/bin/pooling-batches.mjs +13 -0
- package/bin/pooling-writer.mjs +4 -0
- package/bin/prewarm.mjs +33 -21
- package/bin/runtime-context.mjs +100 -0
- package/bin/service-control.mjs +27 -4
- package/bin/stable-launcher.mjs +54 -0
- package/bin/task-only-controller.mjs +226 -0
- package/bin/task-scope.mjs +65 -0
- package/bin/task-transaction-protocol.mjs +173 -0
- package/bin/top.mjs +22 -10
- package/bin/update-check.mjs +13 -2
- package/bin/upgrade-autostart.mjs +47 -0
- package/bin/upgrade-capability.json +8 -0
- package/bin/upgrade-dependencies.mjs +122 -0
- package/bin/upgrade-package.mjs +203 -0
- package/bin/windows-legacy/LegacyProcessBroker.build.json +42 -0
- package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
- package/bin/windows-legacy/src/AssemblyInfo.cs +8 -0
- package/bin/windows-legacy/src/LegacyNative.cs +783 -0
- package/bin/windows-legacy/src/LegacyProcessBroker.cs +841 -0
- package/bin/windows-legacy-process.mjs +241 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.build.json +38 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.exe +0 -0
- package/bin/windows-lifetime/src/AssemblyInfo.cs +8 -0
- package/bin/windows-lifetime/src/ProcessLifetimeHelper.cs +468 -0
- package/bin/windows-process-lifetime.mjs +218 -0
- package/bin/windows-task-channel/TaskChannelGuard.build.json +45 -0
- package/bin/windows-task-channel/TaskChannelGuard.exe +0 -0
- package/bin/windows-task-channel/src/AssemblyInfo.cs +8 -0
- package/bin/windows-task-channel/src/ChannelCliContext.cs +210 -0
- package/bin/windows-task-channel/src/ChannelFiles.cs +231 -0
- package/bin/windows-task-channel/src/ChannelNative.cs +477 -0
- package/bin/windows-task-channel/src/ChannelSession.cs +203 -0
- package/bin/windows-task-channel/src/TaskChannelGuard.cs +686 -0
- package/bin/windows-task-channel.mjs +335 -0
- package/docs/account-upgrades.md +205 -0
- package/docs/execution-context.md +113 -0
- package/package.json +12 -5
- package/supervisor/supervise.mjs +226 -40
- package/tools/account-upgrade/qualify-two-account.ps1 +66 -0
- package/tools/windows-legacy/README.md +225 -0
- package/tools/windows-legacy/build.ps1 +122 -0
- package/tools/windows-legacy/inventory.mjs +90 -0
- package/tools/windows-process-lifetime/README.md +119 -0
- package/tools/windows-process-lifetime/build.ps1 +122 -0
- package/tools/windows-process-lifetime/inventory.mjs +86 -0
- package/tools/windows-task-channel/README.md +329 -0
- package/tools/windows-task-channel/build.ps1 +107 -0
- package/tools/windows-task-channel/inventory.mjs +117 -0
- package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -28
- package/ui/dist/assets/{index-CgPruWYi.js → index-CcwMo2sI.js} +1 -1
- package/ui/dist/assets/index-PVcuDNrN.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/third-party-manifest.json +37 -25
- package/ui/dist/assets/index-CaLPg0c-.css +0 -1
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.ComponentModel;
|
|
4
|
+
using System.Diagnostics;
|
|
5
|
+
using System.Globalization;
|
|
6
|
+
using System.IO;
|
|
7
|
+
using System.Management;
|
|
8
|
+
using System.Runtime.InteropServices;
|
|
9
|
+
using System.Security.Cryptography;
|
|
10
|
+
using System.Security.Principal;
|
|
11
|
+
using System.Text;
|
|
12
|
+
using System.Threading;
|
|
13
|
+
using System.Web.Script.Serialization;
|
|
14
|
+
|
|
15
|
+
internal sealed class LegacyProcessBroker
|
|
16
|
+
{
|
|
17
|
+
private readonly LegacyNative native = new LegacyNative();
|
|
18
|
+
private readonly JavaScriptSerializer json = new JavaScriptSerializer { MaxJsonLength = 262144, RecursionLimit = 20 };
|
|
19
|
+
private readonly List<Held> held = new List<Held>();
|
|
20
|
+
private readonly List<ExcludedIdentity> excluded = new List<ExcludedIdentity>();
|
|
21
|
+
private readonly List<FileStream> scripts = new List<FileStream>();
|
|
22
|
+
private readonly string user = WindowsIdentity.GetCurrent().User.Value;
|
|
23
|
+
private readonly Stopwatch clock = Stopwatch.StartNew();
|
|
24
|
+
private long deadline = 15000;
|
|
25
|
+
private volatile bool committed;
|
|
26
|
+
private string stage = "request";
|
|
27
|
+
private string root;
|
|
28
|
+
private int port;
|
|
29
|
+
private string token;
|
|
30
|
+
private Plan plan;
|
|
31
|
+
private object captureFailure;
|
|
32
|
+
private LegacySnapshot snapshot;
|
|
33
|
+
private LegacyArgumentQueries arguments;
|
|
34
|
+
private bool revalidating;
|
|
35
|
+
private readonly Stopwatch phaseClock = new Stopwatch();
|
|
36
|
+
private object captureDiagnostics;
|
|
37
|
+
private object revalidationDiagnostics;
|
|
38
|
+
private object brokerIdentity;
|
|
39
|
+
|
|
40
|
+
private static int Main(string[] args)
|
|
41
|
+
{
|
|
42
|
+
return new LegacyProcessBroker().Run(args);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private int Run(string[] args)
|
|
46
|
+
{
|
|
47
|
+
try
|
|
48
|
+
{
|
|
49
|
+
if (args.Length != 0) throw new ArgumentException();
|
|
50
|
+
Console.SetIn(new StreamReader(Console.OpenStandardInput(), new UTF8Encoding(false, true), false));
|
|
51
|
+
Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false, true)) { AutoFlush = true });
|
|
52
|
+
StartWatchdog();
|
|
53
|
+
var request = Read();
|
|
54
|
+
if (request.action == "verify")
|
|
55
|
+
{
|
|
56
|
+
ValidatePlan(request.expected);
|
|
57
|
+
Write(new { ok = true, receipt = VerifyGone(request.expected), brokerIdentity });
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
if (request.action != "prepare") throw new ArgumentException();
|
|
61
|
+
if (request.expected != null) ValidatePlan(request.expected);
|
|
62
|
+
root = Path.GetFullPath(request.root).TrimEnd(Path.DirectorySeparatorChar);
|
|
63
|
+
port = request.port;
|
|
64
|
+
if (!Path.IsPathRooted(request.root) ||
|
|
65
|
+
port < 1 ||
|
|
66
|
+
port > 65535 ||
|
|
67
|
+
!Directory.Exists(root)) throw new ArgumentException();
|
|
68
|
+
stage = "capture-roots";
|
|
69
|
+
phaseClock.Start();
|
|
70
|
+
arguments = new LegacyArgumentQueries(CheckPhase);
|
|
71
|
+
arguments.BeginPhase(48);
|
|
72
|
+
Held bridge;
|
|
73
|
+
Held supervisor;
|
|
74
|
+
using (var images = new LegacyImageCache(CheckPhase))
|
|
75
|
+
{
|
|
76
|
+
bridge = Capture(native.Listener(port), null);
|
|
77
|
+
supervisor = Capture(bridge.Info.parentPid, null);
|
|
78
|
+
if (bridge.Info.pid == supervisor.Info.pid ||
|
|
79
|
+
bridge.Created < supervisor.Created) throw new InvalidOperationException();
|
|
80
|
+
bridge.Info.parentCreationTime = supervisor.Info.creationTime;
|
|
81
|
+
bridge.KeepArguments = true;
|
|
82
|
+
supervisor.KeepArguments = true;
|
|
83
|
+
bridge.SelectedRoot = true;
|
|
84
|
+
supervisor.SelectedRoot = true;
|
|
85
|
+
var roots = new List<Held> { bridge, supervisor };
|
|
86
|
+
CaptureFacts(roots, images);
|
|
87
|
+
CheckRootArguments(bridge, supervisor);
|
|
88
|
+
RefreshSnapshot();
|
|
89
|
+
if (!snapshot.Contains(bridge.Info.pid, supervisor.Info.pid) ||
|
|
90
|
+
!snapshot.Contains(supervisor.Info.pid, supervisor.Info.parentPid))
|
|
91
|
+
throw new InvalidOperationException("Snapshot root changed");
|
|
92
|
+
CaptureChildren(supervisor, 0);
|
|
93
|
+
var descendants = held.FindAll(value => value != bridge && value != supervisor);
|
|
94
|
+
CaptureFacts(descendants, images);
|
|
95
|
+
if (!native.Alive(supervisor.Handle) ||
|
|
96
|
+
!native.Alive(bridge.Handle) ||
|
|
97
|
+
native.Listener(port) != bridge.Info.pid) throw new InvalidOperationException("Root changed");
|
|
98
|
+
stage = "plan";
|
|
99
|
+
plan = MakePlan(supervisor, bridge);
|
|
100
|
+
int planBytes = PlanBytes(plan);
|
|
101
|
+
CheckPhase();
|
|
102
|
+
captureDiagnostics = new { elapsedMilliseconds = phaseClock.ElapsedMilliseconds,
|
|
103
|
+
argumentQueries = arguments.Count, imageFiles = images.Count,
|
|
104
|
+
imageBytesHashed = images.Bytes, planUtf8Bytes = planBytes, memory = native.Memory() };
|
|
105
|
+
}
|
|
106
|
+
if (request.expected != null &&
|
|
107
|
+
json.Serialize(request.expected) != json.Serialize(plan))
|
|
108
|
+
throw new InvalidOperationException("Plan changed");
|
|
109
|
+
token = NewToken();
|
|
110
|
+
Interlocked.Exchange(ref deadline, clock.ElapsedMilliseconds + 120000);
|
|
111
|
+
Write(new { ok = true, plan, token, diagnostics = captureDiagnostics, brokerIdentity });
|
|
112
|
+
while (true)
|
|
113
|
+
{
|
|
114
|
+
request = Read();
|
|
115
|
+
if (request.action == "close")
|
|
116
|
+
{
|
|
117
|
+
Write(new { ok = true, closed = true, brokerIdentity });
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
if (request.action == "status")
|
|
121
|
+
{
|
|
122
|
+
Write(new { ok = true, receipt = HeldStatus(), brokerIdentity });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (request.action != "stop" ||
|
|
126
|
+
request.token != token ||
|
|
127
|
+
request.planSha256 != plan.planSha256) throw new ArgumentException();
|
|
128
|
+
stage = "revalidate";
|
|
129
|
+
revalidating = true;
|
|
130
|
+
phaseClock.Restart();
|
|
131
|
+
Interlocked.Exchange(ref deadline, clock.ElapsedMilliseconds + 15000);
|
|
132
|
+
arguments.BeginPhase(32);
|
|
133
|
+
Revalidate(supervisor, bridge);
|
|
134
|
+
arguments.Dispose();
|
|
135
|
+
arguments = null;
|
|
136
|
+
committed = true;
|
|
137
|
+
stage = "stop";
|
|
138
|
+
Interlocked.Exchange(ref deadline, clock.ElapsedMilliseconds + 12000);
|
|
139
|
+
var stopWatch = Stopwatch.StartNew();
|
|
140
|
+
var receipt = Stop(supervisor, bridge);
|
|
141
|
+
Write(new { ok = true, receipt, diagnostics = new {
|
|
142
|
+
revalidation = revalidationDiagnostics, stopMilliseconds = stopWatch.ElapsedMilliseconds,
|
|
143
|
+
memory = native.Memory() }, brokerIdentity });
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (Exception error)
|
|
148
|
+
{
|
|
149
|
+
if (arguments != null) { arguments.Dispose(); arguments = null; }
|
|
150
|
+
var win32 = error as Win32Exception;
|
|
151
|
+
captureFailure = captureFailure ?? native.FailureDetails(error);
|
|
152
|
+
Write(new { ok = false, error = "LEGACY_PROCESS_REFUSED", stage,
|
|
153
|
+
type = error.GetType().Name, nativeError = win32 == null ? 0 : win32.NativeErrorCode,
|
|
154
|
+
reason = RefusalReason(error), captureFailure, mutationStarted = committed, treeCompleteness = "unproven", brokerIdentity });
|
|
155
|
+
return 1;
|
|
156
|
+
}
|
|
157
|
+
finally
|
|
158
|
+
{
|
|
159
|
+
if (arguments != null) arguments.Dispose();
|
|
160
|
+
foreach (var process in held) process.Handle.Dispose();
|
|
161
|
+
foreach (var script in scripts) script.Dispose();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private void StartWatchdog()
|
|
166
|
+
{
|
|
167
|
+
var self = Api.OpenProcess(0x1000, false, Process.GetCurrentProcess().Id);
|
|
168
|
+
int parentId;
|
|
169
|
+
long selfCreated;
|
|
170
|
+
using (self)
|
|
171
|
+
{
|
|
172
|
+
native.Require(!self.IsInvalid);
|
|
173
|
+
parentId = native.Parent(self);
|
|
174
|
+
selfCreated = native.Creation(self);
|
|
175
|
+
brokerIdentity = new { pid = Process.GetCurrentProcess().Id,
|
|
176
|
+
creationTime = selfCreated.ToString(CultureInfo.InvariantCulture) };
|
|
177
|
+
}
|
|
178
|
+
var parent = Api.OpenProcess(0x00100000 | 0x1000, false, parentId);
|
|
179
|
+
native.Require(!parent.IsInvalid);
|
|
180
|
+
if (native.Creation(parent) > selfCreated ||
|
|
181
|
+
!native.Alive(parent))
|
|
182
|
+
{
|
|
183
|
+
parent.Dispose();
|
|
184
|
+
throw new InvalidOperationException("Broker parent identity lost");
|
|
185
|
+
}
|
|
186
|
+
var thread = new Thread(() =>
|
|
187
|
+
{
|
|
188
|
+
using (parent)
|
|
189
|
+
{
|
|
190
|
+
while (clock.ElapsedMilliseconds < Interlocked.Read(ref deadline))
|
|
191
|
+
{
|
|
192
|
+
uint wait = Api.WaitForSingleObject(parent, 100);
|
|
193
|
+
if (wait == 0 &&
|
|
194
|
+
!committed) Environment.Exit(2);
|
|
195
|
+
if (wait == 0xffffffff) Environment.Exit(2);
|
|
196
|
+
if (wait == 0) Thread.Sleep(100);
|
|
197
|
+
}
|
|
198
|
+
Environment.Exit(3);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
thread.IsBackground = true;
|
|
202
|
+
thread.Start();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private Request Read()
|
|
206
|
+
{
|
|
207
|
+
var line = new StringBuilder();
|
|
208
|
+
while (line.Length < 262144)
|
|
209
|
+
{
|
|
210
|
+
int value = Console.In.Read();
|
|
211
|
+
if (value < 0) throw new EndOfStreamException();
|
|
212
|
+
if (value == 10)
|
|
213
|
+
{
|
|
214
|
+
var text = line.ToString();
|
|
215
|
+
if (Encoding.UTF8.GetByteCount(text) + 1 > 262144) throw new ArgumentException("Request bounds");
|
|
216
|
+
return json.Deserialize<Request>(text);
|
|
217
|
+
}
|
|
218
|
+
line.Append((char)value);
|
|
219
|
+
}
|
|
220
|
+
throw new ArgumentException("Request bounds");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private void Write(object value)
|
|
224
|
+
{
|
|
225
|
+
Console.Out.WriteLine(json.Serialize(value));
|
|
226
|
+
Console.Out.Flush();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private Held Capture(int id, Held parent)
|
|
230
|
+
{
|
|
231
|
+
CheckPhase();
|
|
232
|
+
var known = held.Find(value => value.Info.pid == id);
|
|
233
|
+
if (known != null)
|
|
234
|
+
{
|
|
235
|
+
if (!native.Alive(known.Handle) ||
|
|
236
|
+
native.Creation(known.Handle) != known.Created ||
|
|
237
|
+
native.Parent(known.Handle) != known.Info.parentPid ||
|
|
238
|
+
native.Owner(known.Handle) != user) throw new InvalidOperationException("Capture changed");
|
|
239
|
+
if (parent != null &&
|
|
240
|
+
!KnownEdge(known, parent)) return null;
|
|
241
|
+
return known;
|
|
242
|
+
}
|
|
243
|
+
if (held.Count + excluded.Count >= 512) throw new InvalidOperationException("Capture bound");
|
|
244
|
+
var substage = LegacyFailureStage.CaptureOpenQuery;
|
|
245
|
+
long? observedBirth = null;
|
|
246
|
+
int? observedParent = null;
|
|
247
|
+
bool? observedAlive = null;
|
|
248
|
+
bool? observedParentAlive = null;
|
|
249
|
+
bool? observedOwnerMatch = null;
|
|
250
|
+
var handle = Api.OpenProcess(0x00100000 | 0x1000, false, id);
|
|
251
|
+
try
|
|
252
|
+
{
|
|
253
|
+
native.Require(!handle.IsInvalid, LegacyFailureApi.OpenProcessQuery);
|
|
254
|
+
substage = LegacyFailureStage.CaptureIdentity;
|
|
255
|
+
bool alive = native.Alive(handle);
|
|
256
|
+
observedAlive = alive;
|
|
257
|
+
if (!alive) throw new InvalidOperationException("Capture exited");
|
|
258
|
+
long created = native.Creation(handle);
|
|
259
|
+
observedBirth = created;
|
|
260
|
+
substage = LegacyFailureStage.CaptureParent;
|
|
261
|
+
int parentId = native.Parent(handle);
|
|
262
|
+
observedParent = parentId;
|
|
263
|
+
if (parent != null)
|
|
264
|
+
{
|
|
265
|
+
bool parentAlive;
|
|
266
|
+
try { parentAlive = native.Alive(parent.Handle); }
|
|
267
|
+
catch (Exception error)
|
|
268
|
+
{
|
|
269
|
+
native.TagFailure(error, LegacyFailureStage.CaptureParentLiveness,
|
|
270
|
+
parent.Info.pid, parent.Created, parent.Info.parentPid, ownerMatched: true);
|
|
271
|
+
throw;
|
|
272
|
+
}
|
|
273
|
+
observedParentAlive = parentAlive;
|
|
274
|
+
bool bornBeforeParent = created < parent.Created;
|
|
275
|
+
bool changedEdge = parentId != parent.Info.pid;
|
|
276
|
+
if (!parentAlive ||
|
|
277
|
+
changedEdge)
|
|
278
|
+
{
|
|
279
|
+
captureFailure = new { pid = id, creationTime = created.ToString(CultureInfo.InvariantCulture),
|
|
280
|
+
actualParentPid = parentId,
|
|
281
|
+
expectedParentPid = parent.Info.pid, parentCreationTime = parent.Info.creationTime,
|
|
282
|
+
parentAlive, bornBeforeParent, changedEdge };
|
|
283
|
+
throw new InvalidOperationException("Unproven ancestry");
|
|
284
|
+
}
|
|
285
|
+
if (bornBeforeParent)
|
|
286
|
+
{
|
|
287
|
+
// A reused creator PID is not ownership of its older survivors.
|
|
288
|
+
// This candidate never receives terminate access or enters held.
|
|
289
|
+
excluded.Add(new ExcludedIdentity { pid = id,
|
|
290
|
+
creationTime = created.ToString(CultureInfo.InvariantCulture),
|
|
291
|
+
parentPid = parentId, parentCreationTime = parent.Info.creationTime,
|
|
292
|
+
reason = "predates-held-parent" });
|
|
293
|
+
handle.Dispose();
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
substage = LegacyFailureStage.CaptureOwner;
|
|
298
|
+
var owner = native.Owner(handle);
|
|
299
|
+
observedOwnerMatch = owner == user;
|
|
300
|
+
if (owner != user ||
|
|
301
|
+
id == Process.GetCurrentProcess().Id) throw new InvalidOperationException("Owner mismatch");
|
|
302
|
+
substage = LegacyFailureStage.CaptureOpenTerminate;
|
|
303
|
+
var terminable = Api.OpenProcess(0x00100000 | 0x1000 | 1, false, id);
|
|
304
|
+
try
|
|
305
|
+
{
|
|
306
|
+
native.Require(!terminable.IsInvalid, LegacyFailureApi.OpenProcessTerminate);
|
|
307
|
+
if (native.Creation(terminable) != created ||
|
|
308
|
+
!native.Alive(terminable)) throw new InvalidOperationException("Capture changed");
|
|
309
|
+
}
|
|
310
|
+
catch { terminable.Dispose(); throw; }
|
|
311
|
+
handle.Dispose();
|
|
312
|
+
handle = terminable;
|
|
313
|
+
substage = LegacyFailureStage.CaptureImage;
|
|
314
|
+
var image = native.Image(handle);
|
|
315
|
+
var info = new Identity { pid = id, creationTime = created.ToString(CultureInfo.InvariantCulture),
|
|
316
|
+
parentPid = parentId, parentCreationTime = parent == null ? null : parent.Info.creationTime,
|
|
317
|
+
ownerSid = owner, imagePath = image };
|
|
318
|
+
if (!native.Alive(handle)) throw new InvalidOperationException("Capture changed");
|
|
319
|
+
var result = new Held { Handle = handle, Info = info, Created = created };
|
|
320
|
+
held.Add(result);
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
catch (Exception error)
|
|
324
|
+
{
|
|
325
|
+
native.TagFailure(error, substage, id, observedBirth, observedParent,
|
|
326
|
+
parent == null ? (int?)null : parent.Info.pid, parent == null ? null : parent.Info.creationTime,
|
|
327
|
+
observedAlive, observedParentAlive, observedOwnerMatch);
|
|
328
|
+
handle.Dispose();
|
|
329
|
+
throw;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private bool KnownEdge(Held child, Held parent)
|
|
334
|
+
{
|
|
335
|
+
CheckPhase();
|
|
336
|
+
if (!native.Alive(child.Handle) ||
|
|
337
|
+
!native.Alive(parent.Handle) ||
|
|
338
|
+
native.Creation(child.Handle) != child.Created ||
|
|
339
|
+
native.Creation(parent.Handle) != parent.Created ||
|
|
340
|
+
native.Parent(child.Handle) != parent.Info.pid ||
|
|
341
|
+
child.Info.parentPid != parent.Info.pid ||
|
|
342
|
+
native.Owner(child.Handle) != user ||
|
|
343
|
+
native.Owner(parent.Handle) != user)
|
|
344
|
+
throw new InvalidOperationException("Ancestry changed");
|
|
345
|
+
if (child.Created < parent.Created)
|
|
346
|
+
{
|
|
347
|
+
// The selected root stays held; only this false reused-creator edge is ignored.
|
|
348
|
+
if (child.SelectedRoot) return false;
|
|
349
|
+
throw new InvalidOperationException("Unproven ancestry");
|
|
350
|
+
}
|
|
351
|
+
if (child.Info.parentCreationTime != parent.Info.creationTime)
|
|
352
|
+
throw new InvalidOperationException("Ancestry changed");
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private List<int> Children(int parentId)
|
|
357
|
+
{
|
|
358
|
+
CheckPhase();
|
|
359
|
+
var parent = held.Find(value => value.Info.pid == parentId);
|
|
360
|
+
if (snapshot == null ||
|
|
361
|
+
parent == null ||
|
|
362
|
+
!native.Alive(parent.Handle)) throw new InvalidOperationException("Unproven ancestry");
|
|
363
|
+
var ids = snapshot.Children(parentId);
|
|
364
|
+
ids.Sort();
|
|
365
|
+
return ids;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private void CheckPhase()
|
|
369
|
+
{
|
|
370
|
+
if ((!revalidating && clock.ElapsedMilliseconds > 14000) ||
|
|
371
|
+
(revalidating && phaseClock.ElapsedMilliseconds >= 15000))
|
|
372
|
+
throw new InvalidOperationException("Capture bound");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private void RefreshSnapshot()
|
|
376
|
+
{
|
|
377
|
+
var current = new LegacySnapshot();
|
|
378
|
+
current.Read(CheckPhase);
|
|
379
|
+
snapshot = current;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private void CaptureFacts(List<Held> processes, LegacyImageCache images)
|
|
383
|
+
{
|
|
384
|
+
arguments.Read(processes, true);
|
|
385
|
+
foreach (var process in processes)
|
|
386
|
+
{
|
|
387
|
+
try
|
|
388
|
+
{
|
|
389
|
+
CheckPhase();
|
|
390
|
+
if (!native.Alive(process.Handle) ||
|
|
391
|
+
native.Creation(process.Handle) != process.Created ||
|
|
392
|
+
native.Owner(process.Handle) != user ||
|
|
393
|
+
native.Parent(process.Handle) != process.Info.parentPid ||
|
|
394
|
+
!Equal(native.Image(process.Handle), process.Info.imagePath))
|
|
395
|
+
throw new InvalidOperationException("Capture changed");
|
|
396
|
+
process.Info.imageSha256 = images.Hash(process.Info.imagePath);
|
|
397
|
+
}
|
|
398
|
+
catch (Exception error)
|
|
399
|
+
{
|
|
400
|
+
native.TagFailure(error, LegacyFailureStage.CaptureImageFacts, process.Info.pid,
|
|
401
|
+
process.Created, process.Info.parentPid, ownerMatched: true);
|
|
402
|
+
throw;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private int PlanBytes(Plan value)
|
|
408
|
+
{
|
|
409
|
+
try
|
|
410
|
+
{
|
|
411
|
+
int bytes = Encoding.UTF8.GetByteCount(json.Serialize(value));
|
|
412
|
+
if (bytes > 240 * 1024) throw new InvalidOperationException("Plan byte bound");
|
|
413
|
+
return bytes;
|
|
414
|
+
}
|
|
415
|
+
catch (InvalidOperationException) { throw new InvalidOperationException("Plan byte bound"); }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private void CaptureChildren(Held parent, int depth)
|
|
419
|
+
{
|
|
420
|
+
if (depth >= 8) throw new InvalidOperationException("Depth bound");
|
|
421
|
+
foreach (int id in Children(parent.Info.pid))
|
|
422
|
+
{
|
|
423
|
+
if (id == parent.Info.pid) throw new InvalidOperationException("Ancestry cycle");
|
|
424
|
+
var child = Capture(id, parent);
|
|
425
|
+
if (child == null) continue;
|
|
426
|
+
child.Info.parentCreationTime = parent.Info.creationTime;
|
|
427
|
+
CaptureChildren(child, depth + 1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private void CheckRootArguments(Held bridge, Held supervisor)
|
|
432
|
+
{
|
|
433
|
+
var bridgeScript = Path.Combine(root, "bin", "mcp-bridge.mjs");
|
|
434
|
+
var supervisorScript = Path.Combine(root, "supervisor", "supervise.ps1");
|
|
435
|
+
var bridgeArgs = bridge.Arguments;
|
|
436
|
+
var supervisorArgs = supervisor.Arguments;
|
|
437
|
+
var supervisorImage = Path.GetFileName(supervisor.Info.imagePath).ToLowerInvariant();
|
|
438
|
+
bool bridgeValid = string.Equals(Path.GetFileName(bridge.Info.imagePath), "node.exe", StringComparison.OrdinalIgnoreCase) &&
|
|
439
|
+
bridgeArgs.Length == 4 &&
|
|
440
|
+
EqualPath(bridgeArgs[1], bridgeScript) &&
|
|
441
|
+
bridgeArgs[2] == "--port" &&
|
|
442
|
+
bridgeArgs[3] == port.ToString(CultureInfo.InvariantCulture);
|
|
443
|
+
bool supervisorValid = (supervisorImage == "pwsh.exe" || supervisorImage == "powershell.exe") &&
|
|
444
|
+
supervisorArgs.Length == 10 &&
|
|
445
|
+
Equal(supervisorArgs[1], "-NoProfile") &&
|
|
446
|
+
Equal(supervisorArgs[2], "-ExecutionPolicy") &&
|
|
447
|
+
Equal(supervisorArgs[3], "Bypass") &&
|
|
448
|
+
Equal(supervisorArgs[4], "-WindowStyle") &&
|
|
449
|
+
Equal(supervisorArgs[5], "Hidden") &&
|
|
450
|
+
Equal(supervisorArgs[6], "-File") &&
|
|
451
|
+
EqualPath(supervisorArgs[7], supervisorScript) &&
|
|
452
|
+
Equal(supervisorArgs[8], "-Port") &&
|
|
453
|
+
supervisorArgs[9] == port.ToString(CultureInfo.InvariantCulture);
|
|
454
|
+
if (!bridgeValid ||
|
|
455
|
+
!supervisorValid) throw new InvalidOperationException("Root arguments mismatch");
|
|
456
|
+
bridge.Info.scriptSha256 = HoldScript(bridgeScript);
|
|
457
|
+
supervisor.Info.scriptSha256 = HoldScript(supervisorScript);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private string HoldScript(string path)
|
|
461
|
+
{
|
|
462
|
+
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
463
|
+
scripts.Add(stream);
|
|
464
|
+
if (stream.Length > 4 * 1024 * 1024) throw new InvalidOperationException("Script bound");
|
|
465
|
+
using (var hash = SHA256.Create())
|
|
466
|
+
return BitConverter.ToString(hash.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private bool Equal(string left, string right) { return string.Equals(left, right, StringComparison.OrdinalIgnoreCase); }
|
|
470
|
+
private bool EqualPath(string left, string right)
|
|
471
|
+
{
|
|
472
|
+
// Target argv belongs to the target's cwd/drive context, never ours.
|
|
473
|
+
// Only unambiguous drive-absolute or UNC paths can identify its script.
|
|
474
|
+
if (!FullyQualifiedScriptPath(left))
|
|
475
|
+
throw new InvalidOperationException("Script argument not fully qualified");
|
|
476
|
+
return Equal(Path.GetFullPath(left), Path.GetFullPath(right));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private bool FullyQualifiedScriptPath(string value)
|
|
480
|
+
{
|
|
481
|
+
if (string.IsNullOrEmpty(value) ||
|
|
482
|
+
value.Length < 3) return false;
|
|
483
|
+
bool driveLetter = (value[0] >= 'A' && value[0] <= 'Z') ||
|
|
484
|
+
(value[0] >= 'a' && value[0] <= 'z');
|
|
485
|
+
if (driveLetter &&
|
|
486
|
+
value[1] == ':' &&
|
|
487
|
+
(value[2] == '\\' || value[2] == '/')) return true;
|
|
488
|
+
var path = value.Replace('/', '\\');
|
|
489
|
+
if (!path.StartsWith(@"\\", StringComparison.Ordinal) ||
|
|
490
|
+
path[2] == '?' ||
|
|
491
|
+
path[2] == '.') return false;
|
|
492
|
+
int serverEnd = path.IndexOf('\\', 2);
|
|
493
|
+
if (serverEnd <= 2 ||
|
|
494
|
+
serverEnd + 1 >= path.Length) return false;
|
|
495
|
+
int shareEnd = path.IndexOf('\\', serverEnd + 1);
|
|
496
|
+
if (shareEnd < 0) shareEnd = path.Length;
|
|
497
|
+
var share = path.Substring(serverEnd + 1, shareEnd - serverEnd - 1);
|
|
498
|
+
return share.Length > 0 &&
|
|
499
|
+
share != "." &&
|
|
500
|
+
share != "..";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private Plan MakePlan(Held supervisor, Held bridge)
|
|
504
|
+
{
|
|
505
|
+
var observed = new List<Identity>();
|
|
506
|
+
foreach (var process in held)
|
|
507
|
+
if (process != supervisor &&
|
|
508
|
+
process != bridge) observed.Add(process.Info);
|
|
509
|
+
observed.Sort((left, right) => left.pid.CompareTo(right.pid));
|
|
510
|
+
excluded.Sort((left, right) => left.pid.CompareTo(right.pid));
|
|
511
|
+
var result = new Plan { protocol = 1, kind = "legacy-observed-process-set", port = port, root = root,
|
|
512
|
+
ownerSid = user, roots = new Roots { supervisor = supervisor.Info, bridge = bridge.Info },
|
|
513
|
+
observed = observed.ToArray(), excluded = excluded.ToArray(), treeCompleteness = "unproven",
|
|
514
|
+
scope = "Selected roots and captured proven descendants only; uncaptured and historical orphans are untouched." };
|
|
515
|
+
result.observedSetSha256 = native.Hash(Encoding.UTF8.GetBytes(json.Serialize(result.observed)));
|
|
516
|
+
result.planSha256 = DigestPlan(result);
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private string DigestPlan(Plan value)
|
|
521
|
+
{
|
|
522
|
+
var saved = value.planSha256;
|
|
523
|
+
value.planSha256 = null;
|
|
524
|
+
var digest = native.Hash(Encoding.UTF8.GetBytes(json.Serialize(value)));
|
|
525
|
+
value.planSha256 = saved;
|
|
526
|
+
return digest;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private void Revalidate(Held supervisor, Held bridge)
|
|
530
|
+
{
|
|
531
|
+
if (!native.Alive(supervisor.Handle) ||
|
|
532
|
+
!native.Alive(bridge.Handle) ||
|
|
533
|
+
native.Listener(port) != bridge.Info.pid) throw new InvalidOperationException("Root changed");
|
|
534
|
+
var live = held.FindAll(value => native.Alive(value.Handle));
|
|
535
|
+
arguments.Read(live, false);
|
|
536
|
+
using (var images = new LegacyImageCache(CheckPhase))
|
|
537
|
+
{
|
|
538
|
+
foreach (var process in live)
|
|
539
|
+
{
|
|
540
|
+
CheckPhase();
|
|
541
|
+
if (!native.Alive(process.Handle) ||
|
|
542
|
+
native.Creation(process.Handle) != process.Created ||
|
|
543
|
+
native.Parent(process.Handle) != process.Info.parentPid ||
|
|
544
|
+
native.Owner(process.Handle) != user ||
|
|
545
|
+
!Equal(native.Image(process.Handle), process.Info.imagePath) ||
|
|
546
|
+
images.Hash(process.Info.imagePath) != process.Info.imageSha256)
|
|
547
|
+
throw new InvalidOperationException("Identity changed");
|
|
548
|
+
}
|
|
549
|
+
RefreshSnapshot();
|
|
550
|
+
foreach (var process in held)
|
|
551
|
+
{
|
|
552
|
+
if (!native.Alive(process.Handle)) continue;
|
|
553
|
+
if (!snapshot.Contains(process.Info.pid, process.Info.parentPid))
|
|
554
|
+
throw new InvalidOperationException("Snapshot held member changed");
|
|
555
|
+
foreach (int id in Children(process.Info.pid))
|
|
556
|
+
{
|
|
557
|
+
var known = held.Find(value => value.Info.pid == id);
|
|
558
|
+
if (known != null)
|
|
559
|
+
{
|
|
560
|
+
if (!KnownEdge(known, process)) continue;
|
|
561
|
+
}
|
|
562
|
+
else if (!ApprovedNonmember(process, id))
|
|
563
|
+
throw new InvalidOperationException("Observed set expanded; replan");
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (!native.Alive(supervisor.Handle) ||
|
|
567
|
+
!native.Alive(bridge.Handle) ||
|
|
568
|
+
native.Listener(port) != bridge.Info.pid) throw new InvalidOperationException("Root changed");
|
|
569
|
+
CheckPhase();
|
|
570
|
+
revalidationDiagnostics = new { elapsedMilliseconds = phaseClock.ElapsedMilliseconds,
|
|
571
|
+
argumentQueries = arguments.Count, imageFiles = images.Count, imageBytesHashed = images.Bytes };
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private bool ApprovedNonmember(Held parent, int id)
|
|
576
|
+
{
|
|
577
|
+
var prior = excluded.Find(value => value.pid == id &&
|
|
578
|
+
value.parentPid == parent.Info.pid &&
|
|
579
|
+
value.parentCreationTime == parent.Info.creationTime);
|
|
580
|
+
if (prior == null) return false;
|
|
581
|
+
using (var process = Api.OpenProcess(0x00100000 | 0x1000, false, id))
|
|
582
|
+
{
|
|
583
|
+
native.Require(!process.IsInvalid);
|
|
584
|
+
long created = native.Creation(process);
|
|
585
|
+
return native.Alive(parent.Handle) &&
|
|
586
|
+
native.Alive(process) &&
|
|
587
|
+
native.Parent(process) == parent.Info.pid &&
|
|
588
|
+
created < parent.Created &&
|
|
589
|
+
created.ToString(CultureInfo.InvariantCulture) == prior.creationTime;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private Receipt Stop(Held supervisor, Held bridge)
|
|
594
|
+
{
|
|
595
|
+
var errors = new List<string>();
|
|
596
|
+
var stopClock = Stopwatch.StartNew();
|
|
597
|
+
Kill(supervisor, stopClock, errors);
|
|
598
|
+
if (!native.Alive(supervisor.Handle))
|
|
599
|
+
{
|
|
600
|
+
Kill(bridge, stopClock, errors);
|
|
601
|
+
for (int index = held.Count - 1; index >= 0; index--)
|
|
602
|
+
if (held[index] != supervisor &&
|
|
603
|
+
held[index] != bridge) Kill(held[index], stopClock, errors);
|
|
604
|
+
}
|
|
605
|
+
var result = HeldStatus();
|
|
606
|
+
result.errors = errors.ToArray();
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private void Kill(Held process, Stopwatch stopClock, List<string> errors)
|
|
611
|
+
{
|
|
612
|
+
try
|
|
613
|
+
{
|
|
614
|
+
if (!native.Alive(process.Handle)) return;
|
|
615
|
+
if (stopClock.ElapsedMilliseconds >= 8000) throw new TimeoutException();
|
|
616
|
+
bool terminated = Api.TerminateProcess(process.Handle, 1);
|
|
617
|
+
int terminationError = terminated ? 0 : Marshal.GetLastWin32Error();
|
|
618
|
+
uint remaining = (uint)Math.Max(1, 8000 - stopClock.ElapsedMilliseconds);
|
|
619
|
+
// Parent exit can already be terminating this child. A racing
|
|
620
|
+
// TerminateProcess failure is reconciled only by this held handle
|
|
621
|
+
// becoming signaled within the same total stop budget.
|
|
622
|
+
if (Api.WaitForSingleObject(process.Handle, remaining) != 0)
|
|
623
|
+
{
|
|
624
|
+
if (!terminated) throw new Win32Exception(terminationError);
|
|
625
|
+
throw new TimeoutException();
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
catch (Exception error)
|
|
629
|
+
{
|
|
630
|
+
var win32 = error as Win32Exception;
|
|
631
|
+
errors.Add(process.Info.pid + ":" + error.GetType().Name +
|
|
632
|
+
(win32 == null ? "" : ":" + win32.NativeErrorCode));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private Receipt HeldStatus()
|
|
637
|
+
{
|
|
638
|
+
bool rootsGone = true;
|
|
639
|
+
bool observedGone = true;
|
|
640
|
+
foreach (var process in held)
|
|
641
|
+
{
|
|
642
|
+
if (!native.Alive(process.Handle)) continue;
|
|
643
|
+
if (process.Info.pid == plan.roots.bridge.pid ||
|
|
644
|
+
process.Info.pid == plan.roots.supervisor.pid) rootsGone = false;
|
|
645
|
+
else observedGone = false;
|
|
646
|
+
}
|
|
647
|
+
return new Receipt { legacyRootStopVerified = rootsGone, observedDescendantsStopped = observedGone,
|
|
648
|
+
treeCompleteness = "unproven", observedCount = plan.observed.Length,
|
|
649
|
+
excludedCount = plan.excluded.Length, planSha256 = plan.planSha256,
|
|
650
|
+
errors = new string[0], unattributed = "Not discovered or not provable; left untouched." };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private void ValidatePlan(Plan value)
|
|
654
|
+
{
|
|
655
|
+
if (value == null ||
|
|
656
|
+
value.protocol != 1 ||
|
|
657
|
+
value.kind != "legacy-observed-process-set" ||
|
|
658
|
+
value.treeCompleteness != "unproven" ||
|
|
659
|
+
value.ownerSid != user ||
|
|
660
|
+
value.roots == null ||
|
|
661
|
+
value.roots.bridge == null ||
|
|
662
|
+
value.roots.supervisor == null ||
|
|
663
|
+
value.observed == null ||
|
|
664
|
+
value.excluded == null ||
|
|
665
|
+
value.observed.Length > 510 ||
|
|
666
|
+
value.excluded.Length + value.observed.Length > 510 ||
|
|
667
|
+
value.port < 1 ||
|
|
668
|
+
value.port > 65535 ||
|
|
669
|
+
!Path.IsPathRooted(value.root) ||
|
|
670
|
+
DigestPlan(value) != value.planSha256 ||
|
|
671
|
+
native.Hash(Encoding.UTF8.GetBytes(json.Serialize(value.observed))) != value.observedSetSha256)
|
|
672
|
+
throw new ArgumentException("Invalid protected plan");
|
|
673
|
+
PlanBytes(value);
|
|
674
|
+
var ids = new HashSet<int>();
|
|
675
|
+
var identities = new List<Identity> { value.roots.supervisor, value.roots.bridge };
|
|
676
|
+
identities.AddRange(value.observed);
|
|
677
|
+
foreach (var identity in identities)
|
|
678
|
+
{
|
|
679
|
+
long created;
|
|
680
|
+
if (identity == null ||
|
|
681
|
+
identity.pid <= 0 ||
|
|
682
|
+
!ids.Add(identity.pid) ||
|
|
683
|
+
identity.ownerSid != user ||
|
|
684
|
+
!Path.IsPathRooted(identity.imagePath) ||
|
|
685
|
+
!long.TryParse(identity.creationTime, NumberStyles.None, CultureInfo.InvariantCulture, out created) ||
|
|
686
|
+
created <= 0) throw new ArgumentException("Invalid captured identity");
|
|
687
|
+
}
|
|
688
|
+
foreach (var identity in value.excluded)
|
|
689
|
+
{
|
|
690
|
+
long created;
|
|
691
|
+
long parentCreated;
|
|
692
|
+
if (identity == null ||
|
|
693
|
+
identity.pid <= 0 ||
|
|
694
|
+
!ids.Add(identity.pid) ||
|
|
695
|
+
identity.reason != "predates-held-parent" ||
|
|
696
|
+
!long.TryParse(identity.creationTime, NumberStyles.None, CultureInfo.InvariantCulture, out created) ||
|
|
697
|
+
!long.TryParse(identity.parentCreationTime, NumberStyles.None, CultureInfo.InvariantCulture, out parentCreated) ||
|
|
698
|
+
created <= 0 ||
|
|
699
|
+
created >= parentCreated ||
|
|
700
|
+
!identities.Exists(parent => parent.pid == identity.parentPid &&
|
|
701
|
+
parent.creationTime == identity.parentCreationTime))
|
|
702
|
+
throw new ArgumentException("Invalid excluded generation evidence");
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
private Receipt VerifyGone(Plan value)
|
|
707
|
+
{
|
|
708
|
+
bool rootsGone = true;
|
|
709
|
+
bool childrenGone = true;
|
|
710
|
+
var errors = new List<string>();
|
|
711
|
+
var identities = new List<Identity> { value.roots.supervisor, value.roots.bridge };
|
|
712
|
+
identities.AddRange(value.observed);
|
|
713
|
+
for (int index = 0; index < identities.Count; index++)
|
|
714
|
+
{
|
|
715
|
+
var identity = identities[index];
|
|
716
|
+
using (var process = Api.OpenProcess(0x00100000 | 0x1000, false, identity.pid))
|
|
717
|
+
{
|
|
718
|
+
if (process.IsInvalid)
|
|
719
|
+
{
|
|
720
|
+
int code = Marshal.GetLastWin32Error();
|
|
721
|
+
if (code == 87) continue;
|
|
722
|
+
errors.Add(identity.pid + ":OpenProcess:" + code);
|
|
723
|
+
if (index < 2) rootsGone = false; else childrenGone = false;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (native.Creation(process).ToString(CultureInfo.InvariantCulture) != identity.creationTime) continue;
|
|
727
|
+
if (!native.Alive(process)) continue;
|
|
728
|
+
if (index < 2) rootsGone = false; else childrenGone = false;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return new Receipt { legacyRootStopVerified = rootsGone, observedDescendantsStopped = childrenGone,
|
|
732
|
+
observedCount = value.observed.Length, excludedCount = value.excluded.Length, treeCompleteness = "unproven",
|
|
733
|
+
planSha256 = value.planSha256, errors = errors.ToArray(),
|
|
734
|
+
unattributed = "Not discovered or not provable; left untouched." };
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private string NewToken()
|
|
738
|
+
{
|
|
739
|
+
var bytes = new byte[32];
|
|
740
|
+
using (var random = RandomNumberGenerator.Create()) random.GetBytes(bytes);
|
|
741
|
+
return Convert.ToBase64String(bytes);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private string RefusalReason(Exception error)
|
|
745
|
+
{
|
|
746
|
+
// Only fixed own-source reasons are safe to expose. Never relay arbitrary
|
|
747
|
+
// WMI/IO exception text, which can contain paths or command-line content.
|
|
748
|
+
var reasons = new HashSet<string> {
|
|
749
|
+
"Plan changed", "Capture bound", "Capture exited", "Owner mismatch",
|
|
750
|
+
"Unproven ancestry", "Capture changed", "Ancestry changed", "Ancestry cycle",
|
|
751
|
+
"Child bound", "Depth bound", "Root arguments mismatch", "Script bound",
|
|
752
|
+
"Root changed", "Identity changed", "Arguments changed",
|
|
753
|
+
"Observed set expanded; replan", "Process query changed",
|
|
754
|
+
"Process query unavailable", "Process query generation changed",
|
|
755
|
+
"Argument bounds", "Listener ambiguity", "Loopback listener missing",
|
|
756
|
+
"Broker parent identity lost", "Script argument not fully qualified",
|
|
757
|
+
"Snapshot schema unavailable", "Snapshot enumeration failed", "Snapshot entry bound",
|
|
758
|
+
"Snapshot duplicate entry", "Snapshot identity unavailable", "Snapshot close failed",
|
|
759
|
+
"Snapshot root changed", "Snapshot held member changed", "Argument query bound",
|
|
760
|
+
"Argument query cancelled", "Image file identity unavailable", "Image file identity changed",
|
|
761
|
+
"Image hash bound", "Plan byte bound", "Request bounds"
|
|
762
|
+
};
|
|
763
|
+
return reasons.Contains(error.Message) ? error.Message : null;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
internal sealed class Held
|
|
768
|
+
{
|
|
769
|
+
internal SafeProcess Handle;
|
|
770
|
+
internal Identity Info;
|
|
771
|
+
internal long Created;
|
|
772
|
+
internal string[] Arguments;
|
|
773
|
+
internal bool KeepArguments;
|
|
774
|
+
internal bool SelectedRoot;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
internal sealed class Request
|
|
778
|
+
{
|
|
779
|
+
public string action { get; set; }
|
|
780
|
+
public int port { get; set; }
|
|
781
|
+
public string root { get; set; }
|
|
782
|
+
public Plan expected { get; set; }
|
|
783
|
+
public string token { get; set; }
|
|
784
|
+
public string planSha256 { get; set; }
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
internal sealed class Identity
|
|
788
|
+
{
|
|
789
|
+
public int pid { get; set; }
|
|
790
|
+
public string creationTime { get; set; }
|
|
791
|
+
public int parentPid { get; set; }
|
|
792
|
+
public string parentCreationTime { get; set; }
|
|
793
|
+
public string ownerSid { get; set; }
|
|
794
|
+
public string imagePath { get; set; }
|
|
795
|
+
public string imageSha256 { get; set; }
|
|
796
|
+
public string argvSha256 { get; set; }
|
|
797
|
+
public string scriptSha256 { get; set; }
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
internal sealed class Roots
|
|
801
|
+
{
|
|
802
|
+
public Identity supervisor { get; set; }
|
|
803
|
+
public Identity bridge { get; set; }
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
internal sealed class ExcludedIdentity
|
|
807
|
+
{
|
|
808
|
+
public int pid { get; set; }
|
|
809
|
+
public string creationTime { get; set; }
|
|
810
|
+
public int parentPid { get; set; }
|
|
811
|
+
public string parentCreationTime { get; set; }
|
|
812
|
+
public string reason { get; set; }
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
internal sealed class Plan
|
|
816
|
+
{
|
|
817
|
+
public int protocol { get; set; }
|
|
818
|
+
public string kind { get; set; }
|
|
819
|
+
public int port { get; set; }
|
|
820
|
+
public string root { get; set; }
|
|
821
|
+
public string ownerSid { get; set; }
|
|
822
|
+
public Roots roots { get; set; }
|
|
823
|
+
public Identity[] observed { get; set; }
|
|
824
|
+
public ExcludedIdentity[] excluded { get; set; }
|
|
825
|
+
public string observedSetSha256 { get; set; }
|
|
826
|
+
public string treeCompleteness { get; set; }
|
|
827
|
+
public string scope { get; set; }
|
|
828
|
+
public string planSha256 { get; set; }
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
internal sealed class Receipt
|
|
832
|
+
{
|
|
833
|
+
public bool legacyRootStopVerified { get; set; }
|
|
834
|
+
public bool observedDescendantsStopped { get; set; }
|
|
835
|
+
public string treeCompleteness { get; set; }
|
|
836
|
+
public int observedCount { get; set; }
|
|
837
|
+
public int excludedCount { get; set; }
|
|
838
|
+
public string planSha256 { get; set; }
|
|
839
|
+
public string[] errors { get; set; }
|
|
840
|
+
public string unattributed { get; set; }
|
|
841
|
+
}
|