@holoscript/holosystem 0.2.4 → 0.2.6
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 +94 -0
- package/bin/holosystem.mjs +76 -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 +10 -2
- package/src/index.mjs +20 -0
- package/src/source-canon.mjs +307 -0
- package/src/vm-launch.mjs +336 -45
package/README.md
CHANGED
|
@@ -55,6 +55,48 @@ returns one highest-priority candidate, and carries authority, validation, lease
|
|
|
55
55
|
and spend stop conditions. It selects work; it does not claim a board task,
|
|
56
56
|
publish a package, spend funds, or bypass caller authority.
|
|
57
57
|
|
|
58
|
+
## HoloScript Source Canon
|
|
59
|
+
|
|
60
|
+
`source-canon` is the consumer-side language-sovereignty gate. Run it from the
|
|
61
|
+
root of a Git repository:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx holosystem source-canon \
|
|
65
|
+
--output runtime/visual-state/source-canon.hsplus \
|
|
66
|
+
--json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The gate reads the fixed Git-tracked file inventory and accepts only `.holo`,
|
|
70
|
+
`.hs`, and `.hsplus` as canonical authored source. It then parses the actual
|
|
71
|
+
tracked bytes with the exact optional peer `@holoscript/core@8.0.14`; when that
|
|
72
|
+
parser is unavailable, the gate fails closed. This prevents JavaScript or another
|
|
73
|
+
language from becoming canonical merely by being renamed. Other HoloSystem
|
|
74
|
+
commands remain usable without the optional peer.
|
|
75
|
+
|
|
76
|
+
A caller cannot widen the registry with an extension allowlist. A future format
|
|
77
|
+
becomes canonical only when a HoloScript release owns its parser and adds it to
|
|
78
|
+
the package registry.
|
|
79
|
+
|
|
80
|
+
The command exits `0` only when at least one HoloScript source file exists and
|
|
81
|
+
every tracked file uses a canonical format. It exits `2` with every foreign
|
|
82
|
+
tracked path named otherwise. When `--output` is supplied, the durable
|
|
83
|
+
founder-visible projection must itself be a portable repository-relative
|
|
84
|
+
`.hsplus` path; JSON remains available only on standard output for transient
|
|
85
|
+
agent pipelines.
|
|
86
|
+
|
|
87
|
+
This gate audits Git-tracked canon. Dependency caches, runtime receipts, and
|
|
88
|
+
untracked generated artifacts are not silently promoted to source truth. They
|
|
89
|
+
remain non-canonical operational state and must stay untracked or move to an
|
|
90
|
+
external runtime store as the consumer migration proceeds.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import {
|
|
94
|
+
inspectGitTrackedSourceCanon,
|
|
95
|
+
inspectSourceCanon,
|
|
96
|
+
renderSourceCanonProjection,
|
|
97
|
+
} from '@holoscript/holosystem';
|
|
98
|
+
```
|
|
99
|
+
|
|
58
100
|
## Substrate Closure
|
|
59
101
|
|
|
60
102
|
The substrate closure replaces an implicit operating-system dependency tower
|
|
@@ -521,6 +563,20 @@ kill-on-close Job Object with one-process, memory, and UI restrictions, and
|
|
|
521
563
|
only then resumes it. A private low-integrity temporary directory is the only
|
|
522
564
|
intended writable launch location.
|
|
523
565
|
|
|
566
|
+
`vm-launch-whpx-appcontainer` is a fourth, separately named vocabulary. It
|
|
567
|
+
retains those restricted-token and Job controls, gives an ephemeral Windows
|
|
568
|
+
AppContainer identity zero capabilities, grants that identity only read/execute
|
|
569
|
+
access to the measured launch snapshot and modify access to its private temp
|
|
570
|
+
directory, and requires file-read plus loopback-connect attack canaries to fail
|
|
571
|
+
before either WHPX launch begins. The plan cannot name a capability, path,
|
|
572
|
+
command, network mode, or fallback.
|
|
573
|
+
|
|
574
|
+
`vm-launch-appcontainer` uses the same zero-capability boundary with QEMU TCG.
|
|
575
|
+
It fixes the translation buffer at 64 MiB so the one-process 512 MiB Job limit
|
|
576
|
+
remains effective. This is the fully confined adapter proven on the validated
|
|
577
|
+
Windows host: it proves the machine VM and host file/network boundary, but sets
|
|
578
|
+
`hardwareBacked: false` and keeps hardware acceleration missing.
|
|
579
|
+
|
|
524
580
|
First measure the complete caller-owned QEMU runtime closure and both guest
|
|
525
581
|
artifacts:
|
|
526
582
|
|
|
@@ -596,6 +652,25 @@ process creation. The launcher source and local rebuild recipe are shipped in
|
|
|
596
652
|
`native/windows-sandbox`; the measured binary is shipped in
|
|
597
653
|
`native/windows-x64`.
|
|
598
654
|
|
|
655
|
+
For the AppContainer adapter, use schema
|
|
656
|
+
`holoscript.holosystem.whpx-appcontainer-vm-launch-plan.v1`, use the same
|
|
657
|
+
launcher digest with sandbox kind `windows-appcontainer-deny-v1`, and call
|
|
658
|
+
`vm-launch-whpx-appcontainer`. The launcher creates and deletes a fresh profile
|
|
659
|
+
for each launch. Its closed receipt protocol must prove the child AppContainer
|
|
660
|
+
SID, zero capabilities, low-integrity filtered token, snapshot/temp grants,
|
|
661
|
+
access-denied (`5`) for a protected caller-readable file, plus either explicit
|
|
662
|
+
WSA access-denied (`10013`) or a bounded WFP-style packet-drop timeout (`10060`)
|
|
663
|
+
with no accepted live loopback connection. Any missing or changed observation
|
|
664
|
+
blocks the receipt.
|
|
665
|
+
|
|
666
|
+
Use schema `holoscript.holosystem.appcontainer-vm-launch-plan.v1`, target
|
|
667
|
+
accelerator `tcg`, the same two sandbox digests, and command
|
|
668
|
+
`vm-launch-appcontainer` for the software-emulated confined lane. On the
|
|
669
|
+
validated Windows 11 host, the WHPX AppContainer form fails closed because
|
|
670
|
+
Windows returns `0x80070005` when zero-capability QEMU initializes WHPX. The
|
|
671
|
+
package does not merge that result with the separately proven hardware-backed
|
|
672
|
+
low-integrity WHPX receipt.
|
|
673
|
+
|
|
599
674
|
The runner creates a private snapshot and remeasures the complete QEMU closure,
|
|
600
675
|
kernel, and initramfs before and after each launch. It then generates QEMU
|
|
601
676
|
arguments that disable user configuration, default devices, networking, USB,
|
|
@@ -629,15 +704,31 @@ boundary. The sandboxed receipt also does not prove the broader hardware and
|
|
|
629
704
|
firmware properties named above. The
|
|
630
705
|
exact claims are in the [VM launch threat model](./docs/vm-launch-threat-model.md).
|
|
631
706
|
|
|
707
|
+
A verified `vm-launch-whpx-appcontainer` receipt additionally includes
|
|
708
|
+
`host-filesystem-confidentiality` and `host-network-isolation` for the tested
|
|
709
|
+
zero-capability AppContainer boundary. Those are scoped claims: they do not
|
|
710
|
+
establish QEMU provenance, host-kernel correctness, IOMMU separation, measured
|
|
711
|
+
boot, firmware authenticity, crash-dump custody, confidential memory, or
|
|
712
|
+
side-channel resistance.
|
|
713
|
+
|
|
714
|
+
A verified `vm-launch-appcontainer` receipt includes the same process,
|
|
715
|
+
filesystem, and network layers while leaving
|
|
716
|
+
`hardware-hypervisor-acceleration` missing. Receipts describe one executed
|
|
717
|
+
boundary, not the union of properties observed in different launches.
|
|
718
|
+
|
|
632
719
|
```js
|
|
633
720
|
import {
|
|
634
721
|
inspectVmExecutor,
|
|
722
|
+
inspectAppContainerVmLaunchPlan,
|
|
635
723
|
inspectVmLaunchAsset,
|
|
636
724
|
inspectVmLaunchPlan,
|
|
637
725
|
inspectWindowsVmSandboxLauncher,
|
|
726
|
+
inspectWhpxAppContainerVmLaunchPlan,
|
|
638
727
|
inspectWhpxSandboxedVmLaunchPlan,
|
|
639
728
|
inspectWhpxVmLaunchPlan,
|
|
640
729
|
runVmLaunch,
|
|
730
|
+
runAppContainerVmLaunch,
|
|
731
|
+
runWhpxAppContainerVmLaunch,
|
|
641
732
|
runWhpxSandboxedVmLaunch,
|
|
642
733
|
runWhpxVmLaunch,
|
|
643
734
|
} from '@holoscript/holosystem';
|
|
@@ -700,6 +791,9 @@ npx holosystem inspect holosystem.config.json --json
|
|
|
700
791
|
|
|
701
792
|
# Pipeline input is supported.
|
|
702
793
|
npx holosystem create --stdout | npx holosystem inspect - --json
|
|
794
|
+
|
|
795
|
+
# Prove that Git-tracked authoring is HoloScript-only.
|
|
796
|
+
npx holosystem source-canon --output source-canon.hsplus --json
|
|
703
797
|
```
|
|
704
798
|
|
|
705
799
|
Creation never installs packages, connects to storage, acquires credentials, or
|
package/bin/holosystem.mjs
CHANGED
|
@@ -10,13 +10,17 @@ import {
|
|
|
10
10
|
importDebianPackageSnapshot,
|
|
11
11
|
importNpmPackageLock,
|
|
12
12
|
inspectNativeBuildSource,
|
|
13
|
+
inspectGitTrackedSourceCanon,
|
|
13
14
|
inspectHoloSystemConfig,
|
|
14
15
|
inspectVmExecutor,
|
|
15
16
|
inspectVmLaunchAsset,
|
|
16
17
|
runNativeBuild,
|
|
18
|
+
runAppContainerVmLaunch,
|
|
17
19
|
runVmLaunch,
|
|
20
|
+
runWhpxAppContainerVmLaunch,
|
|
18
21
|
runWhpxSandboxedVmLaunch,
|
|
19
22
|
runWhpxVmLaunch,
|
|
23
|
+
renderSourceCanonProjection,
|
|
20
24
|
} from '../src/index.mjs';
|
|
21
25
|
|
|
22
26
|
const CLI_RECEIPT_SCHEMA = 'holoscript.holosystem.cli-receipt.v1';
|
|
@@ -32,11 +36,14 @@ Usage:
|
|
|
32
36
|
holosystem substrate-import-debian --status <status> (--packages <Packages> | --sources <json>) --maintainer-scripts <json> --config <file> [--output <file>] [--force] [--json]
|
|
33
37
|
holosystem native-build-source --source <directory> [--json]
|
|
34
38
|
holosystem native-build --plan <file> --source <directory> --executor <file> --artifact-dir <directory> [--output <receipt>] [--force] [--json]
|
|
39
|
+
holosystem source-canon [--output <projection.hsplus>] [--force] [--json]
|
|
35
40
|
holosystem vm-executor --runtime <directory> [--json]
|
|
36
41
|
holosystem vm-asset --kind <kernel|initrd> --file <file> [--json]
|
|
37
42
|
holosystem vm-launch --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
43
|
+
holosystem vm-launch-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
38
44
|
holosystem vm-launch-whpx --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
39
45
|
holosystem vm-launch-whpx-sandboxed --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
46
|
+
holosystem vm-launch-whpx-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
|
|
40
47
|
holosystem substrate --input <file> [--output <file>] [--force] [--json]
|
|
41
48
|
holosystem --help
|
|
42
49
|
holosystem --version
|
|
@@ -202,6 +209,63 @@ function writeJsonOutput(path, value, { force = false } = {}) {
|
|
|
202
209
|
writeFileSync(absolute, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
203
210
|
}
|
|
204
211
|
|
|
212
|
+
function writeSourceCanonProjection(path, value, { force = false } = {}) {
|
|
213
|
+
if (
|
|
214
|
+
typeof path !== 'string' ||
|
|
215
|
+
!path.endsWith('.hsplus') ||
|
|
216
|
+
/^[A-Za-z]:[\\/]/u.test(path) ||
|
|
217
|
+
/^(?:[\\/]{1,2}|~[\\/])/u.test(path) ||
|
|
218
|
+
path.replaceAll('\\', '/').split('/').some((segment) => segment === '..' || segment === '.')
|
|
219
|
+
) {
|
|
220
|
+
throw new Error('--output must be a portable repository-relative .hsplus path.');
|
|
221
|
+
}
|
|
222
|
+
const absolute = resolve(process.cwd(), path);
|
|
223
|
+
if (existsSync(absolute) && !force)
|
|
224
|
+
throw new Error(`${path} already exists; use --force to replace it.`);
|
|
225
|
+
writeFileSync(absolute, value, 'utf8');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function runSourceCanon(args) {
|
|
229
|
+
let parsed;
|
|
230
|
+
try {
|
|
231
|
+
parsed = parseArguments(args, {
|
|
232
|
+
output: 'value',
|
|
233
|
+
force: 'boolean',
|
|
234
|
+
json: 'boolean',
|
|
235
|
+
});
|
|
236
|
+
} catch (error) {
|
|
237
|
+
die(error.message, { json: args.includes('--json') });
|
|
238
|
+
}
|
|
239
|
+
const { options, positionals } = parsed;
|
|
240
|
+
if (positionals.length > 0) {
|
|
241
|
+
die('source-canon does not accept positional arguments.', { json: options.json });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let report;
|
|
245
|
+
try {
|
|
246
|
+
report = await inspectGitTrackedSourceCanon({ rootDirectory: process.cwd() });
|
|
247
|
+
if (options.output) {
|
|
248
|
+
writeSourceCanonProjection(options.output, renderSourceCanonProjection(report), {
|
|
249
|
+
force: options.force,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
die(`Cannot inspect source canon: ${error.message}`, { json: options.json, code: 2 });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.json) outputJson(report);
|
|
257
|
+
else {
|
|
258
|
+
process.stdout.write(
|
|
259
|
+
`Source canon: ${report.status} HoloScript=${report.summary.holoScriptFiles} foreign=${report.summary.foreignFiles}\n`
|
|
260
|
+
);
|
|
261
|
+
for (const issue of report.issues) {
|
|
262
|
+
process.stdout.write(`BLOCK ${issue.code} ${issue.path}: ${issue.message}\n`);
|
|
263
|
+
}
|
|
264
|
+
if (options.output) process.stdout.write(`Wrote ${options.output}\n`);
|
|
265
|
+
}
|
|
266
|
+
if (!report.verified) process.exitCode = 2;
|
|
267
|
+
}
|
|
268
|
+
|
|
205
269
|
function runInspect(args) {
|
|
206
270
|
let parsed;
|
|
207
271
|
try {
|
|
@@ -697,12 +761,19 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
697
761
|
runNativeBuildSource(argv.slice(1));
|
|
698
762
|
} else if (command === 'native-build') {
|
|
699
763
|
runNativeBuildCommand(argv.slice(1));
|
|
764
|
+
} else if (command === 'source-canon') {
|
|
765
|
+
await runSourceCanon(argv.slice(1));
|
|
700
766
|
} else if (command === 'vm-executor') {
|
|
701
767
|
runVmExecutor(argv.slice(1));
|
|
702
768
|
} else if (command === 'vm-asset') {
|
|
703
769
|
runVmAsset(argv.slice(1));
|
|
704
770
|
} else if (command === 'vm-launch') {
|
|
705
771
|
runVmLaunchCommand(argv.slice(1));
|
|
772
|
+
} else if (command === 'vm-launch-appcontainer') {
|
|
773
|
+
runVmLaunchCommand(argv.slice(1), {
|
|
774
|
+
launcher: runAppContainerVmLaunch,
|
|
775
|
+
commandName: 'vm-launch-appcontainer',
|
|
776
|
+
});
|
|
706
777
|
} else if (command === 'vm-launch-whpx') {
|
|
707
778
|
runVmLaunchCommand(argv.slice(1), {
|
|
708
779
|
launcher: runWhpxVmLaunch,
|
|
@@ -713,6 +784,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
713
784
|
launcher: runWhpxSandboxedVmLaunch,
|
|
714
785
|
commandName: 'vm-launch-whpx-sandboxed',
|
|
715
786
|
});
|
|
787
|
+
} else if (command === 'vm-launch-whpx-appcontainer') {
|
|
788
|
+
runVmLaunchCommand(argv.slice(1), {
|
|
789
|
+
launcher: runWhpxAppContainerVmLaunch,
|
|
790
|
+
commandName: 'vm-launch-whpx-appcontainer',
|
|
791
|
+
});
|
|
716
792
|
} else if (command === 'substrate') {
|
|
717
793
|
runSubstrate(argv.slice(1));
|
|
718
794
|
} 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
|
+
}
|