Merge nucleic/lucid-river-toad-6efj into dev

This commit is contained in:
2026-07-29 03:46:59 -07:00
parent 7d222a1535
commit 71157c0d6e
2 changed files with 68 additions and 0 deletions
+6
View File
@@ -180,6 +180,12 @@ for, and nothing above it should move. These three are different:
mounted vs. in-VM). Deliberately held back until `WslcApiDump` has run: written now, against mounted vs. in-VM). Deliberately held back until `WslcApiDump` has run: written now, against
guessed names, it would not compile, and fixing it blind is the mistake this whole approach guessed names, it would not compile, and fixing it blind is the mistake this whole approach
exists to avoid. exists to avoid.
- **The internal arm itself** (`WslcInternal.cs` in the broker, not a spike) — now fully
de-risked by `--internal-call`: entry point, vtable, impersonation requirement and the
session→compat handoff are all confirmed on hardware. Shape is *find, then hand off*:
`OpenSessionByName` / `ListContainers` / `OpenContainer`, each result QI'd onto its compat
interface and passed to `FromAbi()`, after which the existing facade code drives it. The
container-level QI is the one step still unobserved — confirm it in `WslcSpike` below.
- **`GatewaySpike`** — M1 (b), and the control-plane spike that actually matters now: can a - **`GatewaySpike`** — M1 (b), and the control-plane spike that actually matters now: can a
`Bridged` wslc container reach the host at the `vEthernet (WSL)` address `WslcFacade` returns, `Bridged` wslc container reach the host at the `vEthernet (WSL)` address `WslcFacade` returns,
under the **default** Windows firewall, and does `control-bridge.js` complete an MCP round trip under the **default** Windows firewall, and does `control-bridge.js` complete an MCP round trip
+62
View File
@@ -334,6 +334,7 @@ internal static class InternalComProbe
Console.WriteLine(" → a re-adopted session can be handed to " Console.WriteLine(" → a re-adopted session can be handed to "
+ "Session.FromAbi() and driven by the EXISTING compat facade code."); + "Session.FromAbi() and driven by the EXISTING compat facade code.");
Console.WriteLine(" → D13's internal arm shrinks to: find, then hand off."); Console.WriteLine(" → D13's internal arm shrinks to: find, then hand off.");
ProbeFromAbiOwnership(projected);
Marshal.Release(projected); Marshal.Release(projected);
} }
else else
@@ -352,6 +353,67 @@ internal static class InternalComProbe
private static string? sessionName; private static string? sessionName;
/// <summary>
/// Does <c>Session.FromAbi(ptr)</c> take a reference, or borrow the caller's?
///
/// This is not a detail — it is the difference between a leak and a use-after-free, and the
/// facade will call it on every reattach. `QueryInterface` already handed us one reference;
/// if `FromAbi` AddRefs as well we must `Release` ours, and if it merely wraps the pointer we
/// must NOT. Neither CsWinRT's docs nor the IDL say which.
///
/// Measured rather than assumed, by the only reliable means COM offers: `AddRef`/`Release`
/// return the new count, so an AddRef/Release pair straddling the call reads the delta.
/// The absolute numbers are meaningless (proxies keep their own counts); the DIFFERENCE is
/// the answer.
/// </summary>
private static void ProbeFromAbiOwnership(IntPtr sessionPtr)
{
Console.WriteLine();
Console.WriteLine(" FromAbi ownership (leak vs. use-after-free):");
try
{
// Baseline: AddRef then Release, reading the count at the peak.
var before = Marshal.AddRef(sessionPtr);
Marshal.Release(sessionPtr);
var projection = global::Microsoft.WSL.Containers.Session.FromAbi(sessionPtr);
if (projection is null)
{
Console.WriteLine(" FromAbi returned null — cannot judge");
return;
}
var after = Marshal.AddRef(sessionPtr);
Marshal.Release(sessionPtr);
Console.WriteLine($" refcount {before} → {after} across FromAbi()");
Console.WriteLine(after > before
? " → FromAbi ADDS a reference. The facade must Release its QI reference "
+ "after handing the pointer over, or every reattach leaks the session."
: " → FromAbi BORROWS the pointer. The facade must NOT Release its QI "
+ "reference — the projection depends on it staying alive.");
// Prove the projection is actually usable, not just constructed: GetImages() is the
// cheapest read on Session and mutates nothing. If this works, a re-adopted session
// really is a first-class compat Session.
try
{
var images = projection.GetImages();
Console.WriteLine($" projection.GetImages() = {images.Count} image(s) "
+ "— the re-adopted session is FULLY USABLE through the compat projection");
}
catch (Exception e)
{
Console.WriteLine($" projection.GetImages() threw {e.GetType().Name}: "
+ $"{e.Message} — it projects but does not work; investigate before relying on it");
}
}
catch (Exception e)
{
Console.WriteLine($" FromAbi threw {e.GetType().Name}: {e.Message}");
}
}
private static IEnumerable<string> Wrap(string text) private static IEnumerable<string> Wrap(string text)
{ {
var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries); var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);