Add initial support for pre-shared globals. (#283)
This provides a mechanism to statically define globals that can be
shared between compartments statically. As with MMIO imports, these can
be imported with restricted permissions.
They currently show up in the audit log as MMIO imports. This is not
ideal, we should plumb their names through into the audit log so that
cheriot-audit can check that all references to a pre-shared object refer
to exactly one object.
Fixes #282
diff --git a/sdk/core/allocator/alloc.h b/sdk/core/allocator/alloc.h
index 20ba2da..010d57f 100644
--- a/sdk/core/allocator/alloc.h
+++ b/sdk/core/allocator/alloc.h
@@ -1030,7 +1030,7 @@
*/
[[nodiscard]] __always_inline auto hazard_list_begin()
{
- auto *lockWord{MMIO_CAPABILITY(uint32_t, allocator_epoch)};
+ auto *lockWord{SHARED_OBJECT(uint32_t, allocator_epoch)};
uint32_t epoch = *lockWord >> 16;
Debug::Invariant(
(epoch & 1) == 0,
@@ -1280,8 +1280,8 @@
{
// It is now safe to walk the hazard list.
Capability<void *> hazards =
- const_cast<void **>(MMIO_CAPABILITY_WITH_PERMISSIONS(
- void *, hazard_pointers, true, true, true, false));
+ const_cast<void **>(SHARED_OBJECT_WITH_PERMISSIONS(
+ void *, allocator_hazard_pointers, true, true, true, false));
size_t pointers = hazards.length() / sizeof(void *);
for (size_t i = 0; i < pointers; i++)
{
diff --git a/sdk/core/allocator/main.cc b/sdk/core/allocator/main.cc
index 75743ba..00f7989 100644
--- a/sdk/core/allocator/main.cc
+++ b/sdk/core/allocator/main.cc
@@ -88,8 +88,9 @@
Capability m{tbase.cast<MState>()};
size_t hazardQuarantineSize =
- Capability{MMIO_CAPABILITY_WITH_PERMISSIONS(
- void *, hazard_pointers, true, true, true, false)}
+ Capability{
+ SHARED_OBJECT_WITH_PERMISSIONS(
+ void *, allocator_hazard_pointers, true, true, true, false)}
.length();
m.bounds() = sizeof(*m);
diff --git a/sdk/core/loader/boot.cc b/sdk/core/loader/boot.cc
index 16a5ee6..11cb2c2 100644
--- a/sdk/core/loader/boot.cc
+++ b/sdk/core/loader/boot.cc
@@ -471,22 +471,18 @@
Debug::Invariant(
((entry.address >= LA_ABS(__mmio_region_start)) &&
(entry.address + entry.size() <= LA_ABS(__mmio_region_end))) ||
- ((entry.address == LA_ABS(__export_mem_allocator_epoch)) &&
- (entry.address + entry.size() ==
- LA_ABS(__export_mem_allocator_epoch_end))) ||
- ((entry.address == LA_ABS(__export_mem_hazard_pointers)) &&
- (entry.address + entry.size() ==
- LA_ABS(__export_mem_hazard_pointers_end))),
- "{}--{} is not in the MMIO range ({}--{}) or the hazard pointer "
- "range ({}--{}) or the allocator epoch range ({}--{})",
+ ((entry.address >= LA_ABS(__shared_objects_start)) &&
+ (entry.address + entry.size() <=
+ LA_ABS(__shared_objects_end))),
+ "{}--{} is not in the MMIO range ({}--{}) or the shared object "
+ "range ({}--{})",
entry.address,
entry.address + entry.size(),
LA_ABS(__mmio_region_start),
LA_ABS(__mmio_region_end),
- LA_ABS(__export_mem_hazard_pointers),
- LA_ABS(__export_mem_hazard_pointers_end),
- LA_ABS(__export_mem_allocator_epoch),
- LA_ABS(__export_mem_allocator_epoch_end));
+ LA_ABS(__shared_objects_start),
+ LA_ABS(__shared_objects_end));
+
auto ret = build(entry.address, entry.size());
// Remove any permissions that shouldn't be held here.
ret.permissions() &= entry.permissions();
@@ -782,9 +778,9 @@
Root::Type::RWGlobal,
PermissionSet{Permission::Store,
Permission::LoadStoreCapability}>(
- LA_ABS(__export_mem_hazard_pointers),
- LA_ABS(__export_mem_hazard_pointers_end) -
- LA_ABS(__export_mem_hazard_pointers));
+ LA_ABS(__cheriot_shared_object_allocator_hazard_pointers),
+ LA_ABS(__cheriot_shared_object_allocator_hazard_pointers_end) -
+ LA_ABS(__cheriot_shared_object_allocator_hazard_pointers));
// Space per thread for hazard pointers.
static constexpr size_t HazardPointerSpace =
HazardPointersPerThread * sizeof(void *);
diff --git a/sdk/firmware.ldscript.in b/sdk/firmware.ldscript.in
index 82d57b3..9fc9a29 100644
--- a/sdk/firmware.ldscript.in
+++ b/sdk/firmware.ldscript.in
@@ -107,27 +107,23 @@
}
.allocator_globals_end = .;
- . = ALIGN(8);
- __export_mem_hazard_pointers = .;
- # Two hazard pointers per thread
- . += @thread_count@ * 8 * 2;
- __export_mem_hazard_pointers_end = .;
- # 32-bit counter for allocator epochs.
- __export_mem_allocator_epoch = .;
- . += 4;
- __export_mem_allocator_epoch_end = .;
-
@software_revoker_globals@
@gdc_ld@
+ __compart_cgps_end = .;
+
.sealed_objects :
{
@sealed_objects@
}
- __compart_cgps_end = ALIGN(64);
+ __shared_objects_start = .;
+ @shared_objects@
+ __shared_objects_end = .;
+
+ . = ALIGN(64);
# Everything after this point can be discarded after the loader has
# finished.
diff --git a/sdk/include/compartment-macros.h b/sdk/include/compartment-macros.h
index 8f20940..ef0a024 100644
--- a/sdk/include/compartment-macros.h
+++ b/sdk/include/compartment-macros.h
@@ -6,26 +6,27 @@
#include <stdbool.h>
/**
- * Helper macro, should not be used directly.
+ * Helper macro for MMIO and pre-shared object imports, should not be used
+ * directly.
*/
-#define MMIO_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \
- name, \
- mangledName, \
- permitLoad, \
- permitStore, \
- permitLoadStoreCapabilities, \
- permitLoadMutable) \
+#define IMPORT_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \
+ name, \
+ prefix, \
+ mangledName, \
+ permitLoad, \
+ permitStore, \
+ permitLoadStoreCapabilities, \
+ permitLoadMutable) \
({ \
- volatile type *ret; /* NOLINT(bugprone-macro-parentheses) */ \
+ type *ret; /* NOLINT(bugprone-macro-parentheses) */ \
__asm(".ifndef " mangledName "\n" \
" .type " mangledName ",@object\n" \
" .section .compartment_imports." #name \
",\"awG\",@progbits," #name ",comdat\n" \
" .globl " mangledName "\n" \
" .p2align 3\n" mangledName ":\n" \
- " .word __export_mem_" #name "\n" \
- " .word __export_mem_" #name "_end - __export_mem_" #name \
- " + %c1\n" \
+ " .word " #prefix #name "\n" \
+ " .word " #prefix #name "_end - " #prefix #name " + %c1\n" \
" .size " mangledName ", 8\n" \
" .previous\n" \
".endif\n" \
@@ -42,6 +43,25 @@
})
/**
+ * Helper macro, should not be used directly.
+ */
+#define MMIO_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \
+ name, \
+ mangledName, \
+ permitLoad, \
+ permitStore, \
+ permitLoadStoreCapabilities, \
+ permitLoadMutable) \
+ IMPORT_CAPABILITY_WITH_PERMISSIONS_HELPER(type, \
+ name, \
+ __export_mem_, \
+ mangledName, \
+ permitLoad, \
+ permitStore, \
+ permitLoadStoreCapabilities, \
+ permitLoadMutable)
+
+/**
* Provide a capability of the type `volatile type *` referring to the MMIO
* region exported in the linker script with `name` as its name. This macro
* can be used only in code (it cannot be used to initialise a global).
@@ -57,7 +77,7 @@
permitLoadStoreCapabilities, \
permitLoadMutable) \
MMIO_CAPABILITY_WITH_PERMISSIONS_HELPER( \
- type, \
+ volatile type, /* NOLINT(bugprone-macro-parentheses) */ \
name, \
"__import_mem_" #name "_" #permitLoad "_" #permitStore \
"_" #permitLoadStoreCapabilities "_" #permitLoadMutable, \
@@ -79,6 +99,44 @@
MMIO_CAPABILITY_WITH_PERMISSIONS(type, name, true, true, false, false)
/**
+ * Provide a capability of the type `type *` referring to the pre-shared object
+ * with `name` as its name. This macro can be used only in code (it cannot be
+ * used to initialise a global).
+ *
+ * The last arguments specify the set of permissions that this capability
+ * holds. Pre-shared objects are always global and without store local. They
+ * may optionally omit additional permissions.
+ */
+#define SHARED_OBJECT_WITH_PERMISSIONS(type, \
+ name, \
+ permitLoad, \
+ permitStore, \
+ permitLoadStoreCapabilities, \
+ permitLoadMutable) \
+ IMPORT_CAPABILITY_WITH_PERMISSIONS_HELPER( \
+ type, /* NOLINT(bugprone-macro-parentheses) */ \
+ name, \
+ __cheriot_shared_object_, \
+ "__import_cheriot_shared_object_" #name "_" #permitLoad "_" #permitStore \
+ "_" #permitLoadStoreCapabilities "_" #permitLoadMutable, \
+ permitLoad, \
+ permitStore, \
+ permitLoadStoreCapabilities, \
+ permitLoadMutable)
+
+/**
+ * Provide a capability of the type `type *` referring to the pre-shared object
+ * with `name` as its name. This macro can be used only in code (it cannot be
+ * used to initialise a global).
+ *
+ * Pre-shared object capabilities produced by this macro have load, store,
+ * load-mutable, and load/store-capability permissions. To define a reduced
+ * set of permissions use `SHARED_OBJECT_WITH_PERMISSIONS`.
+ */
+#define SHARED_OBJECT(type, name) \
+ SHARED_OBJECT_WITH_PERMISSIONS(type, name, true, true, true, true)
+
+/**
* Macro to test whether a device with a specific name exists in the board
* definition for the current target.
*/
diff --git a/sdk/lib/compartment_helpers/claim_fast.cc b/sdk/lib/compartment_helpers/claim_fast.cc
index 6181f3a..bf87ae4 100644
--- a/sdk/lib/compartment_helpers/claim_fast.cc
+++ b/sdk/lib/compartment_helpers/claim_fast.cc
@@ -11,7 +11,7 @@
{
void **hazards = switcher_thread_hazard_slots();
auto *epochCounter{const_cast<
- cheriot::atomic<uint32_t> *>(MMIO_CAPABILITY_WITH_PERMISSIONS(
+ cheriot::atomic<uint32_t> *>(SHARED_OBJECT_WITH_PERMISSIONS(
cheriot::atomic<uint32_t>, allocator_epoch, true, false, false, false))};
uint32_t epoch = epochCounter->load();
int values = 2;
diff --git a/sdk/xmake.lua b/sdk/xmake.lua
index 96b03cd..7170625 100644
--- a/sdk/xmake.lua
+++ b/sdk/xmake.lua
@@ -683,6 +683,42 @@
end
end)
+ local shared_objects = {
+ -- 32-bit counter for the hazard-pointer epoch.
+ allocator_epoch = 4,
+ -- Two hazard pointers per thread.
+ allocator_hazard_pointers = #(threads) * 8 * 2
+ }
+ visit_all_dependencies(function (target)
+ local globals = target:values("shared_objects")
+ if globals then
+ for name, size in pairs(globals) do
+ if not (name == "__wrap_locked__") then
+ if shared_objects[global] and (not (shared_objects[global] == size)) then
+ raise("Global " .. global .. " is declared with different sizes.")
+ end
+ shared_objects[name] = size
+ end
+ end
+ end
+ end)
+ -- TODO: We should sort pre-shared globals by size to minimise padding.
+ -- Each global is emitted as a separate section so that we can use
+ -- CAPALIGN and let the linker insert the required padding.
+ local shared_objects_template =
+ "\n\t\t. = ALIGN(MIN(${size}, 8));" ..
+ "\n\t\t__cheriot_shared_object_section_${global} : CAPALIGN" ..
+ "\n\t\t{" ..
+ "\n\t\t\t__cheriot_shared_object_${global} = .;" ..
+ "\n\t\t\t. += ${size};" ..
+ "\n\t\t\t__cheriot_shared_object_${global}_end = .;" ..
+ "\n\t\t}\n"
+ local shared_objects_section = ""
+ for global, size in table.orderpairs(shared_objects) do
+ shared_objects_section = shared_objects_section .. string.gsub(shared_objects_template, "${([_%w]*)}", {global=global, size=size})
+ end
+ ldscript_substitutions.shared_objects = shared_objects_section
+
-- Add the counts of libraries and compartments to the substitution list.
ldscript_substitutions.compartment_count = compartment_count
ldscript_substitutions.library_count = library_count
diff --git a/tests/misc-test.cc b/tests/misc-test.cc
index 3cbab21..9c54c71 100644
--- a/tests/misc-test.cc
+++ b/tests/misc-test.cc
@@ -3,10 +3,13 @@
#define TEST_NAME "Test misc APIs"
#include "tests.hh"
+#include <compartment-macros.h>
#include <ds/pointer.h>
#include <string.h>
#include <timeout.h>
+using namespace CHERI;
+
/**
* Test timeouts.
*
@@ -112,9 +115,51 @@
"The pointer proxy `=` operator does not correctly set the pointer.");
}
+void check_shared_object(const char *name,
+ Capability<void> object,
+ size_t size,
+ PermissionSet permissions)
+{
+ debug_log("Checking shared object {}.", object);
+ TEST(object.length() == size,
+ "Object {} is {} bytes, expected {}",
+ name,
+ object.length(),
+ size);
+ TEST(object.permissions() == permissions,
+ "Object {} has permissions {}, expected {}",
+ name,
+ PermissionSet{object.permissions()},
+ permissions);
+}
+
void test_misc()
{
check_timeouts();
check_memchr();
check_pointer_utilities();
+ debug_log("Testing shared objects.");
+ check_shared_object("exampleK",
+ SHARED_OBJECT(void, exampleK),
+ 1024,
+ {Permission::Global,
+ Permission::Load,
+ Permission::Store,
+ Permission::LoadStoreCapability,
+ Permission::LoadMutable});
+ check_shared_object(
+ "exampleK",
+ SHARED_OBJECT_WITH_PERMISSIONS(void, exampleK, true, true, false, false),
+ 1024,
+ {Permission::Global, Permission::Load, Permission::Store});
+ check_shared_object(
+ "test_word",
+ SHARED_OBJECT_WITH_PERMISSIONS(void, test_word, true, false, true, false),
+ 4,
+ {Permission::Global, Permission::Load, Permission::LoadStoreCapability});
+ check_shared_object("test_word",
+ SHARED_OBJECT_WITH_PERMISSIONS(
+ void, test_word, true, false, false, false),
+ 4,
+ {Permission::Global, Permission::Load});
}
diff --git a/tests/xmake.lua b/tests/xmake.lua
index 267e791..bbc1d78 100644
--- a/tests/xmake.lua
+++ b/tests/xmake.lua
@@ -85,6 +85,9 @@
test("check_pointer")
-- Test various APIs that are too small to deserve their own test file
test("misc")
+ on_load(function(target)
+ target:values_set("shared_objects", { exampleK = 1024, test_word = 4 }, {expand = false})
+ end)
includes(path.join(sdkdir, "lib"))