@holoscript/holosystem 0.2.4 → 0.2.5
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/README.md +49 -0
- package/bin/holosystem.mjs +14 -0
- package/docs/vm-launch-threat-model.md +43 -5
- package/native/windows-sandbox/Canary.c +107 -0
- package/native/windows-sandbox/Program.cs +462 -12
- package/native/windows-sandbox/README.md +14 -3
- package/native/windows-sandbox/build.ps1 +31 -1
- package/native/windows-x64/holosystem-appcontainer-canary.exe +0 -0
- package/native/windows-x64/holosystem-sandbox-launcher.exe +0 -0
- package/package.json +1 -1
- package/src/index.mjs +12 -0
- package/src/vm-launch.mjs +336 -45
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
using System;
|
|
2
2
|
using System.Collections.Generic;
|
|
3
|
+
using System.Diagnostics;
|
|
3
4
|
using System.IO;
|
|
5
|
+
using System.Net;
|
|
6
|
+
using System.Net.Sockets;
|
|
4
7
|
using System.Runtime.InteropServices;
|
|
8
|
+
using System.Security.AccessControl;
|
|
9
|
+
using System.Security.Principal;
|
|
5
10
|
using System.Text;
|
|
6
11
|
using System.Threading.Tasks;
|
|
7
12
|
|
|
8
13
|
internal static class Program
|
|
9
14
|
{
|
|
10
15
|
private const string Protocol = "holoscript.holosystem.windows-sandbox-launch.v1";
|
|
16
|
+
private const string AppContainerProtocol = "holoscript.holosystem.windows-appcontainer-launch.v1";
|
|
11
17
|
private const string LowIntegritySid = "S-1-16-4096";
|
|
12
18
|
private const int OutputLimit = 1024 * 1024;
|
|
13
19
|
|
|
@@ -21,6 +27,9 @@ internal static class Program
|
|
|
21
27
|
private const uint SePrivilegeEnabled = 0x2;
|
|
22
28
|
private const int TokenPrivileges = 3;
|
|
23
29
|
private const int TokenIntegrityLevel = 25;
|
|
30
|
+
private const int TokenIsAppContainer = 29;
|
|
31
|
+
private const int TokenCapabilities = 30;
|
|
32
|
+
private const int TokenAppContainerSid = 31;
|
|
24
33
|
|
|
25
34
|
private const uint CreateSuspended = 0x00000004;
|
|
26
35
|
private const uint CreateNoWindow = 0x08000000;
|
|
@@ -49,6 +58,11 @@ internal static class Program
|
|
|
49
58
|
private const uint JobObjectUiLimitExitWindows = 0x00000080;
|
|
50
59
|
private const int JobObjectBasicUiRestrictions = 4;
|
|
51
60
|
private const int JobObjectExtendedLimitInformation = 9;
|
|
61
|
+
private const int ErrorAccessDenied = 5;
|
|
62
|
+
private const int WsaAccessDenied = 10013;
|
|
63
|
+
private const int WsaTimedOut = 10060;
|
|
64
|
+
private static readonly IntPtr ProcThreadAttributeHandleList = new IntPtr(0x00020002);
|
|
65
|
+
private static readonly IntPtr ProcThreadAttributeSecurityCapabilities = new IntPtr(0x00020009);
|
|
52
66
|
|
|
53
67
|
private sealed class Arguments
|
|
54
68
|
{
|
|
@@ -59,6 +73,9 @@ internal static class Program
|
|
|
59
73
|
public int TimeoutMilliseconds;
|
|
60
74
|
public UIntPtr ProcessMemoryBytes;
|
|
61
75
|
public string[] ChildArguments;
|
|
76
|
+
public bool AppContainerDeny;
|
|
77
|
+
public string ProtectedSentinel;
|
|
78
|
+
public string CanaryExecutable;
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
private sealed class LaunchResult
|
|
@@ -78,6 +95,18 @@ internal static class Program
|
|
|
78
95
|
public bool ProcessMemoryLimit;
|
|
79
96
|
public bool UiRestrictions;
|
|
80
97
|
public bool WritableTempLowIntegrity;
|
|
98
|
+
public bool AppContainer;
|
|
99
|
+
public bool AppContainerSidMatched;
|
|
100
|
+
public int CapabilityCount;
|
|
101
|
+
public bool SnapshotReadExecuteGrant;
|
|
102
|
+
public bool WritableTempModifyGrant;
|
|
103
|
+
public bool FilesystemCanaryDenied;
|
|
104
|
+
public int FilesystemCanaryError;
|
|
105
|
+
public bool NetworkCanaryDenied;
|
|
106
|
+
public int NetworkCanaryError;
|
|
107
|
+
public bool LoopbackAccepted;
|
|
108
|
+
public bool ProfileDeleted;
|
|
109
|
+
public bool AppContainerMode;
|
|
81
110
|
public byte[] StandardOutput = new byte[0];
|
|
82
111
|
public byte[] StandardError = new byte[0];
|
|
83
112
|
public string ErrorStage;
|
|
@@ -126,7 +155,11 @@ internal static class Program
|
|
|
126
155
|
"--executable", "--working-directory", "--sandbox-root", "--writable-temp",
|
|
127
156
|
"--timeout-ms", "--process-memory-bytes"
|
|
128
157
|
};
|
|
129
|
-
|
|
158
|
+
bool appContainerDeny = values.ContainsKey("--appcontainer-deny") ||
|
|
159
|
+
values.ContainsKey("--protected-sentinel") ||
|
|
160
|
+
values.ContainsKey("--canary-executable");
|
|
161
|
+
int expectedCount = required.Length + (appContainerDeny ? 3 : 0);
|
|
162
|
+
if (values.Count != expectedCount)
|
|
130
163
|
{
|
|
131
164
|
throw new ArgumentException("Unknown or missing launcher field.");
|
|
132
165
|
}
|
|
@@ -134,6 +167,14 @@ internal static class Program
|
|
|
134
167
|
{
|
|
135
168
|
if (!values.ContainsKey(key)) throw new ArgumentException("Missing launcher field " + key + ".");
|
|
136
169
|
}
|
|
170
|
+
if (appContainerDeny &&
|
|
171
|
+
(!values.ContainsKey("--appcontainer-deny") ||
|
|
172
|
+
!values.ContainsKey("--protected-sentinel") ||
|
|
173
|
+
!values.ContainsKey("--canary-executable") ||
|
|
174
|
+
values["--appcontainer-deny"] != "v1"))
|
|
175
|
+
{
|
|
176
|
+
throw new ArgumentException("AppContainer deny mode requires its exact closed fields.");
|
|
177
|
+
}
|
|
137
178
|
|
|
138
179
|
int timeout;
|
|
139
180
|
ulong memory;
|
|
@@ -158,6 +199,22 @@ internal static class Program
|
|
|
158
199
|
{
|
|
159
200
|
throw new ArgumentException("Executable, working directory, and writable temp must stay inside sandbox-root.");
|
|
160
201
|
}
|
|
202
|
+
string protectedSentinel = appContainerDeny
|
|
203
|
+
? Path.GetFullPath(values["--protected-sentinel"])
|
|
204
|
+
: null;
|
|
205
|
+
if (appContainerDeny &&
|
|
206
|
+
(IsInsideRoot(protectedSentinel, root, rootPrefix) || !File.Exists(protectedSentinel)))
|
|
207
|
+
{
|
|
208
|
+
throw new ArgumentException("Protected sentinel must be an existing caller-readable file outside sandbox-root.");
|
|
209
|
+
}
|
|
210
|
+
string canaryExecutable = appContainerDeny
|
|
211
|
+
? Path.GetFullPath(values["--canary-executable"])
|
|
212
|
+
: null;
|
|
213
|
+
if (appContainerDeny &&
|
|
214
|
+
(!IsInsideRoot(canaryExecutable, root, rootPrefix) || !File.Exists(canaryExecutable)))
|
|
215
|
+
{
|
|
216
|
+
throw new ArgumentException("AppContainer canary must be an existing file inside sandbox-root.");
|
|
217
|
+
}
|
|
161
218
|
|
|
162
219
|
string[] child = new string[args.Length - separator - 1];
|
|
163
220
|
Array.Copy(args, separator + 1, child, 0, child.Length);
|
|
@@ -169,6 +226,9 @@ internal static class Program
|
|
|
169
226
|
TimeoutMilliseconds = timeout,
|
|
170
227
|
ProcessMemoryBytes = new UIntPtr(memory),
|
|
171
228
|
ChildArguments = child,
|
|
229
|
+
AppContainerDeny = appContainerDeny,
|
|
230
|
+
ProtectedSentinel = protectedSentinel,
|
|
231
|
+
CanaryExecutable = canaryExecutable,
|
|
172
232
|
};
|
|
173
233
|
}
|
|
174
234
|
|
|
@@ -184,6 +244,8 @@ internal static class Program
|
|
|
184
244
|
IntPtr processToken = IntPtr.Zero;
|
|
185
245
|
IntPtr restrictedToken = IntPtr.Zero;
|
|
186
246
|
IntPtr lowSid = IntPtr.Zero;
|
|
247
|
+
IntPtr appContainerSid = IntPtr.Zero;
|
|
248
|
+
IntPtr childToken = IntPtr.Zero;
|
|
187
249
|
IntPtr job = IntPtr.Zero;
|
|
188
250
|
IntPtr stdoutRead = IntPtr.Zero;
|
|
189
251
|
IntPtr stdoutWrite = IntPtr.Zero;
|
|
@@ -192,12 +254,16 @@ internal static class Program
|
|
|
192
254
|
IntPtr nullInput = IntPtr.Zero;
|
|
193
255
|
IntPtr attributeList = IntPtr.Zero;
|
|
194
256
|
IntPtr handleList = IntPtr.Zero;
|
|
257
|
+
IntPtr securityCapabilitiesPointer = IntPtr.Zero;
|
|
258
|
+
string appContainerIdentity = null;
|
|
259
|
+
bool appContainerProfileCreated = false;
|
|
195
260
|
PROCESS_INFORMATION process = new PROCESS_INFORMATION();
|
|
196
261
|
|
|
197
262
|
try
|
|
198
263
|
{
|
|
199
264
|
ApplyLowIntegrityLabel(args.WritableTemp);
|
|
200
265
|
result.WritableTempLowIntegrity = true;
|
|
266
|
+
result.AppContainerMode = args.AppContainerDeny;
|
|
201
267
|
|
|
202
268
|
uint tokenAccess = TokenAssignPrimary | TokenDuplicate | TokenQuery | TokenAdjustDefault | TokenAdjustSessionId;
|
|
203
269
|
Check(OpenProcessToken(GetCurrentProcess(), tokenAccess, out processToken), "open-process-token");
|
|
@@ -223,6 +289,35 @@ internal static class Program
|
|
|
223
289
|
result.ProcessMemoryLimit = true;
|
|
224
290
|
result.UiRestrictions = true;
|
|
225
291
|
|
|
292
|
+
if (args.AppContainerDeny)
|
|
293
|
+
{
|
|
294
|
+
appContainerIdentity = "HoloScript.HoloSystem.WHPX_" + Guid.NewGuid().ToString("N");
|
|
295
|
+
int profileResult = CreateAppContainerProfile(
|
|
296
|
+
appContainerIdentity,
|
|
297
|
+
"HoloSystem WHPX isolation",
|
|
298
|
+
"Ephemeral zero-capability HoloSystem VM host boundary",
|
|
299
|
+
IntPtr.Zero,
|
|
300
|
+
0,
|
|
301
|
+
out appContainerSid
|
|
302
|
+
);
|
|
303
|
+
if (profileResult != 0)
|
|
304
|
+
{
|
|
305
|
+
throw new LauncherException("create-appcontainer-profile", profileResult);
|
|
306
|
+
}
|
|
307
|
+
appContainerProfileCreated = true;
|
|
308
|
+
SecurityIdentifier appSid = new SecurityIdentifier(appContainerSid);
|
|
309
|
+
GrantDirectoryAccess(args.SandboxRoot, appSid, FileSystemRights.ReadAndExecute | FileSystemRights.ListDirectory);
|
|
310
|
+
result.SnapshotReadExecuteGrant = true;
|
|
311
|
+
GrantDirectoryAccess(args.WritableTemp, appSid, FileSystemRights.Modify);
|
|
312
|
+
result.WritableTempModifyGrant = true;
|
|
313
|
+
HardenProtectedSentinel(args.ProtectedSentinel);
|
|
314
|
+
RunAppContainerCanaries(args, restrictedToken, appContainerSid, job, result);
|
|
315
|
+
if (!result.FilesystemCanaryDenied || !result.NetworkCanaryDenied)
|
|
316
|
+
{
|
|
317
|
+
throw new LauncherException("appcontainer-canary-denial", 0);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
226
321
|
SECURITY_ATTRIBUTES pipeSecurity = new SECURITY_ATTRIBUTES();
|
|
227
322
|
pipeSecurity.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
228
323
|
pipeSecurity.bInheritHandle = true;
|
|
@@ -235,9 +330,10 @@ internal static class Program
|
|
|
235
330
|
Check(nullInput != new IntPtr(-1), "open-null-input");
|
|
236
331
|
|
|
237
332
|
UIntPtr attributeBytes = UIntPtr.Zero;
|
|
238
|
-
|
|
333
|
+
int attributeCount = args.AppContainerDeny ? 2 : 1;
|
|
334
|
+
InitializeProcThreadAttributeList(IntPtr.Zero, attributeCount, 0, ref attributeBytes);
|
|
239
335
|
attributeList = Marshal.AllocHGlobal((int)attributeBytes.ToUInt64());
|
|
240
|
-
Check(InitializeProcThreadAttributeList(attributeList,
|
|
336
|
+
Check(InitializeProcThreadAttributeList(attributeList, attributeCount, 0, ref attributeBytes), "initialize-handle-allowlist");
|
|
241
337
|
handleList = Marshal.AllocHGlobal(IntPtr.Size * 3);
|
|
242
338
|
Marshal.WriteIntPtr(handleList, 0, nullInput);
|
|
243
339
|
Marshal.WriteIntPtr(handleList, IntPtr.Size, stdoutWrite);
|
|
@@ -245,7 +341,7 @@ internal static class Program
|
|
|
245
341
|
Check(UpdateProcThreadAttribute(
|
|
246
342
|
attributeList,
|
|
247
343
|
0,
|
|
248
|
-
|
|
344
|
+
ProcThreadAttributeHandleList,
|
|
249
345
|
handleList,
|
|
250
346
|
new UIntPtr((uint)(IntPtr.Size * 3)),
|
|
251
347
|
IntPtr.Zero,
|
|
@@ -253,6 +349,27 @@ internal static class Program
|
|
|
253
349
|
), "set-handle-allowlist");
|
|
254
350
|
result.HandleAllowlist = true;
|
|
255
351
|
|
|
352
|
+
if (args.AppContainerDeny)
|
|
353
|
+
{
|
|
354
|
+
SECURITY_CAPABILITIES securityCapabilities = new SECURITY_CAPABILITIES();
|
|
355
|
+
securityCapabilities.AppContainerSid = appContainerSid;
|
|
356
|
+
securityCapabilities.Capabilities = IntPtr.Zero;
|
|
357
|
+
securityCapabilities.CapabilityCount = 0;
|
|
358
|
+
securityCapabilities.Reserved = 0;
|
|
359
|
+
int securityCapabilitiesSize = Marshal.SizeOf(typeof(SECURITY_CAPABILITIES));
|
|
360
|
+
securityCapabilitiesPointer = Marshal.AllocHGlobal(securityCapabilitiesSize);
|
|
361
|
+
Marshal.StructureToPtr(securityCapabilities, securityCapabilitiesPointer, false);
|
|
362
|
+
Check(UpdateProcThreadAttribute(
|
|
363
|
+
attributeList,
|
|
364
|
+
0,
|
|
365
|
+
ProcThreadAttributeSecurityCapabilities,
|
|
366
|
+
securityCapabilitiesPointer,
|
|
367
|
+
new UIntPtr((uint)securityCapabilitiesSize),
|
|
368
|
+
IntPtr.Zero,
|
|
369
|
+
IntPtr.Zero
|
|
370
|
+
), "set-appcontainer-security-capabilities");
|
|
371
|
+
}
|
|
372
|
+
|
|
256
373
|
STARTUPINFOEX startup = new STARTUPINFOEX();
|
|
257
374
|
startup.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX));
|
|
258
375
|
startup.StartupInfo.dwFlags = StartfUseStdHandles;
|
|
@@ -279,6 +396,22 @@ internal static class Program
|
|
|
279
396
|
Check(created, "create-restricted-process");
|
|
280
397
|
result.Launched = true;
|
|
281
398
|
|
|
399
|
+
if (args.AppContainerDeny)
|
|
400
|
+
{
|
|
401
|
+
Check(OpenProcessToken(process.hProcess, TokenQuery, out childToken), "open-appcontainer-process-token");
|
|
402
|
+
result.AppContainer = IsAppContainer(childToken);
|
|
403
|
+
result.AppContainerSidMatched = AppContainerSidMatches(childToken, appContainerSid);
|
|
404
|
+
result.CapabilityCount = TokenGroupCount(childToken, TokenCapabilities);
|
|
405
|
+
result.LowIntegrity = HasLowIntegrity(childToken);
|
|
406
|
+
result.EnabledPrivilegeCount = EnabledPrivilegeCount(childToken);
|
|
407
|
+
result.PrivilegesBounded = result.EnabledPrivilegeCount <= 1;
|
|
408
|
+
if (!result.AppContainer || !result.AppContainerSidMatched || result.CapabilityCount != 0 ||
|
|
409
|
+
!result.LowIntegrity || !result.PrivilegesBounded)
|
|
410
|
+
{
|
|
411
|
+
throw new LauncherException("appcontainer-token-evidence", 0);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
282
415
|
CloseHandle(stdoutWrite);
|
|
283
416
|
stdoutWrite = IntPtr.Zero;
|
|
284
417
|
CloseHandle(stderrWrite);
|
|
@@ -328,11 +461,24 @@ internal static class Program
|
|
|
328
461
|
CloseIfValid(nullInput);
|
|
329
462
|
if (attributeList != IntPtr.Zero) DeleteProcThreadAttributeList(attributeList);
|
|
330
463
|
if (handleList != IntPtr.Zero) Marshal.FreeHGlobal(handleList);
|
|
464
|
+
if (securityCapabilitiesPointer != IntPtr.Zero) Marshal.FreeHGlobal(securityCapabilitiesPointer);
|
|
331
465
|
if (attributeList != IntPtr.Zero) Marshal.FreeHGlobal(attributeList);
|
|
332
466
|
CloseIfValid(job);
|
|
467
|
+
CloseIfValid(childToken);
|
|
333
468
|
CloseIfValid(restrictedToken);
|
|
334
469
|
CloseIfValid(processToken);
|
|
335
470
|
if (lowSid != IntPtr.Zero) LocalFree(lowSid);
|
|
471
|
+
if (appContainerProfileCreated)
|
|
472
|
+
{
|
|
473
|
+
int deleteResult = DeleteAppContainerProfile(appContainerIdentity);
|
|
474
|
+
result.ProfileDeleted = deleteResult == 0;
|
|
475
|
+
if (deleteResult != 0 && result.ErrorStage == null)
|
|
476
|
+
{
|
|
477
|
+
result.ErrorStage = "delete-appcontainer-profile";
|
|
478
|
+
result.ErrorCode = deleteResult;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (appContainerSid != IntPtr.Zero) FreeSid(appContainerSid);
|
|
336
482
|
}
|
|
337
483
|
return result;
|
|
338
484
|
}
|
|
@@ -352,6 +498,224 @@ internal static class Program
|
|
|
352
498
|
}
|
|
353
499
|
}
|
|
354
500
|
|
|
501
|
+
private static void GrantDirectoryAccess(string path, SecurityIdentifier sid, FileSystemRights rights)
|
|
502
|
+
{
|
|
503
|
+
DirectoryInfo directory = new DirectoryInfo(path);
|
|
504
|
+
DirectorySecurity security = directory.GetAccessControl(AccessControlSections.Access);
|
|
505
|
+
security.AddAccessRule(new FileSystemAccessRule(
|
|
506
|
+
sid,
|
|
507
|
+
rights | FileSystemRights.Synchronize,
|
|
508
|
+
InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
|
|
509
|
+
PropagationFlags.None,
|
|
510
|
+
AccessControlType.Allow
|
|
511
|
+
));
|
|
512
|
+
directory.SetAccessControl(security);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private static void HardenProtectedSentinel(string path)
|
|
516
|
+
{
|
|
517
|
+
SecurityIdentifier user = WindowsIdentity.GetCurrent().User;
|
|
518
|
+
if (user == null) throw new InvalidOperationException("Current user SID is unavailable.");
|
|
519
|
+
FileSecurity security = new FileSecurity();
|
|
520
|
+
security.SetOwner(user);
|
|
521
|
+
security.SetAccessRuleProtection(true, false);
|
|
522
|
+
security.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.FullControl, AccessControlType.Allow));
|
|
523
|
+
File.SetAccessControl(path, security);
|
|
524
|
+
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
|
525
|
+
{
|
|
526
|
+
if (stream.Length == 0) throw new InvalidOperationException("Protected sentinel is empty.");
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private static void RunAppContainerCanaries(
|
|
531
|
+
Arguments args,
|
|
532
|
+
IntPtr restrictedToken,
|
|
533
|
+
IntPtr appContainerSid,
|
|
534
|
+
IntPtr job,
|
|
535
|
+
LaunchResult result)
|
|
536
|
+
{
|
|
537
|
+
TcpListener listener = new TcpListener(IPAddress.Loopback, 0);
|
|
538
|
+
listener.Start(1);
|
|
539
|
+
try
|
|
540
|
+
{
|
|
541
|
+
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
|
542
|
+
Dictionary<string, int> evidence = RunAppContainerCanaryChild(
|
|
543
|
+
args,
|
|
544
|
+
restrictedToken,
|
|
545
|
+
appContainerSid,
|
|
546
|
+
job,
|
|
547
|
+
port
|
|
548
|
+
);
|
|
549
|
+
result.FilesystemCanaryError = evidence["filesystemError"];
|
|
550
|
+
result.NetworkCanaryError = evidence["networkError"];
|
|
551
|
+
result.LoopbackAccepted = listener.Pending();
|
|
552
|
+
result.FilesystemCanaryDenied = evidence["appContainer"] == 1 &&
|
|
553
|
+
evidence["capabilityCount"] == 0 &&
|
|
554
|
+
result.FilesystemCanaryError == ErrorAccessDenied;
|
|
555
|
+
result.NetworkCanaryDenied = evidence["appContainer"] == 1 &&
|
|
556
|
+
evidence["capabilityCount"] == 0 &&
|
|
557
|
+
(result.NetworkCanaryError == WsaAccessDenied ||
|
|
558
|
+
result.NetworkCanaryError == WsaTimedOut) &&
|
|
559
|
+
!result.LoopbackAccepted;
|
|
560
|
+
}
|
|
561
|
+
finally
|
|
562
|
+
{
|
|
563
|
+
listener.Stop();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private static Dictionary<string, int> RunAppContainerCanaryChild(
|
|
568
|
+
Arguments args,
|
|
569
|
+
IntPtr restrictedToken,
|
|
570
|
+
IntPtr appContainerSid,
|
|
571
|
+
IntPtr job,
|
|
572
|
+
int port)
|
|
573
|
+
{
|
|
574
|
+
IntPtr stdoutRead = IntPtr.Zero;
|
|
575
|
+
IntPtr stdoutWrite = IntPtr.Zero;
|
|
576
|
+
IntPtr stderrRead = IntPtr.Zero;
|
|
577
|
+
IntPtr stderrWrite = IntPtr.Zero;
|
|
578
|
+
IntPtr nullInput = IntPtr.Zero;
|
|
579
|
+
IntPtr attributeList = IntPtr.Zero;
|
|
580
|
+
IntPtr handleList = IntPtr.Zero;
|
|
581
|
+
IntPtr securityCapabilitiesPointer = IntPtr.Zero;
|
|
582
|
+
PROCESS_INFORMATION process = new PROCESS_INFORMATION();
|
|
583
|
+
try
|
|
584
|
+
{
|
|
585
|
+
SECURITY_ATTRIBUTES pipeSecurity = new SECURITY_ATTRIBUTES();
|
|
586
|
+
pipeSecurity.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
587
|
+
pipeSecurity.bInheritHandle = true;
|
|
588
|
+
Check(CreatePipe(out stdoutRead, out stdoutWrite, ref pipeSecurity, 0), "create-canary-stdout-pipe");
|
|
589
|
+
Check(SetHandleInformation(stdoutRead, HandleFlagInherit, 0), "protect-canary-stdout-reader");
|
|
590
|
+
Check(CreatePipe(out stderrRead, out stderrWrite, ref pipeSecurity, 0), "create-canary-stderr-pipe");
|
|
591
|
+
Check(SetHandleInformation(stderrRead, HandleFlagInherit, 0), "protect-canary-stderr-reader");
|
|
592
|
+
nullInput = CreateFile("NUL", GenericRead, FileShareRead | FileShareWrite, ref pipeSecurity, OpenExisting, FileAttributeNormal, IntPtr.Zero);
|
|
593
|
+
Check(nullInput != new IntPtr(-1), "open-canary-null-input");
|
|
594
|
+
|
|
595
|
+
UIntPtr attributeBytes = UIntPtr.Zero;
|
|
596
|
+
InitializeProcThreadAttributeList(IntPtr.Zero, 2, 0, ref attributeBytes);
|
|
597
|
+
attributeList = Marshal.AllocHGlobal((int)attributeBytes.ToUInt64());
|
|
598
|
+
Check(InitializeProcThreadAttributeList(attributeList, 2, 0, ref attributeBytes), "initialize-canary-attributes");
|
|
599
|
+
handleList = Marshal.AllocHGlobal(IntPtr.Size * 3);
|
|
600
|
+
Marshal.WriteIntPtr(handleList, 0, nullInput);
|
|
601
|
+
Marshal.WriteIntPtr(handleList, IntPtr.Size, stdoutWrite);
|
|
602
|
+
Marshal.WriteIntPtr(handleList, IntPtr.Size * 2, stderrWrite);
|
|
603
|
+
Check(UpdateProcThreadAttribute(
|
|
604
|
+
attributeList,
|
|
605
|
+
0,
|
|
606
|
+
ProcThreadAttributeHandleList,
|
|
607
|
+
handleList,
|
|
608
|
+
new UIntPtr((uint)(IntPtr.Size * 3)),
|
|
609
|
+
IntPtr.Zero,
|
|
610
|
+
IntPtr.Zero
|
|
611
|
+
), "set-canary-handle-allowlist");
|
|
612
|
+
|
|
613
|
+
SECURITY_CAPABILITIES securityCapabilities = new SECURITY_CAPABILITIES();
|
|
614
|
+
securityCapabilities.AppContainerSid = appContainerSid;
|
|
615
|
+
int securityCapabilitiesSize = Marshal.SizeOf(typeof(SECURITY_CAPABILITIES));
|
|
616
|
+
securityCapabilitiesPointer = Marshal.AllocHGlobal(securityCapabilitiesSize);
|
|
617
|
+
Marshal.StructureToPtr(securityCapabilities, securityCapabilitiesPointer, false);
|
|
618
|
+
Check(UpdateProcThreadAttribute(
|
|
619
|
+
attributeList,
|
|
620
|
+
0,
|
|
621
|
+
ProcThreadAttributeSecurityCapabilities,
|
|
622
|
+
securityCapabilitiesPointer,
|
|
623
|
+
new UIntPtr((uint)securityCapabilitiesSize),
|
|
624
|
+
IntPtr.Zero,
|
|
625
|
+
IntPtr.Zero
|
|
626
|
+
), "set-canary-appcontainer");
|
|
627
|
+
|
|
628
|
+
string executable = args.CanaryExecutable;
|
|
629
|
+
string[] canaryArguments = {
|
|
630
|
+
"--protected-sentinel",
|
|
631
|
+
args.ProtectedSentinel,
|
|
632
|
+
"--loopback-port",
|
|
633
|
+
port.ToString()
|
|
634
|
+
};
|
|
635
|
+
STARTUPINFOEX startup = new STARTUPINFOEX();
|
|
636
|
+
startup.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX));
|
|
637
|
+
startup.StartupInfo.dwFlags = StartfUseStdHandles;
|
|
638
|
+
startup.StartupInfo.hStdInput = nullInput;
|
|
639
|
+
startup.StartupInfo.hStdOutput = stdoutWrite;
|
|
640
|
+
startup.StartupInfo.hStdError = stderrWrite;
|
|
641
|
+
startup.lpAttributeList = attributeList;
|
|
642
|
+
StringBuilder commandLine = new StringBuilder(BuildCommandLine(executable, canaryArguments));
|
|
643
|
+
uint flags = CreateSuspended | CreateNoWindow | CreateUnicodeEnvironment | ExtendedStartupInfoPresent;
|
|
644
|
+
Check(CreateProcessAsUser(
|
|
645
|
+
restrictedToken,
|
|
646
|
+
executable,
|
|
647
|
+
commandLine,
|
|
648
|
+
IntPtr.Zero,
|
|
649
|
+
IntPtr.Zero,
|
|
650
|
+
true,
|
|
651
|
+
flags,
|
|
652
|
+
IntPtr.Zero,
|
|
653
|
+
args.SandboxRoot,
|
|
654
|
+
ref startup,
|
|
655
|
+
out process
|
|
656
|
+
), "create-appcontainer-canary");
|
|
657
|
+
CloseHandle(stdoutWrite);
|
|
658
|
+
stdoutWrite = IntPtr.Zero;
|
|
659
|
+
CloseHandle(stderrWrite);
|
|
660
|
+
stderrWrite = IntPtr.Zero;
|
|
661
|
+
Check(AssignProcessToJobObject(job, process.hProcess), "assign-canary-to-job");
|
|
662
|
+
Task<byte[]> stdoutTask = ReadBoundedAsync(stdoutRead);
|
|
663
|
+
Task<byte[]> stderrTask = ReadBoundedAsync(stderrRead);
|
|
664
|
+
uint resumed = ResumeThread(process.hThread);
|
|
665
|
+
if (resumed == UInt32.MaxValue) ThrowWin32("resume-appcontainer-canary");
|
|
666
|
+
uint wait = WaitForSingleObject(process.hProcess, 10000);
|
|
667
|
+
if (wait != WaitObject0)
|
|
668
|
+
{
|
|
669
|
+
TerminateProcess(process.hProcess, 0x7d);
|
|
670
|
+
WaitForSingleObject(process.hProcess, 5000);
|
|
671
|
+
throw new LauncherException("wait-appcontainer-canary", unchecked((int)wait));
|
|
672
|
+
}
|
|
673
|
+
uint exitCode;
|
|
674
|
+
Check(GetExitCodeProcess(process.hProcess, out exitCode), "read-canary-exit");
|
|
675
|
+
Task.WaitAll(new Task[] { stdoutTask, stderrTask }, 5000);
|
|
676
|
+
byte[] stdout = stdoutTask.IsCompleted ? stdoutTask.Result : new byte[0];
|
|
677
|
+
byte[] stderr = stderrTask.IsCompleted ? stderrTask.Result : new byte[0];
|
|
678
|
+
if (exitCode != 0 || stderr.Length != 0) throw new InvalidOperationException("AppContainer canary failed closed.");
|
|
679
|
+
return ParseCanaryEvidence(Encoding.UTF8.GetString(stdout).Trim());
|
|
680
|
+
}
|
|
681
|
+
finally
|
|
682
|
+
{
|
|
683
|
+
CloseIfValid(process.hThread);
|
|
684
|
+
CloseIfValid(process.hProcess);
|
|
685
|
+
CloseIfValid(stdoutRead);
|
|
686
|
+
CloseIfValid(stdoutWrite);
|
|
687
|
+
CloseIfValid(stderrRead);
|
|
688
|
+
CloseIfValid(stderrWrite);
|
|
689
|
+
CloseIfValid(nullInput);
|
|
690
|
+
if (attributeList != IntPtr.Zero) DeleteProcThreadAttributeList(attributeList);
|
|
691
|
+
if (handleList != IntPtr.Zero) Marshal.FreeHGlobal(handleList);
|
|
692
|
+
if (securityCapabilitiesPointer != IntPtr.Zero) Marshal.FreeHGlobal(securityCapabilitiesPointer);
|
|
693
|
+
if (attributeList != IntPtr.Zero) Marshal.FreeHGlobal(attributeList);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
private static Dictionary<string, int> ParseCanaryEvidence(string value)
|
|
698
|
+
{
|
|
699
|
+
Dictionary<string, int> parsed = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
700
|
+
foreach (string field in value.Split(';'))
|
|
701
|
+
{
|
|
702
|
+
string[] pair = field.Split(new char[] { '=' }, 2);
|
|
703
|
+
int number;
|
|
704
|
+
if (pair.Length != 2 || parsed.ContainsKey(pair[0]) || !Int32.TryParse(pair[1], out number))
|
|
705
|
+
{
|
|
706
|
+
throw new InvalidOperationException("AppContainer canary protocol is invalid.");
|
|
707
|
+
}
|
|
708
|
+
parsed.Add(pair[0], number);
|
|
709
|
+
}
|
|
710
|
+
string[] keys = { "appContainer", "capabilityCount", "filesystemError", "networkError" };
|
|
711
|
+
if (parsed.Count != keys.Length) throw new InvalidOperationException("AppContainer canary protocol is incomplete.");
|
|
712
|
+
foreach (string key in keys)
|
|
713
|
+
{
|
|
714
|
+
if (!parsed.ContainsKey(key)) throw new InvalidOperationException("AppContainer canary field is missing.");
|
|
715
|
+
}
|
|
716
|
+
return parsed;
|
|
717
|
+
}
|
|
718
|
+
|
|
355
719
|
private static void SetTokenIntegrity(IntPtr token, IntPtr sid)
|
|
356
720
|
{
|
|
357
721
|
int sidLength = (int)GetLengthSid(sid);
|
|
@@ -414,6 +778,51 @@ internal static class Program
|
|
|
414
778
|
}
|
|
415
779
|
}
|
|
416
780
|
|
|
781
|
+
private static bool IsAppContainer(IntPtr token)
|
|
782
|
+
{
|
|
783
|
+
IntPtr buffer = QueryTokenInformation(token, TokenIsAppContainer);
|
|
784
|
+
try
|
|
785
|
+
{
|
|
786
|
+
return Marshal.ReadInt32(buffer) != 0;
|
|
787
|
+
}
|
|
788
|
+
finally
|
|
789
|
+
{
|
|
790
|
+
Marshal.FreeHGlobal(buffer);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
private static int TokenGroupCount(IntPtr token, int informationClass)
|
|
795
|
+
{
|
|
796
|
+
IntPtr buffer = QueryTokenInformation(token, informationClass);
|
|
797
|
+
try
|
|
798
|
+
{
|
|
799
|
+
return Marshal.ReadInt32(buffer);
|
|
800
|
+
}
|
|
801
|
+
finally
|
|
802
|
+
{
|
|
803
|
+
Marshal.FreeHGlobal(buffer);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private static bool AppContainerSidMatches(IntPtr token, IntPtr expectedSid)
|
|
808
|
+
{
|
|
809
|
+
IntPtr buffer = QueryTokenInformation(token, TokenAppContainerSid);
|
|
810
|
+
try
|
|
811
|
+
{
|
|
812
|
+
TOKEN_APPCONTAINER_INFORMATION information =
|
|
813
|
+
(TOKEN_APPCONTAINER_INFORMATION)Marshal.PtrToStructure(
|
|
814
|
+
buffer,
|
|
815
|
+
typeof(TOKEN_APPCONTAINER_INFORMATION)
|
|
816
|
+
);
|
|
817
|
+
return information.TokenAppContainer != IntPtr.Zero &&
|
|
818
|
+
EqualSid(information.TokenAppContainer, expectedSid);
|
|
819
|
+
}
|
|
820
|
+
finally
|
|
821
|
+
{
|
|
822
|
+
Marshal.FreeHGlobal(buffer);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
417
826
|
private static IntPtr QueryTokenInformation(IntPtr token, int informationClass)
|
|
418
827
|
{
|
|
419
828
|
int length = 0;
|
|
@@ -529,12 +938,8 @@ internal static class Program
|
|
|
529
938
|
|
|
530
939
|
private static string ToJson(LaunchResult result)
|
|
531
940
|
{
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
"\"launched\":" + Bool(result.Launched) + "," +
|
|
535
|
-
"\"timedOut\":" + Bool(result.TimedOut) + "," +
|
|
536
|
-
"\"exitCode\":" + (result.ExitCode.HasValue ? result.ExitCode.Value.ToString() : "null") + "," +
|
|
537
|
-
"\"isolation\":{" +
|
|
941
|
+
string isolation = result.AppContainerMode
|
|
942
|
+
?
|
|
538
943
|
"\"filteredToken\":" + Bool(result.FilteredToken) + "," +
|
|
539
944
|
"\"disableMaxPrivilege\":" + Bool(result.DisableMaxPrivilege) + "," +
|
|
540
945
|
"\"enabledPrivilegeCount\":" + result.EnabledPrivilegeCount.ToString() + "," +
|
|
@@ -546,8 +951,36 @@ internal static class Program
|
|
|
546
951
|
"\"activeProcessLimit\":" + Bool(result.ActiveProcessLimit) + "," +
|
|
547
952
|
"\"processMemoryLimit\":" + Bool(result.ProcessMemoryLimit) + "," +
|
|
548
953
|
"\"uiRestrictions\":" + Bool(result.UiRestrictions) + "," +
|
|
549
|
-
"\"
|
|
550
|
-
|
|
954
|
+
"\"appContainer\":" + Bool(result.AppContainer) + "," +
|
|
955
|
+
"\"appContainerSidMatched\":" + Bool(result.AppContainerSidMatched) + "," +
|
|
956
|
+
"\"capabilityCount\":" + result.CapabilityCount.ToString() + "," +
|
|
957
|
+
"\"snapshotReadExecuteGrant\":" + Bool(result.SnapshotReadExecuteGrant) + "," +
|
|
958
|
+
"\"writableTempModifyGrant\":" + Bool(result.WritableTempModifyGrant) + "," +
|
|
959
|
+
"\"filesystemCanaryDenied\":" + Bool(result.FilesystemCanaryDenied) + "," +
|
|
960
|
+
"\"filesystemCanaryError\":" + result.FilesystemCanaryError.ToString() + "," +
|
|
961
|
+
"\"networkCanaryDenied\":" + Bool(result.NetworkCanaryDenied) + "," +
|
|
962
|
+
"\"networkCanaryError\":" + result.NetworkCanaryError.ToString() + "," +
|
|
963
|
+
"\"loopbackAccepted\":" + Bool(result.LoopbackAccepted) + "," +
|
|
964
|
+
"\"profileDeleted\":" + Bool(result.ProfileDeleted)
|
|
965
|
+
:
|
|
966
|
+
"\"filteredToken\":" + Bool(result.FilteredToken) + "," +
|
|
967
|
+
"\"disableMaxPrivilege\":" + Bool(result.DisableMaxPrivilege) + "," +
|
|
968
|
+
"\"enabledPrivilegeCount\":" + result.EnabledPrivilegeCount.ToString() + "," +
|
|
969
|
+
"\"privilegesBounded\":" + Bool(result.PrivilegesBounded) + "," +
|
|
970
|
+
"\"lowIntegrity\":" + Bool(result.LowIntegrity) + "," +
|
|
971
|
+
"\"assignedBeforeResume\":" + Bool(result.AssignedBeforeResume) + "," +
|
|
972
|
+
"\"handleAllowlist\":" + Bool(result.HandleAllowlist) + "," +
|
|
973
|
+
"\"killOnClose\":" + Bool(result.KillOnClose) + "," +
|
|
974
|
+
"\"activeProcessLimit\":" + Bool(result.ActiveProcessLimit) + "," +
|
|
975
|
+
"\"processMemoryLimit\":" + Bool(result.ProcessMemoryLimit) + "," +
|
|
976
|
+
"\"uiRestrictions\":" + Bool(result.UiRestrictions) + "," +
|
|
977
|
+
"\"writableTempLowIntegrity\":" + Bool(result.WritableTempLowIntegrity);
|
|
978
|
+
return "{" +
|
|
979
|
+
"\"protocol\":\"" + (result.AppContainerMode ? AppContainerProtocol : Protocol) + "\"," +
|
|
980
|
+
"\"launched\":" + Bool(result.Launched) + "," +
|
|
981
|
+
"\"timedOut\":" + Bool(result.TimedOut) + "," +
|
|
982
|
+
"\"exitCode\":" + (result.ExitCode.HasValue ? result.ExitCode.Value.ToString() : "null") + "," +
|
|
983
|
+
"\"isolation\":{" + isolation + "}," +
|
|
551
984
|
"\"stdoutBase64\":\"" + Convert.ToBase64String(result.StandardOutput) + "\"," +
|
|
552
985
|
"\"stderrBase64\":\"" + Convert.ToBase64String(result.StandardError) + "\"," +
|
|
553
986
|
"\"errorStage\":" + (result.ErrorStage == null ? "null" : "\"" + EscapeJson(result.ErrorStage) + "\"") + "," +
|
|
@@ -597,6 +1030,16 @@ internal static class Program
|
|
|
597
1030
|
[StructLayout(LayoutKind.Sequential)]
|
|
598
1031
|
private struct TOKEN_MANDATORY_LABEL { public SID_AND_ATTRIBUTES Label; }
|
|
599
1032
|
[StructLayout(LayoutKind.Sequential)]
|
|
1033
|
+
private struct TOKEN_APPCONTAINER_INFORMATION { public IntPtr TokenAppContainer; }
|
|
1034
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
1035
|
+
private struct SECURITY_CAPABILITIES
|
|
1036
|
+
{
|
|
1037
|
+
public IntPtr AppContainerSid;
|
|
1038
|
+
public IntPtr Capabilities;
|
|
1039
|
+
public uint CapabilityCount;
|
|
1040
|
+
public uint Reserved;
|
|
1041
|
+
}
|
|
1042
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
600
1043
|
private struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; [MarshalAs(UnmanagedType.Bool)] public bool bInheritHandle; }
|
|
601
1044
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
602
1045
|
private struct STARTUPINFO
|
|
@@ -643,6 +1086,7 @@ internal static class Program
|
|
|
643
1086
|
[DllImport("kernel32.dll", SetLastError = true)] private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
|
644
1087
|
[DllImport("kernel32.dll", SetLastError = true)] private static extern uint ResumeThread(IntPtr thread);
|
|
645
1088
|
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode);
|
|
1089
|
+
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool TerminateProcess(IntPtr process, uint exitCode);
|
|
646
1090
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr CreateJobObject(IntPtr attributes, string name);
|
|
647
1091
|
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetInformationJobObject(IntPtr job, int informationClass, IntPtr information, uint length);
|
|
648
1092
|
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
|
@@ -658,10 +1102,16 @@ internal static class Program
|
|
|
658
1102
|
[DllImport("advapi32.dll", SetLastError = true)] private static extern IntPtr GetSidSubAuthorityCount(IntPtr sid);
|
|
659
1103
|
[DllImport("advapi32.dll", SetLastError = true)] private static extern IntPtr GetSidSubAuthority(IntPtr sid, uint subAuthority);
|
|
660
1104
|
[DllImport("advapi32.dll", SetLastError = true)] private static extern uint GetLengthSid(IntPtr sid);
|
|
1105
|
+
[DllImport("advapi32.dll", SetLastError = true)] private static extern bool EqualSid(IntPtr firstSid, IntPtr secondSid);
|
|
1106
|
+
[DllImport("advapi32.dll", SetLastError = true)] private static extern IntPtr FreeSid(IntPtr sid);
|
|
661
1107
|
[DllImport("advapi32.dll", SetLastError = true)] private static extern bool CopySid(uint destinationLength, IntPtr destination, IntPtr source);
|
|
662
1108
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool ConvertStringSidToSid(string sid, out IntPtr binarySid);
|
|
663
1109
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(string descriptor, uint revision, out IntPtr securityDescriptor, out uint size);
|
|
664
1110
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool SetFileSecurity(string path, uint securityInformation, IntPtr securityDescriptor);
|
|
665
1111
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
666
1112
|
private static extern bool CreateProcessAsUser(IntPtr token, string applicationName, StringBuilder commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref STARTUPINFOEX startupInfo, out PROCESS_INFORMATION processInformation);
|
|
1113
|
+
[DllImport("userenv.dll", CharSet = CharSet.Unicode)]
|
|
1114
|
+
private static extern int CreateAppContainerProfile(string appContainerName, string displayName, string description, IntPtr capabilities, uint capabilityCount, out IntPtr appContainerSid);
|
|
1115
|
+
[DllImport("userenv.dll", CharSet = CharSet.Unicode)]
|
|
1116
|
+
private static extern int DeleteAppContainerProfile(string appContainerName);
|
|
667
1117
|
}
|