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
guessed names, it would not compile, and fixing it blind is the mistake this whole approach
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
`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
+62
View File
@@ -334,6 +334,7 @@ internal static class InternalComProbe
Console.WriteLine(" → a re-adopted session can be handed to "
+ "Session.FromAbi() and driven by the EXISTING compat facade code.");
Console.WriteLine(" → D13's internal arm shrinks to: find, then hand off.");
ProbeFromAbiOwnership(projected);
Marshal.Release(projected);
}
else
@@ -352,6 +353,67 @@ internal static class InternalComProbe
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)
{
var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);