Skip to content

Windows host built-ins (CI run only) - #6

Closed
basal-alfonso[bot] wants to merge 22 commits into
mainfrom
ci/windows-host
Closed

basal-alfonso[bot] wants to merge 22 commits into
mainfrom
ci/windows-host

Conversation

@basal-alfonso

@basal-alfonso basal-alfonso Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Draft to run the Windows CI job on the Windows fs and git built-ins. Not for merge as-is.


Summary by cubic

Adds Windows fs and git host built-ins, a confined Windows worker launcher (basal-launch), and BCryptGenRandom host entropy to enable basal-host and basal-launch tests in Windows CI. CI-only draft, not ready to merge.

  • Windows fs validates raw paths and reserved device names (COM/LPT superscripts included), refuses reparse points, and holds parent directories so writes cannot be moved mid-flight.
  • Windows git uses per-call private config and hooks, refuses script shims, pins approved directories (with a uniform denial when the pin walk fails), and contains children in a kill-on-close job to enforce timeouts and output caps. Handle-allowlist tests read the exact child's handle table via DuplicateHandle + CompareObjectHandles so concurrent ordinary spawns cannot mask a leak, and global-isolation tests add a real default HOME/.gitconfig control.
  • New basal-launch crate starts the worker under an AppContainer profile with restricted Low tokens, job limits, mitigation policies, and parent checks before resume.
  • Windows CI now runs basal-host and basal-launch tests with the static CRT.

Written for commit 39d05bf. Summary will update on new commits.

View guided diff Turn on auto-fix

Windows reserves COM and LPT followed by a superscript one, two or three
as well as the ASCII digits. Also write the device-name check as one
matches! so it passes clippy.
The module now compiles for Windows (x86_64 and aarch64 MSVC) with no
warnings under clippy -D warnings, and its tests can run.

Writes:
- Every directory from the volume root down to the target's parent is
  held without FILE_SHARE_DELETE until the rename returns, so none can be
  moved out of the root mid-write; the parent's location is re-checked
  just before the rename.
- A failed flush of the temporary file stops the write before the rename.
- A new file is created with no explicit security descriptor, so NTFS
  applies real inheritance; the CreatePrivateObjectSecurity path is gone.
  A replaced file keeps its DACL, protection flag included.
- The root is selected by the whole path, so a write to a file root works.
- Intermediate directories are opened with attribute and traverse access
  only; a directory target is refused as "not a regular file".
- A read-only target is replaced, as renameat ignores a target's mode.

Roots and opens:
- Root handles are reopened with the access each use needs, so a file
  root can be read and a directory root listed.
- A failed directory query is an error, never an empty listing.
- Walk errors keep their kind across roots (IO, then NOT_FOUND, then the
  refusal); a volume root X:\ grants its children; a file root is opened
  without FILE_TRAVERSE.
- The reparse check fails closed, and the temp-file cleanup removes only
  regular files.
- Directory replies are parsed as bytes bounded by the reported length,
  the rename buffer is 8-byte aligned and written through raw pointers,
  UNICODE_STRING lengths are checked, and OwnedHandle's field is private.
- remove_temp walks to the lease's own directory.

Tests: link-based tests assert the link exists; the swap test uses
expect_err; the DACL tests apply a DACL and compare SDDL (P on replace,
ID ACEs on create); new tests cover list, subdirectory create,
remove_temp, legacy temps, volume roots, mount points, the held parent,
flush failure and temp-name collisions. The test hooks are thread-local.
The POSIX refusal test now matches the real message. fs.rs only gates
helpers Windows does not use, and shares outside/io_denial/TEMP_SEQ.
A new workspace crate that starts basal's worker under the Windows
confinement; off Windows it contains no code.

- Profile: one shared AppContainer profile, created or opened under a
  session-wide named mutex; failure is appcontainer-profile-unavailable.
  Userenv and User32 are loaded at run time from System32 only.
- Tokens: the primary is a restricted copy of the parent's token (every
  access group deny-only, no privileges, NULL SID as the only restricting
  SID, Low), lowboxed at creation with zero capabilities. The start-up
  thread token is a Low same-package impersonation token derived from a
  never-resumed AppContainer process, set on the suspended main thread
  and closed before resume.
- Creation: CreateProcessAsUserW with STARTUPINFOEX, suspended: LPAC
  security capabilities with the ALL_APPLICATION_PACKAGES opt-out, the
  job list, eight always-on mitigations, the child-process ban, and a
  handle list of exactly the three stdio pipes. Explicit sorted
  environment (SYSTEMROOT, windir, SYSTEMDRIVE, PATH to System32,
  TEMP/TMP/LOCALAPPDATA to a private read-only directory), the image
  directory as cwd, and a private window station and desktop. The
  parent's environment is never passed on.
- Job: flags 0x2508, one live process, no breakaway, the caller's
  commit limit, UI restrictions 0xff.
- Checks before resume: job-limits-mismatch, not-in-owned-job,
  birth-token-mismatch and initial-token-open, each killing the child.
- ConfinedProcess owns process, job and pipes: kill (job, then process,
  exit 137), try_wait, wait, token and job read-back.
- Deviation: Full only without the `deviations` feature; with it, the
  LPAC-only and plain controls and one variant per worker and parent
  check.
- Tests start a GUI-subsystem test child for real. CI's Windows job
  runs them first with `--features deviations`.
- +crt-static for x86_64-pc-windows-msvc in .cargo/config.toml.
…g TEMP path

On windows-latest, kill() failed with access denied: TerminateJobObject
had already begun ending the worker, so the following TerminateProcess
was refused while the process was not yet signaled. A successful job
kill of a worker that is a member of the job is now a successful kill.

The LPAC-only control's TEMP write failed with Win32 3, not 5: the
system TEMP path holds 8.3 short names (RUNNER~1). The worker's TEMP is
now the canonical long path, and the control's assertion accepts any
denial, since only the plain control needs to show the path is writable.
getentropy exists only on Unix. On Windows the system-preferred RNG supplies
each journaled draw, and there is still no fallback when it fails.

(cherry picked from commit a69f2b0)
The workspace test can't build until every crate compiles on Windows, so
basal-host's built-ins get their own test step that reports now.

(cherry picked from commit 990ce58)
Retain both process-attribute payloads through attribute deletion, create suspended, verify membership in the exact kill-on-close job, then resume. Use an explicit canonical drive image and a UTF-16 CRT encoder. Give each call exclusively created private config/hooks resources and read-only NUL stdin. Reuse the fs worker's no-delete-sharing handle walk via a small PinnedDirectory seam; the guard survives every git child in the operation.

Retry failed job termination and put kill/close plus bounded worker cancellation inside the thread scope. Replace handwritten ABI declarations with windows-sys. Replace the original recipe-only or vacuous Windows tests with native argv round trips, job/handle observations, pinned-path controls, live descendant death, byte/line-cap writers, exceptional cleanup watchdogs, and real helper/global-config positive and negative controls. Hook controls now perform checkout rather than log, and tag writers exceed the byte budget rather than merely truncating small output.

Mac and Linux basal-host tests/clippy pass; mounted MSVC lib/tests clippy and child fixture metadata checks pass with warnings denied. Native Windows execution is pending the parent-owned push/CI gate. The shared Unix canonicalize-then-git-C path race is deliberately unchanged in this slice.
Native run 38065124338 exercised all new git tests: seven failed only because GetTempPathW's trailing backslash was passed unchanged to the fs raw-spelling validator. Normalize just that host-selected base before the unchanged reparse-refusing fs walk; repository path policy remains strict.

Add a regression test for that native temp spelling, inspect the actual protected DACL of directory/hooks/config (only SYSTEM and owner rights), refuse a temp junction, and attempt config/hook replacements concurrently while their guards are live. MSVC scratch lib/tests clippy -D warnings passes. Mac/Linux gates are unchanged by these Windows-only edits. All 44 fs tests passed in the first native run; rerun Windows host tests to close the seven git failures.
Native run 38065638982 had 44 fs and 21 git passes, with one handle test failure: PeekNamedPipe returned TRUE after parent writer closure. That establishes that a writer exists somewhere, not that the tested process owns it; concurrent ordinary setup/rustc spawns can inherit the same sentinel. Microsoft CreateProcessW Remarks explicitly document this multithreaded inheritance problem, and its bInheritHandles documentation says inherited handles have the same numeric value and access rights.

Keep the parent's writer alive and DuplicateHandle from the exact tested child's handle table before resume and after it starts; CompareObjectHandles distinguishes the sentinel from a reused numeric slot or OS-created object. Add a deliberately unrestricted suspended CreateProcess(TRUE) positive control requiring that same-object observation to succeed. This strengthens the child-specific no-leak claim instead of relaxing it or retrying global EOF. No product handle allowlist is weakened; a real target-child leak will fail these assertions.

References: learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw (Parameters/Remarks), learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle (Parameters/Remarks), learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-compareobjecthandles (Return value).

Also preserve the original uniform git repository-denial contract when the fs pin walk fails (including missing unapproved paths), with an existence-privacy regression test. Strengthen global isolation with a real default HOME/.gitconfig positive control as well as the explicit GIT_CONFIG_GLOBAL control, so clearing inherited environment alone cannot satisfy the test. MSVC mounted lib/tests clippy with -D warnings passes; edits are Windows-only and do not impact earlier Mac/Linux gates. Native rerun pending.
@basal-alfonso

basal-alfonso Bot commented Oct 10, 2026

Copy link
Copy Markdown
Author

Closing: its contents are merged on the integration/windows branch.

@basal-alfonso basal-alfonso Bot closed this Oct 10, 2026
@ualtinok
ualtinok deleted the ci/windows-host branch October 10, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant