@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
package/README.md
CHANGED
|
@@ -521,6 +521,20 @@ kill-on-close Job Object with one-process, memory, and UI restrictions, and
|
|
|
521
521
|
only then resumes it. A private low-integrity temporary directory is the only
|
|
522
522
|
intended writable launch location.
|
|
523
523
|
|
|
524
|
+
`vm-launch-whpx-appcontainer` is a fourth, separately named vocabulary. It
|
|
525
|
+
retains those restricted-token and Job controls, gives an ephemeral Windows
|
|
526
|
+
AppContainer identity zero capabilities, grants that identity only read/execute
|
|
527
|
+
access to the measured launch snapshot and modify access to its private temp
|
|
528
|
+
directory, and requires file-read plus loopback-connect attack canaries to fail
|
|
529
|
+
before either WHPX launch begins. The plan cannot name a capability, path,
|
|
530
|
+
command, network mode, or fallback.
|
|
531
|
+
|
|
532
|
+
`vm-launch-appcontainer` uses the same zero-capability boundary with QEMU TCG.
|
|
533
|
+
It fixes the translation buffer at 64 MiB so the one-process 512 MiB Job limit
|
|
534
|
+
remains effective. This is the fully confined adapter proven on the validated
|
|
535
|
+
Windows host: it proves the machine VM and host file/network boundary, but sets
|
|
536
|
+
`hardwareBacked: false` and keeps hardware acceleration missing.
|
|
537
|
+
|
|
524
538
|
First measure the complete caller-owned QEMU runtime closure and both guest
|
|
525
539
|
artifacts:
|
|
526
540
|
|
|
@@ -596,6 +610,25 @@ process creation. The launcher source and local rebuild recipe are shipped in
|
|
|
596
610
|
`native/windows-sandbox`; the measured binary is shipped in
|
|
597
611
|
`native/windows-x64`.
|
|
598
612
|
|
|
613
|
+
For the AppContainer adapter, use schema
|
|
614
|
+
`holoscript.holosystem.whpx-appcontainer-vm-launch-plan.v1`, use the same
|
|
615
|
+
launcher digest with sandbox kind `windows-appcontainer-deny-v1`, and call
|
|
616
|
+
`vm-launch-whpx-appcontainer`. The launcher creates and deletes a fresh profile
|
|
617
|
+
for each launch. Its closed receipt protocol must prove the child AppContainer
|
|
618
|
+
SID, zero capabilities, low-integrity filtered token, snapshot/temp grants,
|
|
619
|
+
access-denied (`5`) for a protected caller-readable file, plus either explicit
|
|
620
|
+
WSA access-denied (`10013`) or a bounded WFP-style packet-drop timeout (`10060`)
|
|
621
|
+
with no accepted live loopback connection. Any missing or changed observation
|
|
622
|
+
blocks the receipt.
|
|
623
|
+
|
|
624
|
+
Use schema `holoscript.holosystem.appcontainer-vm-launch-plan.v1`, target
|
|
625
|
+
accelerator `tcg`, the same two sandbox digests, and command
|
|
626
|
+
`vm-launch-appcontainer` for the software-emulated confined lane. On the
|
|
627
|
+
validated Windows 11 host, the WHPX AppContainer form fails closed because
|
|
628
|
+
Windows returns `0x80070005` when zero-capability QEMU initializes WHPX. The
|
|
629
|
+
package does not merge that result with the separately proven hardware-backed
|
|
630
|
+
low-integrity WHPX receipt.
|
|
631
|
+
|
|
599
632
|
The runner creates a private snapshot and remeasures the complete QEMU closure,
|
|
600
633
|
kernel, and initramfs before and after each launch. It then generates QEMU
|
|
601
634
|
arguments that disable user configuration, default devices, networking, USB,
|
|
@@ -629,15 +662,31 @@ boundary. The sandboxed receipt also does not prove the broader hardware and
|
|
|
629
662
|
firmware properties named above. The
|
|
630
663
|
exact claims are in the [VM launch threat model](./docs/vm-launch-threat-model.md).
|
|
631
664
|
|
|
665
|
+
A verified `vm-launch-whpx-appcontainer` receipt additionally includes
|
|
666
|
+
`host-filesystem-confidentiality` and `host-network-isolation` for the tested
|
|
667
|
+
zero-capability AppContainer boundary. Those are scoped claims: they do not
|
|
668
|
+
establish QEMU provenance, host-kernel correctness, IOMMU separation, measured
|
|
669
|
+
boot, firmware authenticity, crash-dump custody, confidential memory, or
|
|
670
|
+
side-channel resistance.
|
|
671
|
+
|
|
672
|
+
A verified `vm-launch-appcontainer` receipt includes the same process,
|
|
673
|
+
filesystem, and network layers while leaving
|
|
674
|
+
`hardware-hypervisor-acceleration` missing. Receipts describe one executed
|
|
675
|
+
boundary, not the union of properties observed in different launches.
|
|
676
|
+
|
|
632
677
|
```js
|
|
633
678
|
import {
|
|
634
679
|
inspectVmExecutor,
|
|
680
|
+
inspectAppContainerVmLaunchPlan,
|
|
635
681
|
inspectVmLaunchAsset,
|
|
636
682
|
inspectVmLaunchPlan,
|
|
637
683
|
inspectWindowsVmSandboxLauncher,
|
|
684
|
+
inspectWhpxAppContainerVmLaunchPlan,
|
|
638
685
|
inspectWhpxSandboxedVmLaunchPlan,
|
|
639
686
|
inspectWhpxVmLaunchPlan,
|
|
640
687
|
runVmLaunch,
|
|
688
|
+
runAppContainerVmLaunch,
|
|
689
|
+
runWhpxAppContainerVmLaunch,
|
|
641
690
|
runWhpxSandboxedVmLaunch,
|
|
642
691
|
runWhpxVmLaunch,
|
|
643
692
|
} from '@holoscript/holosystem';
|
package/bin/holosystem.mjs
CHANGED
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
inspectVmExecutor,
|
|
15
15
|
inspectVmLaunchAsset,
|
|
16
16
|
runNativeBuild,
|
|
17
|
+
runAppContainerVmLaunch,
|
|
17
18
|
runVmLaunch,
|
|
19
|
+
runWhpxAppContainerVmLaunch,
|
|
18
20
|
runWhpxSandboxedVmLaunch,
|
|
19
21
|
runWhpxVmLaunch,
|
|
20
22
|
} from '../src/index.mjs';
|
|
@@ -35,8 +37,10 @@ Usage:
|
|
|
35
37
|
holosystem vm-executor --runtime <directory> [--json]
|
|
36
38
|
holosystem vm-asset --kind <kernel|initrd> --file <file> [--json]
|
|
37
39
|
holosystem vm-launch --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
40
|
+
holosystem vm-launch-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
38
41
|
holosystem vm-launch-whpx --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
39
42
|
holosystem vm-launch-whpx-sandboxed --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
43
|
+
holosystem vm-launch-whpx-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
40
44
|
holosystem substrate --input <file> [--output <file>] [--force] [--json]
|
|
41
45
|
holosystem --help
|
|
42
46
|
holosystem --version
|
|
@@ -703,6 +707,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
703
707
|
runVmAsset(argv.slice(1));
|
|
704
708
|
} else if (command === 'vm-launch') {
|
|
705
709
|
runVmLaunchCommand(argv.slice(1));
|
|
710
|
+
} else if (command === 'vm-launch-appcontainer') {
|
|
711
|
+
runVmLaunchCommand(argv.slice(1), {
|
|
712
|
+
launcher: runAppContainerVmLaunch,
|
|
713
|
+
commandName: 'vm-launch-appcontainer',
|
|
714
|
+
});
|
|
706
715
|
} else if (command === 'vm-launch-whpx') {
|
|
707
716
|
runVmLaunchCommand(argv.slice(1), {
|
|
708
717
|
launcher: runWhpxVmLaunch,
|
|
@@ -713,6 +722,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
713
722
|
launcher: runWhpxSandboxedVmLaunch,
|
|
714
723
|
commandName: 'vm-launch-whpx-sandboxed',
|
|
715
724
|
});
|
|
725
|
+
} else if (command === 'vm-launch-whpx-appcontainer') {
|
|
726
|
+
runVmLaunchCommand(argv.slice(1), {
|
|
727
|
+
launcher: runWhpxAppContainerVmLaunch,
|
|
728
|
+
commandName: 'vm-launch-whpx-appcontainer',
|
|
729
|
+
});
|
|
716
730
|
} else if (command === 'substrate') {
|
|
717
731
|
runSubstrate(argv.slice(1));
|
|
718
732
|
} else {
|
|
@@ -27,6 +27,25 @@ full Job UI restrictions. Its `host-process-isolation` claim is limited to
|
|
|
27
27
|
those controls. It is not an AppContainer and does not claim host filesystem
|
|
28
28
|
confidentiality or host network isolation.
|
|
29
29
|
|
|
30
|
+
`vm-launch-whpx-appcontainer` is another separate schema and receipt. It adds
|
|
31
|
+
an ephemeral zero-capability AppContainer identity to the same filtered,
|
|
32
|
+
low-integrity token and pre-resume Job boundary. The identity receives inherited
|
|
33
|
+
read/execute access to the private measured snapshot and modify access only to
|
|
34
|
+
the private temp directory. Before QEMU starts, the launcher runs an
|
|
35
|
+
AppContainer child under the same token and verifies that it cannot read a
|
|
36
|
+
caller-readable protected sentinel or connect to a live loopback listener. The
|
|
37
|
+
receipt claims filesystem confidentiality and network isolation only when the
|
|
38
|
+
token identity, zero capability count, exact denial errors, and both WHPX boots
|
|
39
|
+
all verify.
|
|
40
|
+
|
|
41
|
+
`vm-launch-appcontainer` applies the same boundary to TCG with a fixed 64 MiB
|
|
42
|
+
translation buffer. It can claim process, filesystem, and network isolation
|
|
43
|
+
after two deterministic boots, but it cannot claim hardware acceleration. On
|
|
44
|
+
the validated Windows 11 host, zero-capability QEMU receives access denied
|
|
45
|
+
(`0x80070005`) while initializing WHPX. That negative result is preserved as a
|
|
46
|
+
blocked WHPX AppContainer receipt; it is never combined with the verified TCG
|
|
47
|
+
AppContainer receipt or the separately verified low-integrity WHPX receipt.
|
|
48
|
+
|
|
30
49
|
## Protected assets and trust boundaries
|
|
31
50
|
|
|
32
51
|
The protected inputs are the declarative plan, the complete caller-owned QEMU
|
|
@@ -65,6 +84,10 @@ than implied away by a successful boot.
|
|
|
65
84
|
| Select ambient execution, weaken a sandbox control, change the launcher, or forge its protocol | Reject the distinct sandbox schema, launcher digest, or incomplete evidence | Closed sandbox vocabulary and protocol-forgery tests |
|
|
66
85
|
| Pass inherited parent handles into sandboxed QEMU | Admit only NUL input and the two bounded output pipes | Native handle-list evidence and receipt assertion |
|
|
67
86
|
| Spawn another process or escape launcher lifetime/resource bounds | Assign suspended QEMU before resume to a one-process, kill-on-close Job | Native Job evidence and adversarial receipt tests |
|
|
87
|
+
| Add an AppContainer capability, path grant, network mode, command, or fallback | Reject the distinct closed plan vocabulary before launch | AppContainer closed-vocabulary test |
|
|
88
|
+
| Forge the AppContainer SID, capability count, profile cleanup, or grant evidence | Reject the closed native protocol | AppContainer protocol-forgery test |
|
|
89
|
+
| Read a caller-readable file outside the measured snapshot | Require exact access-denied evidence from a zero-capability AppContainer | Protected-sentinel canary and receipt assertion |
|
|
90
|
+
| Connect to the host loopback listener from compromised QEMU | Require bounded WSA deny/drop evidence and no accepted connection | Loopback canary and receipt assertion |
|
|
68
91
|
| Put an operational path or guest output in the receipt | Withhold it; report only digest and byte count | Receipt disclosure assertions |
|
|
69
92
|
|
|
70
93
|
## Fixed launch policy
|
|
@@ -93,6 +116,20 @@ rejects unknown fields, non-canonical base64, an unbounded enabled-privilege
|
|
|
93
116
|
count, a missing control, or launcher stderr. Launcher and QEMU snapshots are
|
|
94
117
|
remeasured before and after both launches.
|
|
95
118
|
|
|
119
|
+
The AppContainer adapter uses the same measured launcher but a distinct plan,
|
|
120
|
+
protocol, and control vocabulary. It never accepts caller-specified capabilities
|
|
121
|
+
or ACL paths. Each native invocation creates a fresh AppContainer profile,
|
|
122
|
+
applies the fixed grants, runs both canaries, creates QEMU with the same zero
|
|
123
|
+
capability `SECURITY_CAPABILITIES`, checks the suspended child token, and deletes
|
|
124
|
+
the profile during cleanup. HoloSystem rejects a nonzero capability count,
|
|
125
|
+
unexpected bounded denial code, accepted loopback connection, incomplete cleanup, or
|
|
126
|
+
any unknown protocol field.
|
|
127
|
+
|
|
128
|
+
The TCG AppContainer plan also fixes `tcg,tb-size=64`; a caller cannot enlarge
|
|
129
|
+
the JIT translation buffer or relax the 512 MiB process limit. Its receipt keeps
|
|
130
|
+
`hardware-hypervisor-acceleration` missing even when every confinement canary
|
|
131
|
+
passes.
|
|
132
|
+
|
|
96
133
|
## Residual risk and next layer
|
|
97
134
|
|
|
98
135
|
The QEMU runtime closure includes firmware data files and DLLs, so substitution
|
|
@@ -114,8 +151,9 @@ isolation controls verify. The launcher binary is measured, but source-to-binary
|
|
|
114
151
|
reproducibility and code signing remain part of `qemu-runtime-supply-chain` and
|
|
115
152
|
host correctness trust.
|
|
116
153
|
|
|
117
|
-
The
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
The TCG AppContainer tracer implements capability-scoped filesystem and network
|
|
155
|
+
denial without hardware acceleration; the WHPX form remains blocked on the
|
|
156
|
+
validated host. It does not turn the Windows kernel into a separate trust
|
|
157
|
+
domain. The next layers are a WHPX broker or independent host boundary and
|
|
158
|
+
firmware/measured-boot evidence. IOMMU, device-assignment, crash-dump custody,
|
|
159
|
+
host correctness, and side-channel boundaries remain separate.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#define WIN32_LEAN_AND_MEAN
|
|
2
|
+
#include <winsock2.h>
|
|
3
|
+
#include <windows.h>
|
|
4
|
+
#include <stdio.h>
|
|
5
|
+
#include <wchar.h>
|
|
6
|
+
|
|
7
|
+
static int read_token_integer(HANDLE token, TOKEN_INFORMATION_CLASS information_class, int fallback)
|
|
8
|
+
{
|
|
9
|
+
DWORD required = 0;
|
|
10
|
+
BYTE *buffer;
|
|
11
|
+
int value;
|
|
12
|
+
GetTokenInformation(token, information_class, NULL, 0, &required);
|
|
13
|
+
if (required == 0) return fallback;
|
|
14
|
+
buffer = (BYTE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, required);
|
|
15
|
+
if (buffer == NULL) return fallback;
|
|
16
|
+
if (!GetTokenInformation(token, information_class, buffer, required, &required)) {
|
|
17
|
+
HeapFree(GetProcessHeap(), 0, buffer);
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
value = *(int *)buffer;
|
|
21
|
+
HeapFree(GetProcessHeap(), 0, buffer);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
int wmain(int argc, wchar_t **argv)
|
|
26
|
+
{
|
|
27
|
+
HANDLE file = INVALID_HANDLE_VALUE;
|
|
28
|
+
HANDLE token = NULL;
|
|
29
|
+
WSADATA socket_data;
|
|
30
|
+
SOCKET socket_handle = INVALID_SOCKET;
|
|
31
|
+
struct sockaddr_in address;
|
|
32
|
+
fd_set writable;
|
|
33
|
+
fd_set exceptional;
|
|
34
|
+
struct timeval wait_time;
|
|
35
|
+
u_long nonblocking = 1;
|
|
36
|
+
int port;
|
|
37
|
+
int filesystem_error = 0;
|
|
38
|
+
int network_error = 0;
|
|
39
|
+
int app_container = 0;
|
|
40
|
+
int capability_count = -1;
|
|
41
|
+
|
|
42
|
+
if (argc != 5 || wcscmp(argv[1], L"--protected-sentinel") != 0 ||
|
|
43
|
+
wcscmp(argv[3], L"--loopback-port") != 0) return 64;
|
|
44
|
+
port = _wtoi(argv[4]);
|
|
45
|
+
if (port < 1 || port > 65535) return 64;
|
|
46
|
+
|
|
47
|
+
file = CreateFileW(
|
|
48
|
+
argv[2], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
|
49
|
+
FILE_ATTRIBUTE_NORMAL, NULL
|
|
50
|
+
);
|
|
51
|
+
if (file == INVALID_HANDLE_VALUE) filesystem_error = (int)GetLastError();
|
|
52
|
+
else CloseHandle(file);
|
|
53
|
+
|
|
54
|
+
if (WSAStartup(MAKEWORD(2, 2), &socket_data) != 0) {
|
|
55
|
+
network_error = WSAGetLastError();
|
|
56
|
+
} else {
|
|
57
|
+
socket_handle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
58
|
+
if (socket_handle == INVALID_SOCKET) {
|
|
59
|
+
network_error = WSAGetLastError();
|
|
60
|
+
} else {
|
|
61
|
+
ioctlsocket(socket_handle, FIONBIO, &nonblocking);
|
|
62
|
+
ZeroMemory(&address, sizeof(address));
|
|
63
|
+
address.sin_family = AF_INET;
|
|
64
|
+
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
65
|
+
address.sin_port = htons((u_short)port);
|
|
66
|
+
if (connect(socket_handle, (struct sockaddr *)&address, sizeof(address)) == SOCKET_ERROR) {
|
|
67
|
+
network_error = WSAGetLastError();
|
|
68
|
+
if (network_error == WSAEWOULDBLOCK) {
|
|
69
|
+
FD_ZERO(&writable);
|
|
70
|
+
FD_ZERO(&exceptional);
|
|
71
|
+
FD_SET(socket_handle, &writable);
|
|
72
|
+
FD_SET(socket_handle, &exceptional);
|
|
73
|
+
wait_time.tv_sec = 2;
|
|
74
|
+
wait_time.tv_usec = 0;
|
|
75
|
+
if (select(0, NULL, &writable, &exceptional, &wait_time) > 0) {
|
|
76
|
+
int socket_error = 0;
|
|
77
|
+
int error_length = sizeof(socket_error);
|
|
78
|
+
if (getsockopt(
|
|
79
|
+
socket_handle, SOL_SOCKET, SO_ERROR,
|
|
80
|
+
(char *)&socket_error, &error_length
|
|
81
|
+
) == 0) network_error = socket_error;
|
|
82
|
+
else network_error = WSAGetLastError();
|
|
83
|
+
} else {
|
|
84
|
+
network_error = WSAETIMEDOUT;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
closesocket(socket_handle);
|
|
89
|
+
}
|
|
90
|
+
WSACleanup();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
|
|
94
|
+
app_container = read_token_integer(token, TokenIsAppContainer, 0);
|
|
95
|
+
capability_count = read_token_integer(token, TokenCapabilities, -1);
|
|
96
|
+
CloseHandle(token);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
printf(
|
|
100
|
+
"appContainer=%d;capabilityCount=%d;filesystemError=%d;networkError=%d\n",
|
|
101
|
+
app_container != 0 ? 1 : 0,
|
|
102
|
+
capability_count,
|
|
103
|
+
filesystem_error,
|
|
104
|
+
network_error
|
|
105
|
+
);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|