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,45 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "binary": "TaskChannelGuard.exe",
4
+ "binarySha256": "32cb53e86a72cf26d1159d2a3ba85a9ca7e91f795cb06372f8881b03a2fe7079",
5
+ "inputs": {
6
+ "targetFramework": ".NETFramework,Version=v4.6.2",
7
+ "platform": "AnyCPU",
8
+ "compilerVersion": "5.9.0-1.26357.3 (35d9211b841e7613c1d2f8f5af6d628ace696c4c)",
9
+ "compilerSha256": "3aafb7b9c54fa31a7092af35148971ee616965e7f9a0b80fb7fdc4bdd1d1a555",
10
+ "compilerArguments": [
11
+ "/nologo",
12
+ "/noconfig",
13
+ "/nostdlib+",
14
+ "/target:exe",
15
+ "/platform:anycpu",
16
+ "/langversion:7.3",
17
+ "/optimize+",
18
+ "/debug-",
19
+ "/deterministic+",
20
+ "/checked-",
21
+ "/unsafe-",
22
+ "/warn:4",
23
+ "/warnaserror+",
24
+ "/utf8output"
25
+ ],
26
+ "pathMap": "/_/mcp-pacemaker/windows-task-channel",
27
+ "sourceEncoding": "UTF-8 without BOM, LF line endings",
28
+ "sourceSha256": {
29
+ "AssemblyInfo.cs": "cd8b74ed1747f88261085f206ada2aa73f61a90f633fb7f0394c492e2d57e91a",
30
+ "ChannelCliContext.cs": "d99dcd49c1117a2581e4d9818010eed540fa60a880f2920c174a210e6d369348",
31
+ "ChannelFiles.cs": "ce4870cb839d49f84868014078a88c869f94b0604d985a64720e93423414d476",
32
+ "ChannelNative.cs": "fe19cfb88936273d466dd4e121175a3477ff6ae821306038fd3da455e9608b35",
33
+ "ChannelSession.cs": "9be542fac1ed3fb73b3d6088d7a46f58e6dd5e50c249abd92aa23cf7a051d943",
34
+ "TaskChannelGuard.cs": "3e6d1bf22da763a30c8abda1c032f7dbb73690679cb8d83d71ed4bd8cd706558"
35
+ },
36
+ "buildScriptSha256": "666cf533c635c3f01d34eed899f7d61d259bb777c442bde0bc38af2cacd3f7d6",
37
+ "referenceSha256": {
38
+ "mscorlib.dll": "7118fe658567606fa0538f7b5a2071e4606756c07e9062e1e5b24fa49e97d1f6",
39
+ "System.dll": "b76822f9c08aff597120e3167b3e0fbda836d6bc6f14bc4bc2eeffab5b2c6125",
40
+ "System.Core.dll": "774e965a6c25891a96d6d3b838ae10226ed9963a73b09e4be7c2ec275ffb5886",
41
+ "System.Management.dll": "bc35cddea6226db4d428a9fb70fcefe321a2af003c947edb812f3e911f8e4094",
42
+ "System.Web.Extensions.dll": "7a32bf3263aebed93c3a0847294aebdde15f7427541ed223a4cc4eb93c36d556"
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,8 @@
1
+ using System.Reflection;
2
+ using System.Runtime.InteropServices;
3
+
4
+ [assembly: AssemblyTitle("MCP Task Channel Guard")]
5
+ [assembly: AssemblyDescription("One-operation query-only Windows worker pipe identity")]
6
+ [assembly: AssemblyVersion("1.0.0.0")]
7
+ [assembly: AssemblyFileVersion("1.0.0.0")]
8
+ [assembly: ComVisible(false)]
@@ -0,0 +1,210 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.ComponentModel;
4
+ using System.Globalization;
5
+ using System.Linq;
6
+ using System.Runtime.InteropServices;
7
+
8
+ internal sealed class ChannelCliContext
9
+ {
10
+ private readonly ChannelNative native;
11
+ private readonly Action checkpoint;
12
+
13
+ internal ChannelCliContext(ChannelNative nativeApi, Action check)
14
+ {
15
+ native = nativeApi;
16
+ checkpoint = check;
17
+ }
18
+
19
+ internal int Parent(ProcessHandle self, int selfPid)
20
+ {
21
+ uint pipeOwner;
22
+ if (!ContextApi.GetNamedPipeServerProcessId(ContextApi.GetStdHandle(-10), out pipeOwner))
23
+ throw new ContextObservationException("CLI_PARENT_PIPE_UNVERIFIED");
24
+ var information = new ContextApi.BasicInformation();
25
+ int size = Marshal.SizeOf(information);
26
+ int returned;
27
+ int status = ContextApi.NtQueryInformationProcess(self, 0, out information, size, out returned);
28
+ bool valid = (IntPtr.Size == 4 || IntPtr.Size == 8) &&
29
+ size == 6 * IntPtr.Size &&
30
+ status == 0 &&
31
+ returned == size &&
32
+ information.processId.ToInt64() == selfPid &&
33
+ information.parentReserved.ToInt64() == pipeOwner &&
34
+ pipeOwner > 0 &&
35
+ pipeOwner <= int.MaxValue &&
36
+ pipeOwner != selfPid;
37
+ if (!valid) throw new ContextObservationException("CLI_PARENT_LAYOUT_OR_BINDING_UNVERIFIED");
38
+ return checked((int)pipeOwner);
39
+ }
40
+
41
+ internal ProcessHandle OpenParent(int pid)
42
+ {
43
+ var handle = ChannelApi.OpenProcess(0x101400, false, pid);
44
+ if (handle.IsInvalid)
45
+ {
46
+ handle.Dispose();
47
+ throw new ContextObservationException("CLI_PARENT_QUERY_ACCESS_DENIED");
48
+ }
49
+ return handle;
50
+ }
51
+
52
+ internal ContextResult Observe(ProcessHandle parent, Identity identity)
53
+ {
54
+ var result = new ContextResult
55
+ {
56
+ proofScope = "cli-effective-context-snapshot",
57
+ reason = "CONTEXT_UNVERIFIED",
58
+ observation = new ContextObservation
59
+ {
60
+ method = "pss-threads-held-token-query", processAccess = "0x101400",
61
+ captureFlags = "0x80", threadContextFlags = 0, atomicFutureProtection = false
62
+ }
63
+ };
64
+ try
65
+ {
66
+ checkpoint();
67
+ var firstFacts = native.Facts(parent, false, checkpoint);
68
+ result.actorFacts = firstFacts;
69
+ if (firstFacts.ownerSid != identity.ownerSid ||
70
+ firstFacts.sessionId != identity.sessionId) throw new ContextObservationException("PRIMARY_TOKEN_CHANGED");
71
+ if (firstFacts.elevated ||
72
+ firstFacts.enabledAdministrator) throw new ContextObservationException("PRIMARY_CONTEXT_PRIVILEGED");
73
+ if (firstFacts.guardThreadImpersonating) throw new ContextObservationException("GUARD_THREAD_IMPERSONATING");
74
+ var threads = native.ObserveThreads(parent, checkpoint);
75
+ result.observation.threadCount = threads.count;
76
+ if (threads.impersonating) throw new ContextObservationException("THREAD_TOKEN_OBSERVED");
77
+ var finalFacts = native.Facts(parent, false, checkpoint);
78
+ result.actorFacts = finalFacts;
79
+ result.observation.primaryStable = SamePrimary(firstFacts, finalFacts);
80
+ result.observation.completeStableThreadSet = true;
81
+ if (!result.observation.primaryStable) throw new ContextObservationException("PRIMARY_TOKEN_CHANGED");
82
+ finalFacts.parentThreadImpersonation = "observed-none";
83
+ finalFacts.parentThreadQueryError = null;
84
+ result.ordinaryEligible = Eligible(finalFacts, result.observation);
85
+ result.reason = result.ordinaryEligible ? "OBSERVED_NON_ELEVATED_NO_IMPERSONATION" : "CONTEXT_NOT_ORDINARY";
86
+ }
87
+ catch (ContextObservationException error)
88
+ {
89
+ result.reason = error.Message;
90
+ if (result.actorFacts != null)
91
+ {
92
+ result.actorFacts.parentThreadImpersonation = error.Message == "THREAD_TOKEN_OBSERVED"
93
+ ? "observed-impersonating" : "unverified";
94
+ result.actorFacts.parentThreadQueryError = error.Message;
95
+ }
96
+ }
97
+ catch (Win32Exception error)
98
+ {
99
+ result.reason = "CONTEXT_QUERY_FAILED_" + error.NativeErrorCode.ToString(CultureInfo.InvariantCulture);
100
+ }
101
+ catch (EntryPointNotFoundException) { result.reason = "CONTEXT_API_UNSUPPORTED"; }
102
+ catch (DllNotFoundException) { result.reason = "CONTEXT_API_UNAVAILABLE"; }
103
+ checkpoint();
104
+ result.observation.observedAt = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
105
+ return result;
106
+ }
107
+
108
+ internal bool Eligible(ActorFacts facts, ContextObservation observation)
109
+ {
110
+ return facts != null &&
111
+ observation != null &&
112
+ !facts.elevated &&
113
+ !facts.enabledAdministrator &&
114
+ !facts.guardThreadImpersonating &&
115
+ facts.parentThreadImpersonation == "observed-none" &&
116
+ observation.completeStableThreadSet &&
117
+ observation.primaryStable &&
118
+ observation.threadCount > 0 &&
119
+ observation.threadCount <= 128 &&
120
+ !observation.atomicFutureProtection;
121
+ }
122
+
123
+ private bool SamePrimary(ActorFacts first, ActorFacts second)
124
+ {
125
+ return first.ownerSid == second.ownerSid &&
126
+ first.sessionId == second.sessionId &&
127
+ first.elevated == second.elevated &&
128
+ first.enabledAdministrator == second.enabledAdministrator &&
129
+ first.restricted == second.restricted &&
130
+ first.appContainer == second.appContainer &&
131
+ first.guardThreadImpersonating == second.guardThreadImpersonating;
132
+ }
133
+
134
+ }
135
+
136
+ internal sealed class ContextObservationException : InvalidOperationException
137
+ {
138
+ internal ContextObservationException(string reason) : base(reason) { }
139
+ }
140
+
141
+ internal sealed class ContextResult
142
+ {
143
+ public string proofScope { get; set; }
144
+ public bool ordinaryEligible { get; set; }
145
+ public string reason { get; set; }
146
+ public ActorFacts actorFacts { get; set; }
147
+ public ContextObservation observation { get; set; }
148
+ }
149
+
150
+ internal sealed class ContextObservation
151
+ {
152
+ public string method { get; set; }
153
+ public string processAccess { get; set; }
154
+ public string captureFlags { get; set; }
155
+ public int threadContextFlags { get; set; }
156
+ public int threadCount { get; set; }
157
+ public bool completeStableThreadSet { get; set; }
158
+ public bool primaryStable { get; set; }
159
+ public bool atomicFutureProtection { get; set; }
160
+ public string observedAt { get; set; }
161
+ }
162
+
163
+ internal static class ContextApi
164
+ {
165
+ [StructLayout(LayoutKind.Sequential)]
166
+ internal struct BasicInformation
167
+ {
168
+ internal IntPtr reserved1, peb, reserved2, reserved3, processId, parentReserved;
169
+ }
170
+ [StructLayout(LayoutKind.Sequential)]
171
+ internal struct FileTime { internal uint low, high; }
172
+ [StructLayout(LayoutKind.Sequential)]
173
+ internal struct ThreadInformation { internal uint count, contextLength; }
174
+ [StructLayout(LayoutKind.Sequential)]
175
+ internal struct ThreadEntry
176
+ {
177
+ internal uint exitStatus;
178
+ internal IntPtr teb;
179
+ internal uint processId, threadId;
180
+ internal UIntPtr affinity;
181
+ internal int priority, basePriority;
182
+ internal IntPtr lastArgument;
183
+ internal ushort syscall;
184
+ internal FileTime creation, exit, kernel, user;
185
+ internal IntPtr start;
186
+ internal FileTime capture;
187
+ internal uint flags;
188
+ internal ushort suspendCount, contextSize;
189
+ internal IntPtr context;
190
+ }
191
+ [DllImport("kernel32.dll")] internal static extern IntPtr GetStdHandle(int kind);
192
+ [DllImport("kernel32.dll", SetLastError = true)]
193
+ [return: MarshalAs(UnmanagedType.Bool)]
194
+ internal static extern bool GetNamedPipeServerProcessId(IntPtr pipe, out uint pid);
195
+ [DllImport("ntdll.dll")]
196
+ internal static extern int NtQueryInformationProcess(ProcessHandle process, int kind, out BasicInformation info, int length, out int returned);
197
+ [DllImport("kernel32.dll", SetLastError = true)]
198
+ internal static extern ProcessHandle OpenThread(uint access, [MarshalAs(UnmanagedType.Bool)] bool inherit, uint tid);
199
+ [DllImport("kernel32.dll")] internal static extern void GetSystemTimePreciseAsFileTime(out long time);
200
+ [DllImport("kernel32.dll")] internal static extern IntPtr GetCurrentProcess();
201
+ [DllImport("kernel32.dll")]
202
+ internal static extern uint PssCaptureSnapshot(ProcessHandle process, uint flags, uint contextFlags, out IntPtr snapshot);
203
+ [DllImport("kernel32.dll")] internal static extern uint PssFreeSnapshot(IntPtr process, IntPtr snapshot);
204
+ [DllImport("kernel32.dll")]
205
+ internal static extern uint PssQuerySnapshot(IntPtr snapshot, int kind, out ThreadInformation information, uint length);
206
+ [DllImport("kernel32.dll")] internal static extern uint PssWalkMarkerCreate(IntPtr allocator, out IntPtr marker);
207
+ [DllImport("kernel32.dll")] internal static extern uint PssWalkMarkerFree(IntPtr marker);
208
+ [DllImport("kernel32.dll")]
209
+ internal static extern uint PssWalkSnapshot(IntPtr snapshot, int kind, IntPtr marker, out ThreadEntry entry, uint length);
210
+ }
@@ -0,0 +1,231 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.IO;
4
+ using System.Runtime.InteropServices;
5
+ using System.Security.AccessControl;
6
+ using System.Security.Cryptography;
7
+ using System.Security.Principal;
8
+ using System.Text;
9
+ using Microsoft.Win32.SafeHandles;
10
+
11
+ internal sealed class ChannelFiles : IDisposable
12
+ {
13
+ private readonly ChannelNative native = new ChannelNative();
14
+ private readonly List<IDisposable> held = new List<IDisposable>();
15
+ private readonly string actor;
16
+ private readonly HashSet<string> trusted;
17
+ private readonly Action checkpoint;
18
+
19
+ internal ChannelFiles(string actorSid, Action check = null)
20
+ {
21
+ checkpoint = check;
22
+ actor = actorSid;
23
+ trusted = new HashSet<string>(StringComparer.Ordinal)
24
+ {
25
+ actor, "S-1-5-18", "S-1-5-32-544",
26
+ "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464"
27
+ };
28
+ }
29
+
30
+ internal string PathValue(string path)
31
+ {
32
+ bool absolute = path != null &&
33
+ path.Length > 3 &&
34
+ char.IsLetter(path[0]) &&
35
+ path[1] == ':' &&
36
+ (path[2] == '\\' || path[2] == '/');
37
+ if (!absolute ||
38
+ path.IndexOf(':', 2) >= 0 ||
39
+ path.IndexOf('\0') >= 0) throw new InvalidOperationException("LOCAL_ABSOLUTE_PATH_REQUIRED");
40
+ return Path.GetFullPath(path);
41
+ }
42
+
43
+ internal string Digest(byte[] bytes)
44
+ {
45
+ using (var hash = SHA256.Create())
46
+ return BitConverter.ToString(hash.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant();
47
+ }
48
+
49
+ internal string Hash(Stream stream)
50
+ {
51
+ stream.Position = 0;
52
+ using (var hash = SHA256.Create())
53
+ {
54
+ var digest = BitConverter.ToString(hash.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
55
+ stream.Position = 0;
56
+ return digest;
57
+ }
58
+ }
59
+
60
+ internal void Security(string path, bool directory, bool fullWrite)
61
+ {
62
+ FileSystemSecurity descriptor = directory
63
+ ? (FileSystemSecurity)Directory.GetAccessControl(path, AccessControlSections.Owner | AccessControlSections.Access)
64
+ : File.GetAccessControl(path, AccessControlSections.Owner | AccessControlSections.Access);
65
+ var owner = descriptor.GetOwner(typeof(SecurityIdentifier)).Value;
66
+ if (!trusted.Contains(owner)) throw new InvalidOperationException("UNTRUSTED_PATH_OWNER");
67
+ var binary = new RawSecurityDescriptor(descriptor.GetSecurityDescriptorBinaryForm(), 0);
68
+ if (binary.DiscretionaryAcl == null) throw new InvalidOperationException("NULL_PATH_DACL");
69
+ int dangerous = 0x10000 | 0x40000 | 0x80000 | 0x40;
70
+ if (fullWrite) dangerous |= 2 | 4 | 0x10 | 0x100;
71
+ foreach (GenericAce ace in binary.DiscretionaryAcl)
72
+ {
73
+ if ((ace.AceFlags & AceFlags.InheritOnly) != 0) continue;
74
+ var common = ace as CommonAce;
75
+ if (common == null ||
76
+ common.IsCallback) throw new InvalidOperationException("UNSUPPORTED_PATH_ACE");
77
+ if (common.AceQualifier != AceQualifier.AccessAllowed) continue;
78
+ int mask = common.AccessMask;
79
+ bool writable = (mask & dangerous) != 0 ||
80
+ (mask & unchecked((int)0x50000000)) != 0;
81
+ if (writable &&
82
+ !trusted.Contains(common.SecurityIdentifier.Value))
83
+ throw new InvalidOperationException("UNTRUSTED_PATH_WRITER");
84
+ }
85
+ }
86
+
87
+ internal string Owner(string path)
88
+ {
89
+ return File.GetAccessControl(path, AccessControlSections.Owner).GetOwner(typeof(SecurityIdentifier)).Value;
90
+ }
91
+
92
+ internal void Chain(string path, bool protectWriters)
93
+ {
94
+ var full = PathValue(path);
95
+ var paths = new List<string>();
96
+ var current = full;
97
+ while (current != null)
98
+ {
99
+ paths.Add(current);
100
+ current = Path.GetDirectoryName(current);
101
+ }
102
+ paths.Reverse();
103
+ foreach (var item in paths)
104
+ {
105
+ HoldPath(item, protectWriters, item == full);
106
+ }
107
+ }
108
+
109
+ internal bool HoldPath(string path, bool protectWriters, bool fullWrite)
110
+ {
111
+ if (checkpoint != null) checkpoint();
112
+ var security = new ChannelApi.SecurityAttributes { length = Marshal.SizeOf(typeof(ChannelApi.SecurityAttributes)) };
113
+ var handle = ChannelApi.CreateFileW(path, 0x20080, 3, ref security, 3, 0x02200000, IntPtr.Zero);
114
+ if (handle.IsInvalid)
115
+ {
116
+ handle.Dispose();
117
+ throw new InvalidOperationException("PATH_HOLD_DENIED");
118
+ }
119
+ held.Add(handle);
120
+ ChannelApi.FileInformation information;
121
+ native.Require(ChannelApi.GetFileInformationByHandle(handle, out information));
122
+ if ((information.attributes & 0x400) != 0) throw new InvalidOperationException("REPARSE_PATH_REFUSED");
123
+ bool directory = (information.attributes & 0x10) != 0;
124
+ if (protectWriters) Security(path, directory, fullWrite || !directory);
125
+ if (checkpoint != null) checkpoint();
126
+ return directory;
127
+ }
128
+
129
+ internal FileStream HoldFile(string path, bool protectWriters)
130
+ {
131
+ path = PathValue(path);
132
+ Chain(path, protectWriters);
133
+ var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
134
+ held.Add(stream);
135
+ return stream;
136
+ }
137
+
138
+ internal string Canonical(FileStream stream)
139
+ {
140
+ var text = new StringBuilder(32768);
141
+ uint length = ChannelApi.GetFinalPathNameByHandleW(stream.SafeFileHandle, text, text.Capacity, 0);
142
+ if (length < 1 ||
143
+ length >= text.Capacity) throw new InvalidOperationException("CANONICAL_PATH_UNAVAILABLE");
144
+ var path = text.ToString();
145
+ if (!path.StartsWith(@"\\?\", StringComparison.Ordinal)) throw new InvalidOperationException("CANONICAL_PATH_UNSUPPORTED");
146
+ return PathValue(path.Substring(4));
147
+ }
148
+
149
+ internal T WithDescriptor<T>(string targetSid, bool directory, Func<ChannelApi.SecurityAttributes, T> action, bool pipe = false)
150
+ {
151
+ var target = new SecurityIdentifier(targetSid).Value;
152
+ var rights = pipe ? "0x12019f" : directory ? "0x1200a9" : "0x120089";
153
+ var inherit = directory ? "OICI" : "";
154
+ var sddl = "O:" + actor + "G:" + actor + "D:P(A;" + inherit + ";FA;;;" + actor +
155
+ ")(A;" + inherit + ";FA;;;SY)";
156
+ if (target != actor) sddl += "(A;" + inherit + ";" + rights + ";;;" + target + ")";
157
+ var descriptor = new RawSecurityDescriptor(sddl);
158
+ var bytes = new byte[descriptor.BinaryLength];
159
+ descriptor.GetBinaryForm(bytes, 0);
160
+ var memory = Marshal.AllocHGlobal(bytes.Length);
161
+ try
162
+ {
163
+ Marshal.Copy(bytes, 0, memory, bytes.Length);
164
+ return action(new ChannelApi.SecurityAttributes
165
+ {
166
+ length = Marshal.SizeOf(typeof(ChannelApi.SecurityAttributes)),
167
+ descriptor = memory, inherit = false
168
+ });
169
+ }
170
+ finally { Marshal.FreeHGlobal(memory); }
171
+ }
172
+
173
+ internal void CreateDirectory(string path, string targetSid)
174
+ {
175
+ WithDescriptor(targetSid, true, security =>
176
+ {
177
+ native.Require(ChannelApi.CreateDirectoryW(path, ref security));
178
+ return true;
179
+ });
180
+ }
181
+
182
+ internal FileStream CreateManifest(string path, string targetSid, byte[] bytes)
183
+ {
184
+ var handle = WithDescriptor(targetSid, false, security =>
185
+ ChannelApi.CreateFileW(path, 0xC0000000, 1, ref security, 1, 0x80, IntPtr.Zero));
186
+ if (handle.IsInvalid)
187
+ {
188
+ handle.Dispose();
189
+ throw new InvalidOperationException("MANIFEST_CREATE_DENIED");
190
+ }
191
+ using (var stream = new FileStream(handle, FileAccess.ReadWrite))
192
+ {
193
+ stream.Write(bytes, 0, bytes.Length);
194
+ stream.Flush(true);
195
+ }
196
+ return HoldFile(path, true);
197
+ }
198
+
199
+ internal Dictionary<string, object> InspectRoot(string root)
200
+ {
201
+ root = PathValue(root);
202
+ Chain(root, true);
203
+ int directories = 0, files = 0;
204
+ var pending = new Queue<string>();
205
+ pending.Enqueue(root);
206
+ while (pending.Count != 0)
207
+ {
208
+ if (checkpoint != null) checkpoint();
209
+ var directory = pending.Dequeue();
210
+ if (++directories > 2048) throw new InvalidOperationException("CODE_DIRECTORY_BOUND");
211
+ foreach (var path in Directory.EnumerateFileSystemEntries(directory))
212
+ {
213
+ bool isDirectory = HoldPath(path, true, true);
214
+ if (isDirectory) pending.Enqueue(path);
215
+ else if (++files > 16384) throw new InvalidOperationException("CODE_FILE_BOUND");
216
+ }
217
+ if (checkpoint != null) checkpoint();
218
+ }
219
+ return new Dictionary<string, object>
220
+ {
221
+ { "root", root }, { "directories", directories }, { "files", files },
222
+ { "protection", "observed-owner-and-allow-writers" }, { "loadedCodeAttestation", false }
223
+ };
224
+ }
225
+
226
+ public void Dispose()
227
+ {
228
+ for (int index = held.Count - 1; index >= 0; index--) held[index].Dispose();
229
+ held.Clear();
230
+ }
231
+ }