mirror of
https://github.com/morluto/rea.git
synced 2026-10-11 21:39:55 +00:00
157 lines
8.4 KiB
Markdown
157 lines
8.4 KiB
Markdown
# Windows native controls
|
|
|
|
REA ships one Windows x64 Node-API 8 addon. Users do not compile it or install
|
|
another runtime. Ghidra and a full JDK remain bring-your-own prerequisites.
|
|
The addon uses Windows 10+ APIs and admits fixed local NTFS volumes only.
|
|
|
|
The package owns the artifact path. The loader checks the package version,
|
|
ABI, Node-API compatibility, PE architecture, and SHA-256 before loading it.
|
|
There is no environment override for loading an arbitrary native module.
|
|
|
|
## Build
|
|
|
|
The artifact lane requires Linux, MinGW-w64 `x86_64-w64-mingw32-g++`,
|
|
`x86_64-w64-mingw32-dlltool`, and `tar`. It never installs these commands.
|
|
Node-API headers are reused from the build cache or downloaded from the
|
|
pinned official Node release with its published SHA-256 checked.
|
|
|
|
```sh
|
|
npm run build:windows-native
|
|
npm run verify:windows-native:artifact
|
|
npm pack
|
|
```
|
|
|
|
`build/` is ignored by Git. The npm file inventory includes only the addon and
|
|
its manifest. Release CI builds both, and `prepublishOnly` rejects a missing,
|
|
stale, mismatched, or wrong-architecture artifact. A development tarball built
|
|
without this lane reports Windows native controls as unavailable.
|
|
|
|
## Boundaries
|
|
|
|
- Opaque Node objects carry a native type tag, resource kind, and closed state.
|
|
A pathname, serialized object, or forged handle cannot grant authority.
|
|
- Input admission opens every component with `OPEN_REPARSE_POINT`, verifies
|
|
the actual NTFS object, and retains handles that deny replacement and source
|
|
writing. An admitted child keeps each ancestor nonempty; ancestor handles
|
|
permit unrelated writes while NTFS rejects in-place reparse conversion. Volume serial, 128-bit file ID, final handle path,
|
|
requested path, size, and digest observations are preserved separately.
|
|
- New runtime roots and private child objects are created relative to admitted
|
|
parent handles with `NtCreateFile`. A protected DACL and current-user owner
|
|
are installed at creation. Readback permits exactly the current user and SYSTEM. Child
|
|
files inherit this boundary; private files are also checked by handle.
|
|
- Snapshots stream through a 64 KiB buffer on a Node async worker. Cancellation
|
|
is checked between reads, cleanup waits for settlement, and the digest
|
|
commits to the bytes written. Descriptor and snapshot handles remain held
|
|
until runtime cleanup.
|
|
Snapshot ownership is single-flight per runtime: an overlapping request fails
|
|
with `ERROR_BUSY` before acquiring cancellation ownership. The TypeScript owner
|
|
records only the accepted promise and abort listener; aborting a rejected copy
|
|
must not cancel the active copy.
|
|
- `PROC_THREAD_ATTRIBUTE_JOB_LIST` assigns a suspended child atomically to an
|
|
unnamed, non-inherited Job Object. Membership and kill-on-close policy are
|
|
checked before resume. Breakaway is disabled, and cleanup waits for all
|
|
job members and captured output, including detached descendants.
|
|
- A launch may request a Job Object commit limit (`JOB_OBJECT_LIMIT_JOB_MEMORY`)
|
|
as a positive, page-aligned safe-integer byte count; invalid values fail
|
|
with `ERROR_INVALID_PARAMETER` before any process is created. Readback must
|
|
match the requested flags and limit exactly. A private I/O completion port is
|
|
associated with that job before the child is created; each poll drains it
|
|
without waiting and records a sticky `memoryLimitReached` when
|
|
`JOB_OBJECT_MSG_JOB_MEMORY_LIMIT` arrives, alongside the job's
|
|
`peakJobMemoryBytes`. Notifications are drained before checking for exit
|
|
and again after exit is observed, so a memory failure between those steps
|
|
is included in the first exit report. Runtimes report a reached limit
|
|
differently (Bun 1.4.2 exits 1
|
|
after a JavaScript out-of-memory exception or 3 after a panic), so callers
|
|
classify by the notification. `inspect` reports `jobMemoryLimit` only after
|
|
creating such a job and port. An artifact without that capability refuses a
|
|
requested limit rather than launching an unlimited process. REA requests
|
|
limits only for its JavaScript helpers when running on Bun, which ignores
|
|
their V8 heap flags; Node.js launches are unchanged.
|
|
- Only the suspended child's default file owner is normalized. Caller token
|
|
privileges and ownership remain unchanged. Caller CPU affinity and process
|
|
priority are preserved in the child. Environment names are sorted and
|
|
deduplicated using Windows ordinal comparison, preserving their native Unicode
|
|
semantics and caller-selected values.
|
|
- Cleanup walks locked directory objects, removes files by handle, and unlinks
|
|
reparse entries without traversing their targets. Failed removal retains the
|
|
root and ancestor leases for retry; a partially removed runtime accepts only
|
|
cleanup, and its registry entry remains until removal succeeds. Normal close, cancellation,
|
|
startup failure, and timeout release provider resources. Abrupt owner death
|
|
kills the owned job; it can leave private runtime files for later operator
|
|
cleanup and does not claim transactional deletion after a crash.
|
|
|
|
Windows failures retain their constraint, requested coordinate, Win32 code,
|
|
and system message. Unsupported filesystems and reparse paths are distinct
|
|
from OS access denial and missing packaged controls.
|
|
|
|
Ordinary drive-absolute paths accept backslashes, forward slashes, or mixed
|
|
separators. File identity retains the original requested spelling. Separator
|
|
translation does not resolve dot components, repeated separators, or extend
|
|
the supported device/UNC namespaces; existing admission checks still apply.
|
|
|
|
The bearer-token descriptor remains under its immutable private lease until
|
|
`runtime_close` on Windows; POSIX removes the descriptor after the bridge reads
|
|
it. Job ownership guarantees process termination on owner death, not deletion
|
|
of private files after a crash. SUBST drive aliases and changing DOS-device
|
|
namespaces are outside the verified P0 scope and are not detected as a separate
|
|
alias policy. Mounted-folder paths that report a reparse tag are rejected by
|
|
component admission; broader namespace variants remain unverified. Preserve
|
|
requested and final handle coordinates rather than treating them as one identity.
|
|
|
|
## Real verification
|
|
|
|
```powershell
|
|
npm run verify:windows-native
|
|
npm run verify:ghidra:windows
|
|
```
|
|
|
|
The native inspection probe retains its directory owner if deletion fails. A
|
|
later availability query retries that cleanup before creating another probe;
|
|
successful admission and static artifact failures remain cached. Environment
|
|
teardown makes one final cleanup attempt and reports a persistent failure to
|
|
stderr rather than claiming the directory was removed.
|
|
|
|
CI separately builds and verifies a test-only artifact:
|
|
|
|
```sh
|
|
node scripts/build-windows-native.mjs --inspection-fixture
|
|
```
|
|
|
|
On Windows, after compiling the runtime:
|
|
|
|
```powershell
|
|
node scripts/verify-windows-native-inspection.mjs
|
|
```
|
|
|
|
This artifact injects one access-denied disposition result while using a real
|
|
NTFS directory. The verifier checks retained ownership, recovery on a later
|
|
query, cached successful admission, and cleanup on environment teardown. It
|
|
does not reproduce or identify the host-dependent cause reported in #1701.
|
|
The fixture is stored under `.cache/` and is excluded from the shipped addon.
|
|
|
|
The native lane checks DACL readback independently, source write/replacement
|
|
rejection, unrelated sibling renames, reparse rejection, immutable snapshots,
|
|
asynchronous cancellation, cleanup containment,
|
|
descendant termination, unrelated-process preservation, normal exit, owner
|
|
close, forced owner exit, and Job Object commit limits: invalid limit
|
|
rejection, a 128 MiB-limited child allocating external Buffer memory that the
|
|
V8 heap flags cannot bound (limit notification, peak commit at the limit, all
|
|
members settled, with the notification present in the first native exit report
|
|
and the wrapper's exit event), and a control under the same limit that
|
|
completes normally.
|
|
An optional independently built
|
|
`tests/fixtures/windows/processBoundary.cc` executable adds breakaway and
|
|
caller/child token observations and an in-place ancestor reparse attempt:
|
|
|
|
```powershell
|
|
node scripts/verify-windows-native.mjs . C:\fixtures\process-boundary.exe
|
|
```
|
|
|
|
`nonAdminRunner.cc` is a test-only launcher for an elevated development shell.
|
|
It duplicates an existing non-elevated Explorer token belonging to the same
|
|
user, creates private temporary window-station objects, and launches the test
|
|
inside a kill-on-close fixture job. It does not create accounts, modify host
|
|
policy, or change the caller's token. A real-provider claim must additionally
|
|
verify the packaged CLI and MCP on that ordinary-user token.
|