[MG_Remote] (P6/hs): the build stamp that was silently empty, and two asymmetries the handshake never checked

CONTRACT-P6 4.2 and 4.3. Three defects, all of them things the handshake
believed it was checking and was not.

1. THE BUILD STAMP WAS "" AND NOTHING SAID SO. CMakeLists took the git hash
   through an execute_process with NO RESULT_VARIABLE, so a failed
   `git rev-parse` yielded the empty string. Verified live rather than
   inferred: this worktree's `.git` is a file holding a WINDOWS path, WSL's
   git answers `fatal: not a git repository`, result 128 - and every WSL build
   mixed "" into its ABI fingerprint. Two builds from different commits then
   agreed exactly, which is the one thing the stamp exists to prevent. The
   already-failing SessionHandshakeTest was reporting this the whole time.

   The fix may NOT touch GIT_COMMIT_HASH_SHORT: it is baked into
   glGetString(GL_VERSION), so redefining it changes an application-visible GL
   string and the pull build's .rodata - a behaviour leak and a G1 break at
   once. MGGitHash.h.in gains a SECOND pair instead, consumed only by the
   fingerprint, plus -DMOBILEGL_BUILD_STAMP for trees git cannot read (CI has
   ${GITHUB_SHA} in hand). A stampless build is now a configure-time WARNING
   naming the consequence, not silence.

   MOBILEGL_BUILD_STAMP_PRESENT IS MIXED AS ITS OWN FIELD. "this build cannot
   name its commit" and "this build's commit is the empty string" are different
   claims, and collapsing them is how every stampless build in the world agreed
   with every other. The test asserts the flag moves the fingerprint in EITHER
   build state, and the `BuildStamp = ""` perturbation is now guarded - in a
   stampless build production's value already IS "", so that assertion was
   passing while proving nothing.

2. THE CLIENT NEVER READ abiMajor/abiMinor. Hello has carried them since P5
   and the SERVER checks them; the client compared only the fingerprint. The
   fingerprint mixes AbiVersion so a mismatch would fire anyway - but as
   "struct shapes", which sends the reader looking for a layout change that is
   not there. Under spawn the peer is a SEPARATE IMAGE the operator chose with
   MOBILEGL_IPC_SERVER_PATH, so this is exactly the failure that needs its own
   sentence. Both handshake sites call one helper; there are two sites, and
   only one of them was reachable before `sm`, which is how the gap survived.

3. Hello::pid WAS 0 AND Welcome::serverPid ECHOED IT. So both were always 0 and
   neither named anything, while §9.5's arm-proof gate wants exactly the child
   pid. Both now carry real ids, and the pair is the cheapest arm proof in the
   protocol: under spawn they MUST differ, and a same-process session cannot
   produce two different values. The client keeps what the server STATED -
   local launcher knowledge is a different fact from one that crossed the wire,
   and only the second can be asserted from the server's own mouth.

R-16 both ways: revert serverPid to echoing hello->pid() and the new test reds
on both halves, naming 4.3. G1 re-measured after the CMake and header changes:
0 symbol diff, .text unchanged at 0xa52203.

Unit suite goes from two long-standing failures to one - the remaining
PipeCatalogue.FrontendNeverTakesAnApplierAddress is an environmental path
problem in this tree, unrelated. All six OpenRA arms still green.
This commit is contained in:
2026-09-21 19:21:43 -04:00
parent 8b13d8a84c
commit 90f9a79058
10 changed files with 249 additions and 9 deletions
+28 -1
View File
@@ -24,8 +24,27 @@
#include <cstring>
#include <vector>
// CONTRACT-P6 4.3: the one value in the handshake that a same-process session cannot fake.
#if defined(_WIN32)
#include <process.h>
#else
#include <unistd.h>
#endif
namespace MobileGL::MG_Remote::Server {
namespace {
// File-local on purpose: two callers, both handshake facts. A process-id helper on a
// public header invites uses that are not.
std::uint32_t SelfProcessId() {
#if defined(_WIN32)
return static_cast<std::uint32_t>(::_getpid());
#else
return static_cast<std::uint32_t>(::getpid());
#endif
}
} // namespace
// THE THREE FLAG-SPACE COLLISIONS, AS TRIPWIRES RATHER THAN AS A COMMENT.
//
// MGPipeCallFlags (MG_Pipe/MGPipe.h:42-54) and RingRecordFlags (Ring.h) are separate spaces
@@ -531,7 +550,15 @@ namespace MobileGL::MG_Remote::Server {
auto stamp = builder.CreateString(GIT_COMMIT_HASH_SHORT);
auto welcome = ::MobileGL::Wire::CreateWelcome(
builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR,
static_cast<Uint32>(hello->pid()), cmd, stage, reply, event, stamp, ourFingerprint);
// CONTRACT-P6 4.3: THE SERVER'S OWN PID, not an echo of the client's.
//
// This field echoed hello->pid() - which is itself hard-coded 0 - so serverPid
// was ALWAYS 0 and named nothing. §9.5's arm-proof gate wants the child pid and
// this is its natural carrier: under spawn it is the one value in the handshake
// that a same-process session cannot produce, because there the two pids are
// equal by construction.
static_cast<Uint32>(SelfProcessId()),
cmd, stage, reply, event, stamp, ourFingerprint);
auto root = ::MobileGL::Wire::CreateCtrlEnvelope(
builder, ::MobileGL::Wire::CtrlMsg::Welcome, welcome.Union());
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root);