From a62c65615a96b853651eaf066cbe7d22d74cf994 Mon Sep 17 00:00:00 2001 From: Yaya48 Date: Wed, 14 Oct 2026 00:20:22 +0200 Subject: [PATCH] IOS LLE: StarOS's panic words must not alias LoadBootstrapELF accepted __panic_state and __panic_code at the same address, or as two words sharing bytes: the code's store would then also set the guard, and nothing could tell the two apart. They must now be two non-overlapping words of the data segment, like the other checks, before any byte is written. BootstrapELFTest.PanicWordsMustNotAlias: the same word and three partial overlaps, all inside the data segment, are rejected; adjacent words are accepted. With the rule disabled, the test fails. (StarOS MMU-001 review of F1.) Co-Authored-By: Claude Opus 5.5 --- Source/Core/Core/IOS/Starlet/BootstrapELF.cpp | 9 +++++-- .../Core/IOS/Starlet/BootstrapELFTest.cpp | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp b/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp index fdbb27da7e..d4a2ad76b2 100644 --- a/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp +++ b/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp @@ -216,9 +216,14 @@ bool LoadBootstrapELF(std::span image, ARMBus& bus, BootstrapELFInfo* { return Fail(error, "bootstrap ELF is missing the trace or fault symbols"); } - // The panic words are optional, but both or neither, as words of the data segment. + // The panic words are optional, but both or neither, as two distinct words of the data segment: + // aliased, the code's store would also set the guard, and a probe could not tell them apart. + const bool panic_words_overlap = + static_cast(parsed.panic_state) < static_cast(parsed.panic_code) + 4 && + static_cast(parsed.panic_code) < static_cast(parsed.panic_state) + 4; if ((parsed.panic_state != 0) != (parsed.panic_code != 0) || - (parsed.panic_state != 0 && (!in_data(parsed.panic_state, 4) || !in_data(parsed.panic_code, 4)))) + (parsed.panic_state != 0 && (!in_data(parsed.panic_state, 4) || + !in_data(parsed.panic_code, 4) || panic_words_overlap))) { return Fail(error, "bootstrap ELF has an invalid panic symbol"); } diff --git a/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp b/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp index 919f2a1116..cc0b8b1b7a 100644 --- a/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp +++ b/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp @@ -281,5 +281,32 @@ TEST(BootstrapELFTest, PanicWordsAreOptionalButBothInData) EXPECT_TRUE(rejected.bytes.empty()); } } + +TEST(BootstrapELFTest, PanicWordsMustNotAlias) +{ + BootstrapELFInfo info; + std::string error; + // The same word, or two words sharing bytes, in either order; all inside the 0x420-byte data + // segment, so only the aliasing rule rejects them. + for (const auto [state, code] : + {std::pair{DATA_BASE + 4, DATA_BASE + 4}, std::pair{DATA_BASE + 4, DATA_BASE + 6}, + std::pair{DATA_BASE + 7, DATA_BASE + 4}, std::pair{DATA_BASE + 0x41c, DATA_BASE + 0x41a}}) + { + RecordingBus rejected; + EXPECT_FALSE(LoadBootstrapELF(MakePanicImage(state, code), rejected, &info, &error)) + << std::hex << state << ' ' << code; + EXPECT_EQ(error, "bootstrap ELF has an invalid panic symbol"); + EXPECT_TRUE(rejected.bytes.empty()); + } + // Adjacent words do not alias. + for (const auto [state, code] : + {std::pair{DATA_BASE + 4, DATA_BASE + 8}, std::pair{DATA_BASE + 8, DATA_BASE + 4}}) + { + RecordingBus bus; + EXPECT_TRUE(LoadBootstrapELF(MakePanicImage(state, code), bus, &info, &error)) << error; + EXPECT_EQ(info.panic_state, state); + EXPECT_EQ(info.panic_code, code); + } +} } // namespace } // namespace IOS::LLE