mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_State, MG_Test] (P7): bound program archive vector counts
This commit is contained in:
@@ -247,15 +247,16 @@ namespace MobileGL::MG_State::GLState {
|
||||
return true;
|
||||
}
|
||||
|
||||
// A COUNT IS CHECKED AGAINST THE BYTES THAT REMAIN BEFORE ANYTHING IS ALLOCATED. Every
|
||||
// element this codec writes costs at least one byte, so a count larger than the
|
||||
// remaining bytes cannot describe this stream - and refusing it here is what stops a
|
||||
// corrupt or truncated archive from turning into a multi-gigabyte resize before the
|
||||
// element loop notices it has run out.
|
||||
Bool TakeCount(ReadCursor& in, SizeT& count) {
|
||||
// A COUNT IS CHECKED AGAINST THE BYTES THAT REMAIN BEFORE ANYTHING IS ALLOCATED.
|
||||
// For resizable vectors, also charge sizeof(value_type) per element: even when a
|
||||
// serialized record is compact, its immediate vector reservation must not exceed the
|
||||
// remaining untrusted archive bytes. The division form avoids multiplying attacker
|
||||
// input and is safe on both 32-bit and 64-bit SizeT.
|
||||
Bool TakeCount(ReadCursor& in, SizeT& count, SizeT allocationBytesPerElement = 1) {
|
||||
Uint64 raw = 0;
|
||||
if (!TakeRaw(in, raw)) return false;
|
||||
if (raw > static_cast<Uint64>(in.Remaining())) {
|
||||
if (allocationBytesPerElement == 0 ||
|
||||
raw > static_cast<Uint64>(in.Remaining() / allocationBytesPerElement)) {
|
||||
in.Ok = false;
|
||||
return false;
|
||||
}
|
||||
@@ -304,7 +305,8 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
} else if constexpr (ArchiveVector<T>) {
|
||||
SizeT count = 0;
|
||||
if (!TakeCount(in, count)) return;
|
||||
using Element = typename T::value_type;
|
||||
if (!TakeCount(in, count, sizeof(Element))) return;
|
||||
value.clear();
|
||||
value.resize(count);
|
||||
for (auto& element : value) {
|
||||
|
||||
@@ -326,6 +326,31 @@ TEST(ProgramArtifactsCodec, ATruncatedStreamIsRefusedNotGuessed) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// A count is not permission to reserve a container larger than the bytes left in the
|
||||
// untrusted archive. This mutates the first LinkArtifacts vector count to the boundary value
|
||||
// that the former raw <= Remaining check accepted but sizeof(value_type) <= Remaining rejects.
|
||||
TEST(ProgramArtifactsCodec, AVectorCountCannotReservePastTheRemainingArchiveBytes) {
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Vector<Uint8> bytes;
|
||||
EncodeProgramArtifacts(LinkArtifacts{}, SpirvArtifacts{}, bytes);
|
||||
constexpr SizeT countOffset = sizeof(Uint32) + sizeof(Uint64);
|
||||
ASSERT_GT(bytes.size(), countOffset + sizeof(Uint64));
|
||||
const SizeT available = bytes.size() - countOffset - sizeof(Uint64);
|
||||
const Uint64 forgedCount = static_cast<Uint64>(available / sizeof(UniformReflection)) + 1;
|
||||
ASSERT_LT(forgedCount, static_cast<Uint64>(available))
|
||||
<< "fixture must pass the old count <= remaining check";
|
||||
std::memcpy(bytes.data() + countOffset, &forgedCount, sizeof(forgedCount));
|
||||
|
||||
LinkArtifacts link;
|
||||
SpirvArtifacts spirv;
|
||||
EXPECT_FALSE(DecodeProgramArtifacts(bytes.data(), bytes.size(), link, spirv));
|
||||
EXPECT_TRUE(link.uniformReflection.empty());
|
||||
EXPECT_TRUE(spirv.generatedSpirv.empty());
|
||||
#else
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: the archive codec is push-only";
|
||||
#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) {
|
||||
|
||||
Reference in New Issue
Block a user