windows-development-sandbox 1.13.2 → 1.15.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/config.example.json +2 -1
- package/dist/app/features/defs.d.ts +1 -1
- package/dist/app/features/projects/projects.def.d.ts +1 -1
- package/dist/app/features/projects/projects.run.d.ts +1 -1
- package/dist/app/features/projects/projects.run.js +10 -2
- package/dist/app/features/sandbox/sandbox.def.js +3 -1
- package/dist/app/features/settings/categories.def.js +1 -0
- package/dist/app/features/storage/storage.run.js +1 -1
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-VENWSGX2.js → chunk-D7CM63BS.js} +2303 -1462
- package/dist/bin/{chunk-K4SU3MHJ.js → chunk-HTQT5SBY.js} +1 -1
- package/dist/bin/{chunk-IL7BXJF2.js → chunk-RQ4BWBYX.js} +1 -1
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +1 -1
- package/dist/cli/commands/projects/here.js +2 -0
- package/dist/cli/commands/repair/checks/contained.js +1 -1
- package/dist/cli/services/health-service.js +15 -3
- package/dist/cli/services/projects-service.d.ts +8 -3
- package/dist/cli/services/projects-service.js +21 -4
- package/dist/core/config/defaults.js +1 -0
- package/dist/core/schema/config.d.ts +4 -0
- package/dist/core/schema/projects.d.ts +4 -0
- package/dist/core/schema/projects.js +3 -0
- package/dist/guest/boot/boot.js +8 -2
- package/dist/guest/daemon/daemon.js +3 -0
- package/dist/guest/daemon/live-folders.d.ts +48 -0
- package/dist/guest/daemon/live-folders.js +245 -0
- package/dist/guest/daemon/live.d.ts +4 -0
- package/dist/guest/daemon/live.js +57 -0
- package/dist/guest/daemon/sessions.d.ts +7 -0
- package/dist/guest/daemon/sessions.js +23 -2
- package/dist/guest/entries/daemon.js +5 -2
- package/dist/guest/runtime/csc.d.ts +1 -0
- package/dist/guest/runtime/csc.js +1 -0
- package/dist/guest/runtime/secrets.d.ts +5 -2
- package/dist/guest/runtime/secrets.js +12 -5
- package/dist/guest/sessions/pty-host.d.ts +10 -0
- package/dist/guest/sessions/pty-host.js +48 -3
- package/dist/host/channel/channel.js +1 -1
- package/dist/host/engines/hcs/live-folders.d.ts +117 -0
- package/dist/host/engines/hcs/live-folders.js +347 -0
- package/dist/host/engines/index.d.ts +1 -0
- package/dist/host/engines/index.js +1 -0
- package/dist/host/install/install-record.d.ts +1 -0
- package/dist/host/install/install-record.js +2 -0
- package/dist/host/launch/generate.js +8 -1
- package/dist/host/projects/join.d.ts +4 -0
- package/dist/host/projects/join.js +11 -3
- package/dist/host/projects/mapper.d.ts +34 -5
- package/dist/host/projects/mapper.js +88 -20
- package/dist/host/projects/registry.d.ts +2 -0
- package/dist/host/projects/registry.js +1 -1
- package/dist/host/sandbox/contained-deps.d.ts +2 -1
- package/dist/host/sandbox/contained-deps.js +6 -4
- package/dist/host/secrets/launch-secrets.js +2 -1
- package/dist/host/sharing/lifecycle.js +9 -1
- package/dist/host/storage/contained/contained-storage.js +13 -0
- package/dist/host/storage/contained/disk-port.d.ts +5 -0
- package/dist/host/storage/contained/helper-port.d.ts +72 -0
- package/dist/host/storage/contained/helper-port.js +162 -0
- package/dist/host/storage/contained/index.d.ts +1 -0
- package/dist/host/storage/contained/index.js +1 -0
- package/dist/host/web/bridge.d.ts +5 -0
- package/dist/host/web/bridge.js +11 -1
- package/dist/host/web/server.d.ts +2 -0
- package/dist/host/web/server.js +11 -1
- package/dist/runtime/agent.js +23 -0
- package/dist/runtime/daemon.js +523 -135
- package/dist/runtime/db.js +23 -0
- package/dist/runtime/main.js +221 -15
- package/dist/runtime/netproxy.js +23 -0
- package/dist/runtime/view.js +209 -12
- package/dist/selftest/channel.test.js +72 -1
- package/dist/selftest/contained.test.js +1 -37
- package/dist/selftest/hcs.test.js +5 -2
- package/dist/selftest/index.js +4 -0
- package/dist/selftest/install.test.js +11 -0
- package/dist/selftest/layout.test.js +1 -0
- package/dist/selftest/live-daemon.test.js +51 -1
- package/dist/selftest/live-folders.test.d.ts +2 -0
- package/dist/selftest/live-folders.test.js +588 -0
- package/dist/selftest/live-terminal.test.js +112 -4
- package/dist/selftest/projects.test.js +7 -7
- package/dist/selftest/storage-helper.test.d.ts +2 -0
- package/dist/selftest/storage-helper.test.js +493 -0
- package/dist/selftest/support/engine-golden.js +1 -0
- package/dist/selftest/support/fixtures.d.ts +1 -0
- package/dist/selftest/support/live.js +2 -0
- package/dist/selftest/support/vhdx.d.ts +14 -0
- package/dist/selftest/support/vhdx.js +72 -0
- package/dist/selftest/web.test.js +14 -2
- package/dist/shared/channel/index.d.ts +1 -0
- package/dist/shared/channel/index.js +1 -0
- package/dist/shared/channel/live.d.ts +53 -0
- package/dist/shared/channel/live.js +92 -0
- package/dist/shared/channel/run-dir.d.ts +11 -0
- package/dist/shared/channel/run-dir.js +20 -0
- package/dist/shared/channel/secrets.d.ts +1 -0
- package/dist/shared/channel/secrets.js +5 -0
- package/dist/shared/paths/projects.d.ts +6 -1
- package/dist/shared/paths/projects.js +21 -1
- package/dist/shared/pty/index.d.ts +3 -1
- package/dist/shared/pty/index.js +3 -1
- package/dist/shared/pty/key-gate.d.ts +24 -0
- package/dist/shared/pty/key-gate.js +45 -0
- package/dist/shared/pty/mouse-modes.d.ts +27 -0
- package/dist/shared/pty/mouse-modes.js +155 -0
- package/dist/shared/pty/terminal.d.ts +0 -5
- package/dist/shared/pty/terminal.js +51 -20
- package/dist/sync/watcher.js +10 -1
- package/dist/web/index.html +1 -1
- package/dist/web/{main-NBQ3LF4E.js → main-7DJZ2M27.js} +34 -34
- package/native/csharp/HcsHost/AssemblyInfo.cs +4 -4
- package/native/csharp/HcsHost/FolderAccess.cs +361 -0
- package/native/csharp/HcsHost/Hcs.cs +31 -3
- package/native/csharp/HcsHost/Program.cs +54 -6
- package/native/csharp/HcsHost/Share.cs +64 -11
- package/native/csharp/HcsHost/WsbGuard.cs +130 -0
- package/native/csharp/HcsHost/WsbShare.cs +295 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +15 -0
- package/native/csharp/StorageHelper/Client.cs +297 -0
- package/native/csharp/StorageHelper/Log.cs +177 -0
- package/native/csharp/StorageHelper/MountRecords.cs +160 -0
- package/native/csharp/StorageHelper/Operations.cs +596 -0
- package/native/csharp/StorageHelper/Pins.cs +270 -0
- package/native/csharp/StorageHelper/Privileges.cs +78 -0
- package/native/csharp/StorageHelper/Program.cs +349 -0
- package/native/csharp/StorageHelper/Protocol.cs +107 -0
- package/native/csharp/StorageHelper/RateLimiter.cs +97 -0
- package/native/csharp/StorageHelper/Rules.cs +156 -0
- package/native/csharp/StorageHelper/Server.cs +656 -0
- package/native/csharp/StorageHelper/Settings.cs +297 -0
- package/native/csharp/StorageHelper/StorageService.cs +53 -0
- package/native/csharp/StorageHelper/UserSettings.cs +206 -0
- package/native/csharp/StorageHelper/Vhdx.cs +230 -0
- package/native/csharp/StorageHelper/VirtualDisk.cs +241 -0
- package/native/csharp/StorageHelper/Volumes.cs +379 -0
- package/native/csharp/StorageHelper/Watchdog.cs +128 -0
- package/native/csharp/StorageHelper/Win32.cs +250 -0
- package/native/scripts/contained-common.ps1 +4 -0
- package/native/scripts/hcs-live-share.ps1 +191 -0
- package/native/scripts/set-storage-helper.ps1 +203 -0
- package/package.json +1 -1
- package/sandbox/csharp/BindMount.cs +120 -0
- package/sandbox/csharp/PtyHost.cs +236 -1
- package/schemas/config.schema.json +8 -0
|
@@ -4,13 +4,13 @@ using System.Reflection;
|
|
|
4
4
|
using System.Runtime.InteropServices;
|
|
5
5
|
|
|
6
6
|
[assembly: AssemblyTitle("Windows Development Sandbox HCS helper")]
|
|
7
|
-
[assembly: AssemblyDescription("Helper of windows-development-sandbox (open source, MIT), used only by the beta HCS engine: asks the Windows Host Compute Service to list, create, start and stop its sandbox virtual machine, gives that machine access to its own disk file
|
|
7
|
+
[assembly: AssemblyDescription("Helper of windows-development-sandbox (open source, MIT), used only by the beta HCS engine: asks the Windows Host Compute Service to list, create, start and stop its sandbox virtual machine, gives that machine access to its own disk file, creates a blank virtual disk file and, for the opt-in live folders alpha, adds or removes a folder share on the running Windows Sandbox after an administrator prompt. It never mounts a disk on this PC. Source: https://github.com/JoshDiDuca/windows-development-sandbox")]
|
|
8
8
|
[assembly: AssemblyCompany("windows-development-sandbox (open source)")]
|
|
9
9
|
[assembly: AssemblyProduct("Windows Development Sandbox")]
|
|
10
10
|
[assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
|
|
11
|
-
[assembly: AssemblyVersion("1.
|
|
12
|
-
[assembly: AssemblyFileVersion("1.
|
|
13
|
-
[assembly: AssemblyInformationalVersion("1.
|
|
11
|
+
[assembly: AssemblyVersion("1.15.0.0")]
|
|
12
|
+
[assembly: AssemblyFileVersion("1.15.0.0")]
|
|
13
|
+
[assembly: AssemblyInformationalVersion("1.15.0")]
|
|
14
14
|
[assembly: ComVisible(false)]
|
|
15
15
|
// computecore.dll and computestorage.dll are not KnownDLLs: load them from System32 only, never from the exe's folder.
|
|
16
16
|
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// What wsb-share may expose, checked here in the elevated helper without relying on the unelevated caller: the plain
|
|
2
|
+
// long path of an existing folder on a fixed local drive (ShareRequest.FolderProblem), not on a subst drive, opened
|
|
3
|
+
// without following links and shared in the form Windows itself reports for that handle; never Windows, Program Files,
|
|
4
|
+
// ProgramData, the profile root or another user's profile, nor this tool's storage or data folder (or a folder that
|
|
5
|
+
// holds them); and only a folder the unelevated user can read (and write, for a writable share) themselves.
|
|
6
|
+
using System;
|
|
7
|
+
using System.ComponentModel;
|
|
8
|
+
using System.IO;
|
|
9
|
+
using System.Runtime.InteropServices;
|
|
10
|
+
using System.Text;
|
|
11
|
+
|
|
12
|
+
namespace AgentSandbox.HcsHost
|
|
13
|
+
{
|
|
14
|
+
public static class FolderAccess
|
|
15
|
+
{
|
|
16
|
+
private const uint FileReadAttributes = 0x80;
|
|
17
|
+
private const uint FileShareAll = 0x7;
|
|
18
|
+
/// <summary>Read and write, never delete: while the handle is held the folder cannot be renamed or replaced.</summary>
|
|
19
|
+
private const uint FileShareReadWrite = 0x3;
|
|
20
|
+
private const uint OpenExisting = 3;
|
|
21
|
+
private const uint FlagBackupSemantics = 0x02000000;
|
|
22
|
+
private const uint FlagOpenReparsePoint = 0x00200000;
|
|
23
|
+
private const uint AttributeDirectory = 0x10;
|
|
24
|
+
private const uint AttributeReparsePoint = 0x400;
|
|
25
|
+
private const uint TokenQuery = 0x0008;
|
|
26
|
+
private const uint TokenDuplicate = 0x0002;
|
|
27
|
+
private const int TokenLinkedTokenClass = 19;
|
|
28
|
+
private const int SecurityIdentification = 1;
|
|
29
|
+
private const uint OwnerGroupDacl = 0x1 | 0x2 | 0x4;
|
|
30
|
+
private const uint FileGenericRead = 0x120089;
|
|
31
|
+
private const uint FileGenericWrite = 0x120116;
|
|
32
|
+
private const uint FileGenericExecute = 0x1200A0;
|
|
33
|
+
private const uint FileAllAccess = 0x1F01FF;
|
|
34
|
+
private const uint FileTraverse = 0x20;
|
|
35
|
+
private static readonly IntPtr InvalidHandle = new IntPtr(-1);
|
|
36
|
+
|
|
37
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
38
|
+
private struct GenericMapping
|
|
39
|
+
{
|
|
40
|
+
public uint GenericRead;
|
|
41
|
+
public uint GenericWrite;
|
|
42
|
+
public uint GenericExecute;
|
|
43
|
+
public uint GenericAll;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
47
|
+
private struct ByHandleFileInformation
|
|
48
|
+
{
|
|
49
|
+
public uint FileAttributes;
|
|
50
|
+
public long CreationTime;
|
|
51
|
+
public long LastAccessTime;
|
|
52
|
+
public long LastWriteTime;
|
|
53
|
+
public uint VolumeSerialNumber;
|
|
54
|
+
public uint FileSizeHigh;
|
|
55
|
+
public uint FileSizeLow;
|
|
56
|
+
public uint NumberOfLinks;
|
|
57
|
+
public uint FileIndexHigh;
|
|
58
|
+
public uint FileIndexLow;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private static class NativeMethods
|
|
62
|
+
{
|
|
63
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
64
|
+
public static extern IntPtr CreateFileW(string name, uint access, uint share, IntPtr security, uint disposition, uint flags, IntPtr template);
|
|
65
|
+
|
|
66
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
67
|
+
public static extern bool GetFileInformationByHandle(IntPtr file, out ByHandleFileInformation info);
|
|
68
|
+
|
|
69
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
70
|
+
public static extern uint GetFinalPathNameByHandleW(IntPtr file, StringBuilder path, uint length, uint flags);
|
|
71
|
+
|
|
72
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
73
|
+
public static extern uint QueryDosDeviceW(string device, StringBuilder target, uint length);
|
|
74
|
+
|
|
75
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
76
|
+
public static extern bool CloseHandle(IntPtr handle);
|
|
77
|
+
|
|
78
|
+
[DllImport("kernel32.dll")]
|
|
79
|
+
public static extern IntPtr GetCurrentProcess();
|
|
80
|
+
|
|
81
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
82
|
+
public static extern bool OpenProcessToken(IntPtr process, uint access, out IntPtr token);
|
|
83
|
+
|
|
84
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
85
|
+
public static extern bool GetTokenInformation(IntPtr token, int infoClass, out IntPtr linked, int length, out int returned);
|
|
86
|
+
|
|
87
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
88
|
+
public static extern bool DuplicateToken(IntPtr token, int level, out IntPtr duplicate);
|
|
89
|
+
|
|
90
|
+
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
91
|
+
public static extern bool GetFileSecurityW(string name, uint info, byte[] descriptor, uint length, out uint needed);
|
|
92
|
+
|
|
93
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
94
|
+
public static extern bool AccessCheck(byte[] descriptor, IntPtr token, uint desired, ref GenericMapping mapping, byte[] privileges, ref uint privilegesLength, out uint granted, out bool status);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// <summary>
|
|
98
|
+
/// Null (with the path to share in `finalPath` and the folder still open in `held`, without delete sharing, so it
|
|
99
|
+
/// cannot be renamed or swapped for a link until the caller closes it after the share) when the folder may be
|
|
100
|
+
/// shared with Windows Sandbox; otherwise why not.
|
|
101
|
+
/// </summary>
|
|
102
|
+
public static string Problem(string path, bool readOnly, string storageRoot, string dataPath, out string finalPath, out IntPtr held)
|
|
103
|
+
{
|
|
104
|
+
finalPath = null;
|
|
105
|
+
held = IntPtr.Zero;
|
|
106
|
+
if (string.IsNullOrEmpty(storageRoot) || string.IsNullOrEmpty(dataPath))
|
|
107
|
+
{
|
|
108
|
+
return "the request must name this tool's storage and data folders (they are never shared)";
|
|
109
|
+
}
|
|
110
|
+
string problem = ShareRequest.FolderProblem(path);
|
|
111
|
+
if (problem != null)
|
|
112
|
+
{
|
|
113
|
+
return problem;
|
|
114
|
+
}
|
|
115
|
+
string folder = path.TrimEnd('\\');
|
|
116
|
+
if (IsSubst(folder.Substring(0, 2)))
|
|
117
|
+
{
|
|
118
|
+
return "the folder is on a subst drive: give its real path";
|
|
119
|
+
}
|
|
120
|
+
string real;
|
|
121
|
+
problem = Open(folder, out real, out held);
|
|
122
|
+
if (problem != null)
|
|
123
|
+
{
|
|
124
|
+
return problem;
|
|
125
|
+
}
|
|
126
|
+
problem = CheckOpened(folder, real, readOnly, storageRoot, dataPath);
|
|
127
|
+
if (problem != null)
|
|
128
|
+
{
|
|
129
|
+
Close(held);
|
|
130
|
+
held = IntPtr.Zero;
|
|
131
|
+
return problem;
|
|
132
|
+
}
|
|
133
|
+
finalPath = real;
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static string CheckOpened(string folder, string real, bool readOnly, string storageRoot, string dataPath)
|
|
138
|
+
{
|
|
139
|
+
string problem;
|
|
140
|
+
if (!string.Equals(real, folder, StringComparison.OrdinalIgnoreCase))
|
|
141
|
+
{
|
|
142
|
+
return "the folder's real path is " + real + ": give that one";
|
|
143
|
+
}
|
|
144
|
+
problem = DeniedProblem(real, storageRoot, dataPath);
|
|
145
|
+
if (problem != null)
|
|
146
|
+
{
|
|
147
|
+
return problem;
|
|
148
|
+
}
|
|
149
|
+
return UserAccessProblem(real, readOnly);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public static void Close(IntPtr held)
|
|
153
|
+
{
|
|
154
|
+
if (held != IntPtr.Zero && held != InvalidHandle)
|
|
155
|
+
{
|
|
156
|
+
NativeMethods.CloseHandle(held);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// <summary>After the share: the held folder is still at `path` (null), or why not.</summary>
|
|
161
|
+
public static string MovedProblem(IntPtr held, string path)
|
|
162
|
+
{
|
|
163
|
+
string now;
|
|
164
|
+
string problem = FinalPath(held, out now);
|
|
165
|
+
if (problem != null)
|
|
166
|
+
{
|
|
167
|
+
return problem;
|
|
168
|
+
}
|
|
169
|
+
return string.Equals(now, path, StringComparison.OrdinalIgnoreCase) ? null : "the folder moved while it was being shared";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private static bool IsSubst(string drive)
|
|
173
|
+
{
|
|
174
|
+
StringBuilder target = new StringBuilder(1024);
|
|
175
|
+
if (NativeMethods.QueryDosDeviceW(drive, target, (uint)target.Capacity) == 0)
|
|
176
|
+
{
|
|
177
|
+
return true; // unknown: treated like a subst (fail closed)
|
|
178
|
+
}
|
|
179
|
+
return target.ToString().StartsWith("\\??\\", StringComparison.Ordinal);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// <summary>
|
|
183
|
+
/// Opens the folder itself without following a link and without delete sharing (kept open in `handle`), and
|
|
184
|
+
/// says the path Windows reports for it.
|
|
185
|
+
/// </summary>
|
|
186
|
+
private static string Open(string folder, out string real, out IntPtr handle)
|
|
187
|
+
{
|
|
188
|
+
real = null;
|
|
189
|
+
handle = NativeMethods.CreateFileW(folder, FileReadAttributes, FileShareReadWrite, IntPtr.Zero, OpenExisting, FlagBackupSemantics | FlagOpenReparsePoint, IntPtr.Zero);
|
|
190
|
+
if (handle == InvalidHandle)
|
|
191
|
+
{
|
|
192
|
+
handle = IntPtr.Zero;
|
|
193
|
+
return "the folder cannot be opened (or something is renaming it): " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
|
194
|
+
}
|
|
195
|
+
bool keep = false;
|
|
196
|
+
try
|
|
197
|
+
{
|
|
198
|
+
ByHandleFileInformation info;
|
|
199
|
+
if (!NativeMethods.GetFileInformationByHandle(handle, out info))
|
|
200
|
+
{
|
|
201
|
+
return "the folder cannot be read: " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
|
202
|
+
}
|
|
203
|
+
if ((info.FileAttributes & AttributeDirectory) == 0 || (info.FileAttributes & AttributeReparsePoint) != 0)
|
|
204
|
+
{
|
|
205
|
+
return "only a plain folder (not a file or a link) can be shared";
|
|
206
|
+
}
|
|
207
|
+
string problem = FinalPath(handle, out real);
|
|
208
|
+
keep = problem == null;
|
|
209
|
+
return problem;
|
|
210
|
+
}
|
|
211
|
+
finally
|
|
212
|
+
{
|
|
213
|
+
if (!keep)
|
|
214
|
+
{
|
|
215
|
+
NativeMethods.CloseHandle(handle);
|
|
216
|
+
handle = IntPtr.Zero;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private static string FinalPath(IntPtr handle, out string real)
|
|
222
|
+
{
|
|
223
|
+
real = null;
|
|
224
|
+
StringBuilder sb = new StringBuilder(1024);
|
|
225
|
+
uint length = NativeMethods.GetFinalPathNameByHandleW(handle, sb, (uint)sb.Capacity, 0);
|
|
226
|
+
if (length == 0 || length >= sb.Capacity)
|
|
227
|
+
{
|
|
228
|
+
return "the folder's real path cannot be read";
|
|
229
|
+
}
|
|
230
|
+
string text = sb.ToString();
|
|
231
|
+
{
|
|
232
|
+
if (!text.StartsWith("\\\\?\\", StringComparison.Ordinal) || text.StartsWith("\\\\?\\UNC\\", StringComparison.OrdinalIgnoreCase))
|
|
233
|
+
{
|
|
234
|
+
return "only folders on a local drive can be shared";
|
|
235
|
+
}
|
|
236
|
+
real = text.Substring(4).TrimEnd('\\');
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private static bool Within(string parent, string child)
|
|
242
|
+
{
|
|
243
|
+
if (string.IsNullOrEmpty(parent))
|
|
244
|
+
{
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
string p = parent.TrimEnd('\\');
|
|
248
|
+
return string.Equals(p, child, StringComparison.OrdinalIgnoreCase) ||
|
|
249
|
+
child.StartsWith(p + "\\", StringComparison.OrdinalIgnoreCase);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// <summary>Pure apart from the special folders: the folders that are never shared.</summary>
|
|
253
|
+
public static string DeniedProblem(string real, string storageRoot, string dataPath)
|
|
254
|
+
{
|
|
255
|
+
string[] system = new string[]
|
|
256
|
+
{
|
|
257
|
+
Environment.GetFolderPath(Environment.SpecialFolder.Windows),
|
|
258
|
+
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
|
|
259
|
+
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
|
260
|
+
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
|
261
|
+
};
|
|
262
|
+
foreach (string s in system)
|
|
263
|
+
{
|
|
264
|
+
if (Within(s, real) || Within(real, s))
|
|
265
|
+
{
|
|
266
|
+
return "system folders cannot be shared: " + real;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
string profile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile).TrimEnd('\\');
|
|
270
|
+
string users = Path.GetDirectoryName(profile);
|
|
271
|
+
if (Within(users, real) && !(Within(profile, real) && !string.Equals(profile, real, StringComparison.OrdinalIgnoreCase)))
|
|
272
|
+
{
|
|
273
|
+
return "only a folder inside your own profile can be shared from the users folder (never the profile itself or another user's)";
|
|
274
|
+
}
|
|
275
|
+
foreach (string own in new string[] { storageRoot, dataPath })
|
|
276
|
+
{
|
|
277
|
+
if (!string.IsNullOrEmpty(own) && (Within(own, real) || Within(real, own)))
|
|
278
|
+
{
|
|
279
|
+
return "this tool's own storage or data folder cannot be shared: " + real;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// <summary>The token of the user who asked: the unelevated half of an elevated token, else this one.</summary>
|
|
286
|
+
private static IntPtr UserToken()
|
|
287
|
+
{
|
|
288
|
+
IntPtr process;
|
|
289
|
+
if (!NativeMethods.OpenProcessToken(NativeMethods.GetCurrentProcess(), TokenQuery | TokenDuplicate, out process))
|
|
290
|
+
{
|
|
291
|
+
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
292
|
+
}
|
|
293
|
+
try
|
|
294
|
+
{
|
|
295
|
+
IntPtr linked;
|
|
296
|
+
int returned;
|
|
297
|
+
if (NativeMethods.GetTokenInformation(process, TokenLinkedTokenClass, out linked, IntPtr.Size, out returned) && linked != IntPtr.Zero)
|
|
298
|
+
{
|
|
299
|
+
return linked;
|
|
300
|
+
}
|
|
301
|
+
IntPtr duplicate;
|
|
302
|
+
if (!NativeMethods.DuplicateToken(process, SecurityIdentification, out duplicate))
|
|
303
|
+
{
|
|
304
|
+
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
305
|
+
}
|
|
306
|
+
return duplicate;
|
|
307
|
+
}
|
|
308
|
+
finally
|
|
309
|
+
{
|
|
310
|
+
NativeMethods.CloseHandle(process);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// <summary>Null when the unelevated user may list (and, for a writable share, change) the folder themselves.</summary>
|
|
315
|
+
public static string UserAccessProblem(string folder, bool readOnly)
|
|
316
|
+
{
|
|
317
|
+
uint needed;
|
|
318
|
+
NativeMethods.GetFileSecurityW(folder, OwnerGroupDacl, null, 0, out needed);
|
|
319
|
+
if (needed == 0 || needed > 1024 * 1024)
|
|
320
|
+
{
|
|
321
|
+
return "the folder's permissions cannot be read";
|
|
322
|
+
}
|
|
323
|
+
byte[] descriptor = new byte[needed];
|
|
324
|
+
if (!NativeMethods.GetFileSecurityW(folder, OwnerGroupDacl, descriptor, needed, out needed))
|
|
325
|
+
{
|
|
326
|
+
return "the folder's permissions cannot be read: " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
|
327
|
+
}
|
|
328
|
+
IntPtr token = IntPtr.Zero;
|
|
329
|
+
try
|
|
330
|
+
{
|
|
331
|
+
token = UserToken();
|
|
332
|
+
GenericMapping mapping = new GenericMapping();
|
|
333
|
+
mapping.GenericRead = FileGenericRead;
|
|
334
|
+
mapping.GenericWrite = FileGenericWrite;
|
|
335
|
+
mapping.GenericExecute = FileGenericExecute;
|
|
336
|
+
mapping.GenericAll = FileAllAccess;
|
|
337
|
+
uint desired = FileGenericRead | FileTraverse | (readOnly ? 0u : FileGenericWrite);
|
|
338
|
+
byte[] privileges = new byte[256];
|
|
339
|
+
uint privilegesLength = (uint)privileges.Length;
|
|
340
|
+
uint granted;
|
|
341
|
+
bool allowed;
|
|
342
|
+
if (!NativeMethods.AccessCheck(descriptor, token, desired, ref mapping, privileges, ref privilegesLength, out granted, out allowed))
|
|
343
|
+
{
|
|
344
|
+
return "the folder's permissions cannot be checked: " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
|
345
|
+
}
|
|
346
|
+
if (!allowed)
|
|
347
|
+
{
|
|
348
|
+
return readOnly ? "you cannot read this folder yourself, so it cannot be shared" : "you cannot change this folder yourself, so it cannot be shared writable";
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
finally
|
|
353
|
+
{
|
|
354
|
+
if (token != IntPtr.Zero)
|
|
355
|
+
{
|
|
356
|
+
NativeMethods.CloseHandle(token);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -63,7 +63,9 @@ namespace AgentSandbox.HcsHost
|
|
|
63
63
|
|
|
64
64
|
public static class Hcs
|
|
65
65
|
{
|
|
66
|
+
/// <summary>HcsOpenComputeSystem documents no narrower right than GENERIC_ALL for modify.</summary>
|
|
66
67
|
private const uint GenericAll = 0x10000000;
|
|
68
|
+
private const uint LoadLibrarySearchSystem32 = 0x00000800;
|
|
67
69
|
private const uint QueryMs = 15000;
|
|
68
70
|
private const uint ChangeMs = 180000;
|
|
69
71
|
|
|
@@ -116,7 +118,7 @@ namespace AgentSandbox.HcsHost
|
|
|
116
118
|
public static extern IntPtr LocalFree(IntPtr mem);
|
|
117
119
|
|
|
118
120
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
119
|
-
public static extern IntPtr
|
|
121
|
+
public static extern IntPtr LoadLibraryEx(string fileName, IntPtr file, uint flags);
|
|
120
122
|
|
|
121
123
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
122
124
|
public static extern bool FreeLibrary(IntPtr module);
|
|
@@ -139,10 +141,10 @@ namespace AgentSandbox.HcsHost
|
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
/// <summary>Whether a DLL (computecore.dll) can be loaded at all on this PC.</summary>
|
|
144
|
+
/// <summary>Whether a DLL (computecore.dll) can be loaded at all on this PC (from System32 only).</summary>
|
|
143
145
|
public static bool CanLoad(string dll)
|
|
144
146
|
{
|
|
145
|
-
IntPtr module = NativeMethods.
|
|
147
|
+
IntPtr module = NativeMethods.LoadLibraryEx(dll, IntPtr.Zero, LoadLibrarySearchSystem32);
|
|
146
148
|
if (module == IntPtr.Zero)
|
|
147
149
|
{
|
|
148
150
|
return false;
|
|
@@ -254,6 +256,32 @@ namespace AgentSandbox.HcsHost
|
|
|
254
256
|
return WithSystem(id, ChangeMs, delegate(IntPtr system, IntPtr op) { return NativeMethods.HcsModifyComputeSystem(system, op, modifySettingRequest, IntPtr.Zero); });
|
|
255
257
|
}
|
|
256
258
|
|
|
259
|
+
/// <summary>Opens a compute system and keeps the handle (Close it): checks and changes then use the same system.</summary>
|
|
260
|
+
public static int Open(string id, out IntPtr system)
|
|
261
|
+
{
|
|
262
|
+
return NativeMethods.HcsOpenComputeSystem(id, GenericAll, out system);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public static void Close(IntPtr system)
|
|
266
|
+
{
|
|
267
|
+
if (system != IntPtr.Zero)
|
|
268
|
+
{
|
|
269
|
+
NativeMethods.HcsCloseComputeSystem(system);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// <summary>The properties of an opened system (Id, SystemType, Owner, State, ...).</summary>
|
|
274
|
+
public static HcsReply PropertiesOf(IntPtr system)
|
|
275
|
+
{
|
|
276
|
+
return Run(delegate(IntPtr op) { return NativeMethods.HcsGetComputeSystemProperties(system, op, "{\"PropertyTypes\":[]}"); }, QueryMs);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// <summary>One ModifySettingRequest on an opened system, waiting at most `waitMs`.</summary>
|
|
280
|
+
public static HcsReply ModifyOn(IntPtr system, string modifySettingRequest, uint waitMs)
|
|
281
|
+
{
|
|
282
|
+
return Run(delegate(IntPtr op) { return NativeMethods.HcsModifyComputeSystem(system, op, modifySettingRequest, IntPtr.Zero); }, waitMs);
|
|
283
|
+
}
|
|
284
|
+
|
|
257
285
|
/// <summary>Lets the virtual machine `vmId` open the file (its disk). The file must exist.</summary>
|
|
258
286
|
public static HcsReply GrantVmAccess(string vmId, string filePath)
|
|
259
287
|
{
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// HcsHost.exe <verb> [arguments] --out <response.json>
|
|
2
2
|
// Verbs: check, enumerate, properties <id>, create <request.json>, start <id>, terminate <id>,
|
|
3
|
-
// grant-vm-access <vmId> <file>, create-vhdx <path> <maxGb>, share <request.json> (
|
|
3
|
+
// grant-vm-access <vmId> <file>, create-vhdx <path> <maxGb>, share <request.json> (our own systems),
|
|
4
|
+
// wsb-share <request.json> --sha256 <hex> (live folders on the running Windows Sandbox, WsbShare.cs),
|
|
5
|
+
// wsb-rules <enumerate.json> [id] (pure: applies WsbGuard's rules to a recorded list; asks HCS nothing).
|
|
4
6
|
// The answer is one JSON file (see Json.Response); exit code 0 = ok, 1 = failed, 2 = wrong use. A create request is
|
|
5
7
|
// {"id": "...", "document": "<the HCS document as a JSON string>"}. Never attaches a disk to this PC.
|
|
6
8
|
using System;
|
|
@@ -23,6 +25,7 @@ namespace AgentSandbox.HcsHost
|
|
|
23
25
|
public static int Main(string[] args)
|
|
24
26
|
{
|
|
25
27
|
string outFile = null;
|
|
28
|
+
string sha256 = null;
|
|
26
29
|
List<string> rest = new List<string>();
|
|
27
30
|
for (int i = 0; i < args.Length; i++)
|
|
28
31
|
{
|
|
@@ -31,6 +34,11 @@ namespace AgentSandbox.HcsHost
|
|
|
31
34
|
outFile = args[i + 1];
|
|
32
35
|
i++;
|
|
33
36
|
}
|
|
37
|
+
else if (args[i] == "--sha256" && i + 1 < args.Length)
|
|
38
|
+
{
|
|
39
|
+
sha256 = args[i + 1];
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
34
42
|
else
|
|
35
43
|
{
|
|
36
44
|
rest.Add(args[i]);
|
|
@@ -40,7 +48,7 @@ namespace AgentSandbox.HcsHost
|
|
|
40
48
|
int code;
|
|
41
49
|
try
|
|
42
50
|
{
|
|
43
|
-
code = Dispatch(rest, out response);
|
|
51
|
+
code = Dispatch(rest, sha256, out response);
|
|
44
52
|
}
|
|
45
53
|
catch (Exception ex)
|
|
46
54
|
{
|
|
@@ -73,11 +81,11 @@ namespace AgentSandbox.HcsHost
|
|
|
73
81
|
return reply.Ok ? ExitOk : ExitFailed;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
private static int Dispatch(List<string> args, out string response)
|
|
84
|
+
private static int Dispatch(List<string> args, string sha256, out string response)
|
|
77
85
|
{
|
|
78
86
|
if (args.Count == 0)
|
|
79
87
|
{
|
|
80
|
-
return Usage(out response, "HcsHost <check|enumerate|properties|create|start|terminate|grant-vm-access|create-vhdx> ... --out file");
|
|
88
|
+
return Usage(out response, "HcsHost <check|enumerate|properties|create|start|terminate|grant-vm-access|create-vhdx|share|wsb-share|wsb-rules> ... --out file");
|
|
81
89
|
}
|
|
82
90
|
string verb = args[0];
|
|
83
91
|
if (verb == "check")
|
|
@@ -113,6 +121,22 @@ namespace AgentSandbox.HcsHost
|
|
|
113
121
|
{
|
|
114
122
|
return Share(args[1], out response);
|
|
115
123
|
}
|
|
124
|
+
if (verb == "wsb-share" && args.Count == 2)
|
|
125
|
+
{
|
|
126
|
+
if (sha256 == null || !System.Text.RegularExpressions.Regex.IsMatch(sha256, "^[0-9A-Fa-f]{64}$"))
|
|
127
|
+
{
|
|
128
|
+
return Usage(out response, "wsb-share <request.json> --sha256 <its SHA-256>");
|
|
129
|
+
}
|
|
130
|
+
Dictionary<string, string> values = new Dictionary<string, string>();
|
|
131
|
+
string message;
|
|
132
|
+
bool ok = WsbShare.Run(args[1], sha256, values, out message);
|
|
133
|
+
response = Json.Response(ok, 0, message, "", values);
|
|
134
|
+
return ok ? ExitOk : (message.StartsWith("Refused: ") ? ExitUsage : ExitFailed);
|
|
135
|
+
}
|
|
136
|
+
if (verb == "wsb-rules" && (args.Count == 2 || args.Count == 3))
|
|
137
|
+
{
|
|
138
|
+
return WsbRules(args[1], args.Count == 3 ? args[2] : null, out response);
|
|
139
|
+
}
|
|
116
140
|
if (verb == "grant-vm-access" && args.Count == 3)
|
|
117
141
|
{
|
|
118
142
|
if (Refused(Guard.IdProblem(args[1]), out response) || Refused(Guard.DiskProblem(args[2]), out response))
|
|
@@ -175,8 +199,8 @@ namespace AgentSandbox.HcsHost
|
|
|
175
199
|
}
|
|
176
200
|
|
|
177
201
|
/// <summary>
|
|
178
|
-
///
|
|
179
|
-
///
|
|
202
|
+
/// Adds or removes a vSMB share on a running system of OURS (id prefix and Owner, as for start/terminate).
|
|
203
|
+
/// Windows Sandbox's own system is refused like any other: that is wsb-share's, with its own rules.
|
|
180
204
|
/// </summary>
|
|
181
205
|
private static int Share(string requestFile, out string response)
|
|
182
206
|
{
|
|
@@ -195,6 +219,30 @@ namespace AgentSandbox.HcsHost
|
|
|
195
219
|
return FromReply(Hcs.Modify(request.Id, values["request"]), values, out response);
|
|
196
220
|
}
|
|
197
221
|
|
|
222
|
+
/// <summary>The test hook for WsbGuard: its rules on a recorded enumerate answer. Never calls HCS.</summary>
|
|
223
|
+
private static int WsbRules(string file, string id, out string response)
|
|
224
|
+
{
|
|
225
|
+
FileInfo info = new FileInfo(file);
|
|
226
|
+
if (!info.Exists || info.Length > MaxRequestBytes)
|
|
227
|
+
{
|
|
228
|
+
return Usage(out response, "wsb-rules <enumerate.json> [id]");
|
|
229
|
+
}
|
|
230
|
+
string found;
|
|
231
|
+
string problem = WsbGuard.Candidate(File.ReadAllText(file, Encoding.UTF8), out found);
|
|
232
|
+
if (problem == null && id != null)
|
|
233
|
+
{
|
|
234
|
+
problem = WsbGuard.IdProblem(id) ?? WsbGuard.MatchProblem(found, id);
|
|
235
|
+
}
|
|
236
|
+
Dictionary<string, string> values = new Dictionary<string, string>();
|
|
237
|
+
values["id"] = found ?? "";
|
|
238
|
+
if (Refused(problem, out response))
|
|
239
|
+
{
|
|
240
|
+
return ExitUsage;
|
|
241
|
+
}
|
|
242
|
+
response = Json.Response(true, 0, "ok", "", values);
|
|
243
|
+
return ExitOk;
|
|
244
|
+
}
|
|
245
|
+
|
|
198
246
|
/// <summary>True (with the refusal as the response) when `problem` says why the request is not allowed.</summary>
|
|
199
247
|
private static bool Refused(string problem, out string response)
|
|
200
248
|
{
|