Skip to content

Commit 4e3ece5

Browse files
committed
Fix cross-context blob handle validation
Include a per-context id in encoded blob handles so identical slot and generation values from different contexts cannot collide. Add an x64 static assertion for the token layout and document that target assumption. Strengthen the wrong-context test so both contexts own live blobs before the invalid release attempt.
1 parent 7edb758 commit 4e3ece5

4 files changed

Lines changed: 71 additions & 18 deletions

File tree

source/runtime/AssetSuiteRuntimeState.cpp

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,44 @@
88

99
#include <fstream>
1010
#include <new>
11+
#include <atomic>
1112
#include <cstdint>
1213
#include <utility>
1314

1415
namespace
1516
{
16-
constexpr uintptr_t SLOT_INDEX_MASK = 0xffffffffu;
17+
static_assert(sizeof(uintptr_t) >= 8, "BlobHandle token encoding requires a 64-bit target.");
18+
19+
constexpr uint32_t SLOT_INDEX_BITS = 24;
20+
constexpr uint32_t GENERATION_BITS = 20;
21+
constexpr uint32_t CONTEXT_ID_BITS = 20;
22+
constexpr uintptr_t SLOT_INDEX_MASK = (uintptr_t{ 1 } << SLOT_INDEX_BITS) - 1u;
23+
constexpr uintptr_t GENERATION_MASK = (uintptr_t{ 1 } << GENERATION_BITS) - 1u;
24+
constexpr uintptr_t CONTEXT_ID_MASK = (uintptr_t{ 1 } << CONTEXT_ID_BITS) - 1u;
1725
constexpr uint32_t INITIAL_BLOB_GENERATION = 1;
26+
constexpr uint32_t INITIAL_BLOB_CONTEXT_ID = 1;
1827

19-
AssetSuite::BlobHandle EncodeBlobHandle(size_t slotIndex, uint32_t generation) noexcept
28+
std::atomic<uint32_t> nextBlobContextId = INITIAL_BLOB_CONTEXT_ID;
29+
30+
AssetSuite::BlobHandle EncodeBlobHandle(size_t slotIndex, uint32_t generation, uint32_t contextId) noexcept
2031
{
32+
if (slotIndex >= SLOT_INDEX_MASK || generation == 0 || contextId == 0)
33+
{
34+
return nullptr;
35+
}
36+
2137
const uintptr_t token =
22-
(static_cast<uintptr_t>(generation) << 32) |
38+
(static_cast<uintptr_t>(contextId) << (SLOT_INDEX_BITS + GENERATION_BITS)) |
39+
(static_cast<uintptr_t>(generation) << SLOT_INDEX_BITS) |
2340
(static_cast<uintptr_t>(slotIndex) + 1u);
2441
return reinterpret_cast<AssetSuite::BlobHandle>(token);
2542
}
2643

27-
bool DecodeBlobHandle(AssetSuite::BlobHandle handle, size_t& slotIndex, uint32_t& generation) noexcept
44+
bool DecodeBlobHandle(
45+
AssetSuite::BlobHandle handle,
46+
size_t& slotIndex,
47+
uint32_t& generation,
48+
uint32_t& contextId) noexcept
2849
{
2950
const uintptr_t token = reinterpret_cast<uintptr_t>(handle);
3051
const uintptr_t encodedSlotIndex = token & SLOT_INDEX_MASK;
@@ -33,8 +54,9 @@ namespace
3354
return false;
3455
}
3556

36-
generation = static_cast<uint32_t>(token >> 32);
37-
if (generation == 0)
57+
generation = static_cast<uint32_t>((token >> SLOT_INDEX_BITS) & GENERATION_MASK);
58+
contextId = static_cast<uint32_t>((token >> (SLOT_INDEX_BITS + GENERATION_BITS)) & CONTEXT_ID_MASK);
59+
if (generation == 0 || contextId == 0)
3860
{
3961
return false;
4062
}
@@ -43,9 +65,15 @@ namespace
4365
return true;
4466
}
4567

68+
uint32_t AllocateBlobContextId() noexcept
69+
{
70+
uint32_t contextId = nextBlobContextId.fetch_add(1, std::memory_order_relaxed) & static_cast<uint32_t>(CONTEXT_ID_MASK);
71+
return contextId == 0 ? INITIAL_BLOB_CONTEXT_ID : contextId;
72+
}
73+
4674
uint32_t NextBlobGeneration(uint32_t generation) noexcept
4775
{
48-
++generation;
76+
generation = (generation + 1u) & static_cast<uint32_t>(GENERATION_MASK);
4977
return generation == 0 ? INITIAL_BLOB_GENERATION : generation;
5078
}
5179
}
@@ -156,6 +184,11 @@ AssetSuite::ErrorCode AssetSuite::Internal::RuntimeState::FileLoader::LoadToMemo
156184
return ErrorCode::OK;
157185
}
158186

187+
AssetSuite::Internal::RuntimeState::BlobStorage::BlobStorage()
188+
: contextId(AllocateBlobContextId())
189+
{
190+
}
191+
159192
AssetSuite::BlobHandle AssetSuite::Internal::RuntimeState::BlobStorage::Create(Blob blob)
160193
{
161194
size_t slotIndex = 0;
@@ -171,7 +204,7 @@ AssetSuite::BlobHandle AssetSuite::Internal::RuntimeState::BlobStorage::Create(B
171204
}
172205

173206
slots[slotIndex].blob = std::make_unique<Blob>(std::move(blob));
174-
return EncodeBlobHandle(slotIndex, slots[slotIndex].generation);
207+
return EncodeBlobHandle(slotIndex, slots[slotIndex].generation, contextId);
175208
}
176209

177210
bool AssetSuite::Internal::RuntimeState::BlobStorage::Owns(BlobHandle blob) const noexcept
@@ -189,7 +222,13 @@ AssetSuite::Internal::RuntimeState::BlobStorage::Get(BlobHandle blob) const noex
189222
{
190223
size_t slotIndex = 0;
191224
uint32_t generation = 0;
192-
if (!DecodeBlobHandle(blob, slotIndex, generation))
225+
uint32_t decodedContextId = 0;
226+
if (!DecodeBlobHandle(blob, slotIndex, generation, decodedContextId))
227+
{
228+
return nullptr;
229+
}
230+
231+
if (decodedContextId != contextId)
193232
{
194233
return nullptr;
195234
}
@@ -212,7 +251,13 @@ AssetSuite::Result AssetSuite::Internal::RuntimeState::BlobStorage::Release(Blob
212251
{
213252
size_t slotIndex = 0;
214253
uint32_t generation = 0;
215-
if (!blob || !DecodeBlobHandle(*blob, slotIndex, generation))
254+
uint32_t decodedContextId = 0;
255+
if (!blob || !DecodeBlobHandle(*blob, slotIndex, generation, decodedContextId))
256+
{
257+
return Result::ErrorInvalidHandle;
258+
}
259+
260+
if (decodedContextId != contextId)
216261
{
217262
return Result::ErrorInvalidHandle;
218263
}

source/runtime/AssetSuiteRuntimeState.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <array>
4+
#include <cstdint>
45
#include <cstddef>
56
#include <filesystem>
67
#include <memory>
@@ -64,6 +65,8 @@ namespace AssetSuite::Internal
6465

6566
struct BlobStorage
6667
{
68+
BlobStorage();
69+
6770
BlobHandle Create(Blob blob);
6871
bool Owns(BlobHandle blob) const noexcept;
6972
bool IsLive(BlobHandle blob) const noexcept;
@@ -79,6 +82,7 @@ namespace AssetSuite::Internal
7982
uint32_t generation = 1;
8083
};
8184

85+
uint32_t contextId = 0;
8286
std::vector<Slot> slots;
8387
std::vector<size_t> freeSlots;
8488
};

source/runtime/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ The public SDK surface is guarded by `PublicHeaderCompile`, `PublicHeaderHygiene
3636

3737
`Internal::Blob` stores raw asset bytes in runtime-owned memory and copies minimal source metadata into the runtime object. The metadata currently tracks the original source path, source extension, and best-known public `AssetFormat` derived from the extension when possible.
3838

39-
`BlobHandle` is treated as an opaque slot/generation token by the runtime. The public type remains pointer-shaped for ABI opacity, but blob validation decodes the token and never dereferences the handle value directly.
39+
`BlobHandle` is treated as an opaque context/slot/generation token by the runtime. The public type remains pointer-shaped for ABI opacity, but blob validation decodes the token and never dereferences the handle value directly. This token encoding requires a 64-bit target and is guarded by a compile-time assertion.
4040

41-
`RuntimeState::BlobStorage` owns reusable blob slots for one context. `ReleaseBlob` clears the blob payload, advances the slot generation, returns the slot to the free list, and nulls the caller's handle. Copied stale handles are rejected by generation mismatch, and handles from another context are rejected because each context validates against only its own slot table.
41+
`RuntimeState::BlobStorage` owns reusable blob slots for one context. `ReleaseBlob` clears the blob payload, advances the slot generation, returns the slot to the free list, and nulls the caller's handle. Copied stale handles are rejected by generation mismatch, and handles from another context are rejected by context-id mismatch before slot lookup.
4242

4343
## Deferred Scope
4444

unit_tests/GeneralUnitTests.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,19 +458,23 @@ namespace GeneralUnitTests
458458
{
459459
AssetSuite::ContextHandle firstContext = nullptr;
460460
AssetSuite::ContextHandle secondContext = nullptr;
461-
AssetSuite::BlobHandle blob = nullptr;
461+
AssetSuite::BlobHandle firstBlob = nullptr;
462+
AssetSuite::BlobHandle secondBlob = nullptr;
462463
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::CreateContext(nullptr, &firstContext));
463464
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::CreateContext(nullptr, &secondContext));
464-
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::LoadFile(firstContext, "test_file.xyz", &blob));
465+
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::LoadFile(firstContext, "test_file.xyz", &firstBlob));
466+
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::LoadFile(secondContext, "test_file.xyz", &secondBlob));
467+
Assert::IsFalse(firstBlob == secondBlob);
465468

466-
const auto result = AssetSuite::ReleaseBlob(secondContext, &blob);
469+
const auto result = AssetSuite::ReleaseBlob(secondContext, &firstBlob);
467470

468471
Assert::AreEqual(true, AssetSuite::Result::ErrorInvalidHandle == result);
469-
Assert::IsNotNull(blob);
472+
Assert::IsNotNull(firstBlob);
470473
Assert::AreEqual(static_cast<size_t>(1), firstContext->Runtime().BlobStorage().LiveCount());
471-
Assert::AreEqual(static_cast<size_t>(0), secondContext->Runtime().BlobStorage().LiveCount());
474+
Assert::AreEqual(static_cast<size_t>(1), secondContext->Runtime().BlobStorage().LiveCount());
472475

473-
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::ReleaseBlob(firstContext, &blob));
476+
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::ReleaseBlob(secondContext, &secondBlob));
477+
Assert::AreEqual(true, AssetSuite::Result::Success == AssetSuite::ReleaseBlob(firstContext, &firstBlob));
474478
DestroyContextForCleanup(secondContext);
475479
DestroyContextForCleanup(firstContext);
476480
}

0 commit comments

Comments
 (0)