Merge nucleic/lucid-river-toad-6efj into dev
This commit is contained in:
+12
-4
@@ -133,10 +133,18 @@ class, confirming §13.1's retraction against the machine rather than against an
|
|||||||
answering through both faces — and `ListSessions()` (slot 6) returned S_OK. So hand-written
|
answering through both faces — and `ListSessions()` (slot 6) returned S_OK. So hand-written
|
||||||
`ComImport` is sufficient and the C++/WinRT shim §13.1 mused about is not needed.
|
`ComImport` is sufficient and the C++/WinRT shim §13.1 mused about is not needed.
|
||||||
|
|
||||||
It returned **0 sessions**, though, so the entry-struct layout (inline `wchar_t` buffers, the same
|
Re-run as `--session --keep --internal-call`, `ListSessions` returned the live session by name
|
||||||
shape `ListContainers` uses) is still untested. Run `--session --keep --internal-call` to create a
|
(`#6 "nucleic-spike"`), so the entry-struct layout — inline `wchar_t` buffers, the same shape
|
||||||
session, re-adopt it through the internal interface, and exercise it — that combination is §2.3's
|
`ListContainers` uses — marshals as declared. **Enumeration is proven.**
|
||||||
reattach story end to end, and it also answers the handoff question below. `wsl --shutdown` after.
|
|
||||||
|
That run also turned up a trap worth knowing before writing any of this into brokerd:
|
||||||
|
`OpenSessionByName` failed **`0x80070542`** on the session `ListSessions` had just listed.
|
||||||
|
That is `ERROR_BAD_IMPERSONATION_LEVEL` — the service impersonates the caller to resolve a
|
||||||
|
*per-user* session, and .NET hands COM proxies `RPC_C_IMP_LEVEL_IDENTIFY` by default. A security
|
||||||
|
error that reads exactly like "not found", and only on the methods reattach needs. The probe now
|
||||||
|
raises the proxy blanket to `RPC_C_IMP_LEVEL_IMPERSONATE`; brokerd should call
|
||||||
|
`CoInitializeSecurity` at startup instead, **before** its first COM call — including the compat
|
||||||
|
SDK's, or it fails `RPC_E_TOO_LATE`. `wsl --shutdown` to clean up after `--keep`.
|
||||||
|
|
||||||
The hypothesis was that one of those objects also implements the internal interface — COM
|
The hypothesis was that one of those objects also implements the internal interface — COM
|
||||||
objects routinely expose several — so `--internal` activates each and QIs for the internal IIDs.
|
objects routinely expose several — so `--internal` activates each and QIs for the internal IIDs.
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ internal static class InternalComProbe
|
|||||||
if (interfaceName == "IWSLCSessionManager")
|
if (interfaceName == "IWSLCSessionManager")
|
||||||
{
|
{
|
||||||
bound = true;
|
bound = true;
|
||||||
|
RaiseImpersonation(candidate);
|
||||||
if (callThrough) CallThrough(candidate);
|
if (callThrough) CallThrough(candidate);
|
||||||
}
|
}
|
||||||
Marshal.Release(candidate);
|
Marshal.Release(candidate);
|
||||||
@@ -236,6 +237,32 @@ internal static class InternalComProbe
|
|||||||
ProbeSessionHandoff(proxy);
|
ProbeSessionHandoff(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grant the server permission to impersonate us on this proxy.
|
||||||
|
///
|
||||||
|
/// Found the hard way: `OpenSessionByName` failed `0x80070542`
|
||||||
|
/// (`HRESULT_FROM_WIN32(1346)` = `ERROR_BAD_IMPERSONATION_LEVEL`) on a session that
|
||||||
|
/// `ListSessions` had just listed by name. It is not a "missing" error at all — the service
|
||||||
|
/// impersonates the caller to resolve a **per-user** session, and a .NET COM client is handed
|
||||||
|
/// `RPC_C_IMP_LEVEL_IDENTIFY` by default, which lets the server check who we are but not act
|
||||||
|
/// as us. `GetVersion` and `ListSessions` never impersonate, which is exactly why those two
|
||||||
|
/// succeeded and made the failure look like a per-method capability gap.
|
||||||
|
///
|
||||||
|
/// `CoSetProxyBlanket` is the surgical fix — it applies to this proxy only and works after
|
||||||
|
/// marshalling has already happened. `nucleic-brokerd` can instead call `CoInitializeSecurity`
|
||||||
|
/// once at startup, before its first COM call, which covers every proxy it will ever hold;
|
||||||
|
/// that is the production shape, and it must come first or it fails `RPC_E_TOO_LATE`.
|
||||||
|
/// </summary>
|
||||||
|
private static void RaiseImpersonation(IntPtr proxy)
|
||||||
|
{
|
||||||
|
var hr = CoSetProxyBlanket(
|
||||||
|
proxy, RpcCAuthnDefault, RpcCAuthzDefault, ColeDefaultPrincipal,
|
||||||
|
RpcCAuthnLevelDefault, RpcCImpLevelImpersonate, ColeDefaultAuthinfo, EoacNone);
|
||||||
|
Console.WriteLine(hr >= 0
|
||||||
|
? " proxy blanket raised to RPC_C_IMP_LEVEL_IMPERSONATE"
|
||||||
|
: $" CoSetProxyBlanket failed: {Hresult(hr)} — per-user calls will likely fail 0x80070542");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The question that decides the SHAPE of D13's internal arm.
|
/// The question that decides the SHAPE of D13's internal arm.
|
||||||
///
|
///
|
||||||
@@ -277,8 +304,22 @@ internal static class InternalComProbe
|
|||||||
if (hr < 0)
|
if (hr < 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine($" failed: {Hresult(hr)}");
|
Console.WriteLine($" failed: {Hresult(hr)}");
|
||||||
Console.WriteLine(" (expected if no session of that name is running — create "
|
// Be specific about WHY, and never blame absence when the cause is security — an
|
||||||
+ "one first: --session --keep --internal-call)");
|
// earlier version printed "expected if no session of that name is running" directly
|
||||||
|
// under a ListSessions that had just printed that session by name.
|
||||||
|
Console.WriteLine((uint)hr switch
|
||||||
|
{
|
||||||
|
ErrorBadImpersonationLevel =>
|
||||||
|
" → NOT a missing session: the server could not impersonate us. If the "
|
||||||
|
+ "blanket was raised above and this still fails, the process needs "
|
||||||
|
+ "CoInitializeSecurity(RPC_C_IMP_LEVEL_IMPERSONATE) BEFORE its first COM call.",
|
||||||
|
0x8004060F =>
|
||||||
|
" → WSLC_E_SESSION_NOT_FOUND: no session of that name. Create one: "
|
||||||
|
+ "--session --keep --internal-call",
|
||||||
|
_ =>
|
||||||
|
" → unexpected; compare against the ListSessions output above, which "
|
||||||
|
+ "says whether the session actually exists.",
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +375,10 @@ internal static class InternalComProbe
|
|||||||
0x80004002 => "E_NOINTERFACE — the class does not implement it",
|
0x80004002 => "E_NOINTERFACE — the class does not implement it",
|
||||||
0x80070005 => "E_ACCESSDENIED — registered, but this token may not activate it",
|
0x80070005 => "E_ACCESSDENIED — registered, but this token may not activate it",
|
||||||
0x800401F0 => "CO_E_NOTINITIALIZED",
|
0x800401F0 => "CO_E_NOTINITIALIZED",
|
||||||
|
0x80070542 => "ERROR_BAD_IMPERSONATION_LEVEL — the server needs to impersonate the caller "
|
||||||
|
+ "and this proxy only grants IDENTIFY. A SECURITY error, not a missing object",
|
||||||
|
0x80010119 => "RPC_E_TOO_LATE — CoInitializeSecurity after the first COM call",
|
||||||
|
0x8004060F => "WSLC_E_SESSION_NOT_FOUND",
|
||||||
_ => $"0x{code:X8}",
|
_ => $"0x{code:X8}",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -358,6 +403,22 @@ internal static class InternalComProbe
|
|||||||
private const uint CoinitMultithreaded = 0;
|
private const uint CoinitMultithreaded = 0;
|
||||||
private const uint RpcEChangedMode = 0x80010106;
|
private const uint RpcEChangedMode = 0x80010106;
|
||||||
private const uint ENoInterface = 0x80004002;
|
private const uint ENoInterface = 0x80004002;
|
||||||
|
private const uint ErrorBadImpersonationLevel = 0x80070542;
|
||||||
|
|
||||||
|
// CoSetProxyBlanket's "keep the default" sentinels are -1, not 0 — passing 0 for the
|
||||||
|
// principal name means "no principal" rather than "default" and fails differently.
|
||||||
|
private const uint RpcCAuthnDefault = 0xFFFFFFFF;
|
||||||
|
private const uint RpcCAuthzDefault = 0xFFFFFFFF;
|
||||||
|
private const uint RpcCAuthnLevelDefault = 0;
|
||||||
|
private const uint RpcCImpLevelImpersonate = 3;
|
||||||
|
private const uint EoacNone = 0;
|
||||||
|
private static readonly IntPtr ColeDefaultPrincipal = new(-1);
|
||||||
|
private static readonly IntPtr ColeDefaultAuthinfo = new(-1);
|
||||||
|
|
||||||
|
[DllImport("ole32.dll")]
|
||||||
|
private static extern int CoSetProxyBlanket(
|
||||||
|
IntPtr proxy, uint authnService, uint authzService, IntPtr serverPrincipalName,
|
||||||
|
uint authnLevel, uint impersonationLevel, IntPtr authInfo, uint capabilities);
|
||||||
|
|
||||||
[DllImport("ole32.dll")]
|
[DllImport("ole32.dll")]
|
||||||
private static extern int CoInitializeEx(IntPtr reserved, uint coInit);
|
private static extern int CoInitializeEx(IntPtr reserved, uint coInit);
|
||||||
|
|||||||
Reference in New Issue
Block a user