Files
maka/scripts/windows-runtime-host-local-ipc-trust.ps1
agent-radar 7be535059e
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 19:05:14 +08:00

245 lines
7.7 KiB
PowerShell

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
$ErrorActionPreference = 'Stop'
if ($env:OS -ne 'Windows_NT') {
throw 'Windows Runtime Host Local IPC trust verification must run on Windows'
}
$repositoryRoot = Split-Path -Parent $PSScriptRoot
$fixturePath = Join-Path $repositoryRoot 'packages/runtime-host/dist/__tests__/fixtures/windows-local-ipc-trust-host.js'
if (-not (Test-Path $fixturePath)) {
throw "Missing Runtime Host trust fixture: $fixturePath"
}
Add-Type -TypeDefinition @'
using System;
using System.ComponentModel;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
public static class MakaWindowsPipeTrustProbe
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(
string username,
string domain,
string password,
int logonType,
int logonProvider,
out SafeAccessTokenHandle token);
public static string TryOpenAsUser(
string username,
string domain,
string password,
string pipeName,
PipeDirection direction)
{
SafeAccessTokenHandle token;
if (!LogonUser(username, domain, password, 2, 0, out token))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "LogonUser failed");
}
using (token)
{
return WindowsIdentity.RunImpersonated(token, () => TryOpen(pipeName, direction));
}
}
private static string TryOpen(string pipeName, PipeDirection direction)
{
try
{
using (var client = new NamedPipeClientStream(
".",
pipeName,
direction,
PipeOptions.None,
TokenImpersonationLevel.Identification))
{
client.Connect(3000);
return "connected";
}
}
catch (UnauthorizedAccessException)
{
return "access_denied";
}
catch (IOException error)
{
var win32Code = error.HResult & 0xffff;
return win32Code == 5 ? "access_denied" : "io_error:" + win32Code;
}
catch (TimeoutException)
{
return "timeout";
}
}
}
'@
function Read-ProcessLine {
param(
[Parameter(Mandatory = $true)]
[System.Diagnostics.Process]$Process,
[Parameter(Mandatory = $true)]
[int]$TimeoutMilliseconds
)
$readTask = $Process.StandardOutput.ReadLineAsync()
if (-not $readTask.Wait($TimeoutMilliseconds)) {
throw "Timed out waiting for Runtime Host trust fixture output"
}
$line = $readTask.Result
if ($null -eq $line) {
$stderr = $Process.StandardError.ReadToEnd()
throw "Runtime Host trust fixture exited before producing output: $stderr"
}
return $line
}
$userName = "MakaIpc$(([Guid]::NewGuid().ToString('N')).Substring(0, 8))"
$password = "Maka-Ipc!$([Guid]::NewGuid().ToString('N'))aA1"
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$fixture = $null
$currentUserClients = @()
$createdUser = $false
try {
New-LocalUser `
-Name $userName `
-Password $securePassword `
-AccountNeverExpires `
-PasswordNeverExpires | Out-Null
$createdUser = $true
$startInfo = [System.Diagnostics.ProcessStartInfo]::new()
$startInfo.FileName = (Get-Command node.exe).Source
$startInfo.ArgumentList.Add($fixturePath)
$startInfo.WorkingDirectory = $repositoryRoot
$startInfo.UseShellExecute = $false
$startInfo.RedirectStandardInput = $true
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$startInfo.CreateNoWindow = $true
$candidateFixture = [System.Diagnostics.Process]::new()
$candidateFixture.StartInfo = $startInfo
try {
if (-not $candidateFixture.Start()) {
throw 'Unable to start Runtime Host trust fixture'
}
} catch {
$candidateFixture.Dispose()
throw
}
$fixture = $candidateFixture
# The fixture prints readiness only once the endpoint ACL has been applied, so
# this deadline has to outlast WINDOWS_PIPE_ACL_TIMEOUT_MS in
# packages/runtime-host/src/control/endpoint.ts -- otherwise this throws first
# and the fixture's own diagnostic is never read. 45s matches the client
# readiness budget in packages/runtime-host/src/client/wait-for-ready.ts.
$ready = Read-ProcessLine -Process $fixture -TimeoutMilliseconds 45000 | ConvertFrom-Json
if ($ready.type -ne 'ready' -or $ready.endpoint -notmatch '^\\\\\.\\pipe\\(.+)$') {
throw "Invalid Runtime Host trust fixture readiness: $($ready | ConvertTo-Json -Compress)"
}
$pipeName = $Matches[1]
# Keep enough owner connections open to consume libuv's initial pending pipe
# instances and force replacement instances before probing the foreign user.
for ($index = 0; $index -lt 8; $index += 1) {
$currentUserClient = [System.IO.Pipes.NamedPipeClientStream]::new(
'.',
$pipeName,
[System.IO.Pipes.PipeDirection]::InOut,
[System.IO.Pipes.PipeOptions]::None,
[System.Security.Principal.TokenImpersonationLevel]::Identification
)
try {
$currentUserClient.Connect(5000)
$currentUserClients += $currentUserClient
} catch {
$currentUserClient.Dispose()
throw
}
$accepted = Read-ProcessLine -Process $fixture -TimeoutMilliseconds 5000 | ConvertFrom-Json
if ($accepted.type -ne 'accepted' -or $accepted.principalKind -ne 'local_owner') {
throw "Current-user connection did not receive Local Owner authority"
}
}
foreach ($direction in @(
[System.IO.Pipes.PipeDirection]::In,
[System.IO.Pipes.PipeDirection]::Out,
[System.IO.Pipes.PipeDirection]::InOut
)) {
$foreignResult = [MakaWindowsPipeTrustProbe]::TryOpenAsUser(
$userName,
$env:COMPUTERNAME,
$password,
$pipeName,
$direction
)
if ($foreignResult -ne 'access_denied') {
throw "Foreign Windows user unexpectedly opened the Local IPC listener ($direction): $foreignResult"
}
}
Write-Output 'Runtime Host Windows Local IPC admitted the current user and denied a foreign user.'
} finally {
try {
try {
foreach ($client in $currentUserClients) {
$client.Dispose()
}
} finally {
if ($null -ne $fixture) {
if (-not $fixture.HasExited) {
$fixture.StandardInput.WriteLine('close')
$fixture.StandardInput.Flush()
if (-not $fixture.WaitForExit(5000)) {
$fixture.Kill($true)
$fixture.WaitForExit()
}
}
if ($fixture.ExitCode -ne 0) {
$stderr = $fixture.StandardError.ReadToEnd()
throw "Runtime Host trust fixture exited with $($fixture.ExitCode): $stderr"
}
}
}
} finally {
try {
if ($null -ne $fixture) {
$fixture.Dispose()
}
} finally {
if ($createdUser) {
Remove-LocalUser -Name $userName -ErrorAction Stop
}
}
}
}