From 3ef5c61e9669fb4546e69c21357a91576cb62c66 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 23 Sep 2026 12:20:22 -0400 Subject: [PATCH] [MG_Backend, MG_Test] (P7): a PH-5 unit control that discriminates the element charge, @Ph-declined on PH-2's eight Fatal sites, and the mip-shadow comment made true --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 8 +-- MobileGL/MG_Backend/DirectGLES/Managers.h | 4 +- MobileGL/MG_Backend/DirectGLES/SlotTables.h | 4 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 4 +- .../Program/ProgramArtifactsCodecTest.cpp | 61 +++++++++++++++++++ 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 8836cae2..6f32888a 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3012,7 +3012,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // stops being twinned is the failure mode the refusal exists to replace. if (res.Slot >= BackendBufferResourceTable::kMaxHandleSlot) { #if MOBILEGL_BUILD_DISAGGREGATED - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - " "buffer twin request named slot %u, past this table's %u bound", @@ -3027,7 +3027,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Uint32 liveGen = g_backendBufferResources.LiveGenAt(res.Slot); if (liveGen != 0 && liveGen > res.Gen) { #if MOBILEGL_BUILD_DISAGGREGATED - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - " "buffer twin request named generation %u at slot %u, behind live generation %u", @@ -4348,7 +4348,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (MG_Pipe::MGPipeHandleIsNull(handle)) return false; if (handle.Slot >= Registry::SlotTable::kMaxHandleSlot) { #if MOBILEGL_BUILD_DISAGGREGATED - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - " "%s handle slot %u is past this table's %u bound", @@ -4363,7 +4363,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Uint32 liveGen = registry.LiveGenAt(handle.Slot); if (liveGen != 0 && liveGen > handle.Gen) { #if MOBILEGL_BUILD_DISAGGREGATED - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - " "%s handle {%u, %u} names generation behind live generation %u", diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 702f545b..f31b3a95 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -481,7 +481,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr; if (handle.Slot >= SlotTable::kMaxHandleSlot) { #if MOBILEGL_BUILD_DISAGGREGATED - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - " "GetOrCreateByHandle named slot %u, past this table's %u bound", @@ -493,7 +493,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const Uint32 liveGen = m_slotTable.LiveGenAt(handle.Slot); if (liveGen != 0 && liveGen > handle.Gen) { #if MOBILEGL_BUILD_DISAGGREGATED - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - " "GetOrCreateByHandle named generation %u at slot %u, behind live generation %u", diff --git a/MobileGL/MG_Backend/DirectGLES/SlotTables.h b/MobileGL/MG_Backend/DirectGLES/SlotTables.h index d61b9cff..2290c68b 100644 --- a/MobileGL/MG_Backend/DirectGLES/SlotTables.h +++ b/MobileGL/MG_Backend/DirectGLES/SlotTables.h @@ -351,7 +351,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #if MOBILEGL_BUILD_DISAGGREGATED // PH-2: handle.Slot is peer supplied on the disaggregated arm. Release builds // must publish a named protocol fault instead of silently returning m_nullTwin. - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - " "GetOrCreate(handle) named slot %u, past this table's %u bound", @@ -385,7 +385,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #if MOBILEGL_BUILD_DISAGGREGATED // PH-2: a stale peer generation must not silently shed the incumbent twin in a // release server. The funnel names the exact identity fault and notifies the peer. - MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12 MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - " "GetOrCreate(handle) named generation %u at slot %u, behind live generation %u", diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index adcd622f..8721ee30 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1692,8 +1692,8 @@ void main() { const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForTwinAddress(&resource); for (const auto uploadTarget : uploadTargets) { for (Uint32 level = baseMipLevel + 1; level < requiredMipLevelCount; ++level) { - // A level the shadow already tracks (an adopted base chain) keeps its bytes; - // the generation made the GPU newer than either, which the mark says. + // Bytes the shadow held for a generated level go: the declaration below moves its + // format and byte bound (NoteLevelDefined clears them); the GPU copy is newer. // PH-4: a twin-address key never receives a staged run (no Adopt names one), // so these generated levels are declared BYTELESS - an Unknown format gives a // zero byte bound and skips the Tex2D device-limit rule the defaults would diff --git a/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp b/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp index e223e6f8..c777650d 100644 --- a/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp @@ -32,7 +32,15 @@ // stays name-for-name identical between the pull and the push trees. #include +#include #include +#include +#include + +#if !defined(_WIN32) +#include +#include +#endif #include "Includes.h" #if MOBILEGL_PIPE_PUSH @@ -351,6 +359,59 @@ TEST(ProgramArtifactsCodec, AVectorCountCannotReservePastTheRemainingArchiveByte #endif } +// PH-5's DISCRIMINATING control (P7 F2 latch). The case above cannot fail on PH-5's account: its +// count is one element past PH-5's bound, and with TakeCount put back to the old one-byte-per- +// element charge the same count is admitted, resize() is tiny, and the element reads run out of +// bytes - DecodeProgramArtifacts answers false either way (red-once: it stays green). What PH-5 +// actually changed is the ALLOCATION a count may cause before the reads, so this case forges the +// count BETWEEN the two bounds - `count == Remaining()`, in an archive padded to 7 MiB - and +// decodes it in a forked child whose address space has 64 MiB of headroom. PH-5 refuses before +// the resize and the child exits 0; the old bound resizes to Remaining() elements of +// sizeof(UniformReflection) each, far past the headroom, and the child dies of std::bad_alloc. +// The same shape crosses the wire in PeerLatchTest's D11ArchiveVectorCount row. +#if MOBILEGL_PIPE_PUSH && !defined(_WIN32) +namespace { + [[noreturn]] void DecodeACountOnlyTheByteBoundAdmitsUnderACapAndExit() { + Vector bytes; + EncodeProgramArtifacts(LinkArtifacts{}, SpirvArtifacts{}, bytes); + constexpr SizeT countOffset = sizeof(Uint32) + sizeof(Uint64); + bytes.resize(SizeT{7} << 20, 0); + const Uint64 remaining = bytes.size() - countOffset - sizeof(Uint64); + std::memcpy(bytes.data() + countOffset, &remaining, sizeof(remaining)); + constexpr Uint64 kHeadroom = Uint64{64} << 20; + if (remaining * sizeof(UniformReflection) < 4 * kHeadroom) ::_exit(64); // the case would prove nothing + Uint64 vmKb = 0; + { + std::ifstream status("/proc/self/status"); + std::string line; + while (std::getline(status, line)) { + if (line.rfind("VmSize:", 0) == 0) vmKb = std::strtoull(line.c_str() + 7, nullptr, 10); + } + } + if (vmKb == 0) ::_exit(65); + rlimit cap{}; + if (::getrlimit(RLIMIT_AS, &cap) != 0) ::_exit(66); + cap.rlim_cur = static_cast(vmKb * 1024 + kHeadroom); + if (::setrlimit(RLIMIT_AS, &cap) != 0) ::_exit(67); + LinkArtifacts link; + SpirvArtifacts spirv; + const Bool decoded = DecodeProgramArtifacts(bytes.data(), bytes.size(), link, spirv); + ::_exit(!decoded && link.uniformReflection.empty() ? 0 : 3); + } +} // namespace +#endif + +TEST(ProgramArtifactsCodec, ACountOnlyTheByteBoundAdmitsIsRefusedBeforeItsResize) { +#if MOBILEGL_PIPE_PUSH && !defined(_WIN32) + EXPECT_EXIT(DecodeACountOnlyTheByteBoundAdmitsUnderACapAndExit(), ::testing::ExitedWithCode(0), ".*") + << "std::bad_alloc escaping the child is the resize PH-5 exists to refuse: the vector count was " + "charged one byte per element instead of sizeof(value_type); exit 3 is a decode that did not " + "refuse; 64+ is the fixture"; +#else + GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off (the archive codec is push-only), or no RLIMIT_AS / fork here"; +#endif +} + // Negative control 2: both the version and schema word must refuse mismatches. // Monolith v1 retains the old native-size echo in that second word. TEST(ProgramArtifactsCodec, AVersionMismatchIsRefused) {