Merge nucleic/lucid-river-toad-6efj into dev
This commit is contained in:
@@ -132,6 +132,7 @@ internal static class InternalComProbe
|
||||
if (interfaceName == "IWSLCSessionManager")
|
||||
{
|
||||
bound = true;
|
||||
RaiseImpersonation(candidate);
|
||||
if (callThrough) CallThrough(candidate);
|
||||
}
|
||||
Marshal.Release(candidate);
|
||||
@@ -236,6 +237,32 @@ internal static class InternalComProbe
|
||||
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>
|
||||
/// The question that decides the SHAPE of D13's internal arm.
|
||||
///
|
||||
@@ -277,8 +304,22 @@ internal static class InternalComProbe
|
||||
if (hr < 0)
|
||||
{
|
||||
Console.WriteLine($" failed: {Hresult(hr)}");
|
||||
Console.WriteLine(" (expected if no session of that name is running — create "
|
||||
+ "one first: --session --keep --internal-call)");
|
||||
// Be specific about WHY, and never blame absence when the cause is security — an
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -334,6 +375,10 @@ internal static class InternalComProbe
|
||||
0x80004002 => "E_NOINTERFACE — the class does not implement it",
|
||||
0x80070005 => "E_ACCESSDENIED — registered, but this token may not activate it",
|
||||
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}",
|
||||
};
|
||||
|
||||
@@ -358,6 +403,22 @@ internal static class InternalComProbe
|
||||
private const uint CoinitMultithreaded = 0;
|
||||
private const uint RpcEChangedMode = 0x80010106;
|
||||
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")]
|
||||
private static extern int CoInitializeEx(IntPtr reserved, uint coInit);
|
||||
|
||||
Reference in New Issue
Block a user