Signed-off-by: York Jasper Niebuhr <[email protected]>
---
include/linux/spslr.h | 82 ++++
include/sanemaker/traps.h | 135 +++++++
kernel/Makefile | 2 +
kernel/spslr/Makefile | 7 +
kernel/spslr/pinpoint.h | 76 ++++
kernel/spslr/sanemaker_traps.c | 117 ++++++
kernel/spslr/spslr.c | 555 +++++++++++++++++++++++++++
kernel/spslr/spslr_env.c | 74 ++++
kernel/spslr/spslr_env.h | 45 +++
kernel/spslr/spslr_randomizer.c | 646 ++++++++++++++++++++++++++++++++
kernel/spslr/spslr_randomizer.h | 29 ++
11 files changed, 1768 insertions(+)
create mode 100644 include/linux/spslr.h
create mode 100644 include/sanemaker/traps.h
create mode 100644 kernel/spslr/Makefile
create mode 100644 kernel/spslr/pinpoint.h
create mode 100644 kernel/spslr/sanemaker_traps.c
create mode 100644 kernel/spslr/spslr.c
create mode 100644 kernel/spslr/spslr_env.c
create mode 100644 kernel/spslr/spslr_env.h
create mode 100644 kernel/spslr/spslr_randomizer.c
create mode 100644 kernel/spslr/spslr_randomizer.h
diff --git a/include/linux/spslr.h b/include/linux/spslr.h
new file mode 100644
index 000000000000..2606d66c39f0
--- /dev/null
+++ b/include/linux/spslr.h
@@ -0,0 +1,82 @@
+#ifndef SPSLR_SELFPATCH_H
+#define SPSLR_SELFPATCH_H
+
+#ifdef CONFIG_SPSLR
+
+#include <linux/types.h>
+
+#define SPSLR_START_UNITS_SYM __start_spslr_units
+#define SPSLR_STOP_UNITS_SYM __stop_spslr_units
+#define SPSLR_START_TARGETS_SYM __start_spslr_targets
+#define SPSLR_STOP_TARGETS_SYM __stop_spslr_targets
+
+extern bool spslr_enabled;
+
+enum spslr_viability { SPSLR_VIABLE, SPSLR_NONVIABLE };
+
+enum spslr_error {
+ SPSLR_OK,
+ SPSLR_ERROR_INCOMPLETE_CTX,
+ SPSLR_ERROR_INCOMPATIBLE_CTX,
+ SPSLR_ERROR_RANDOMIZER_INIT,
+ SPSLR_ERROR_INITIAL_TARGET_LAYOUT,
+ SPSLR_ERROR_RANDOMIZED_TARGET_LAYOUT,
+ SPSLR_ERROR_RANDOMIZE,
+ SPSLR_ERROR_MEMORY,
+ SPSLR_ERROR_UNINITIALIZED,
+ SPSLR_ERROR_ALREADY_PATCHED,
+ SPSLR_ERROR_PATCH_DPINS,
+ SPSLR_ERROR_PATCH_IPINS,
+ SPSLR_ERROR_MAP_TARGETS
+};
+
+struct spslr_status {
+ enum spslr_viability viability;
+ enum spslr_error error;
+};
+
+struct spslr_entry {
+ const void *start_units; // Address of SPSLR_START_UNITS_SYM
+ const void *stop_units; // Address of SPSLR_STOP_UNITS_SYM
+ const void *start_targets; // Address of SPSLR_START_TARGETS_SYM
+ const void *stop_targets; // Address of SPSLR_STOP_TARGETS_SYM
+};
+
+struct spslr_ctx {
+ struct spslr_entry entry;
+ void *workspace; // Temporary buffer of spslr_workspace_size(&entry)
bytes
+};
+
+/*
+ * Runtime entry points are intentionally split:
+ *
+ * spslr_init() creates randomized layouts.
+ * spslr_selfpatch() patches the main executable.
+ * spslr_patch_module() patches with module-local metadata using host layouts.
+ */
+
+struct spslr_status spslr_init(void);
+struct spslr_status spslr_selfpatch(void);
+unsigned long spslr_workspace_size(const struct spslr_entry *entry);
+struct spslr_status spslr_patch_module(const struct spslr_ctx *m);
+
+/* Use spslr_target_hash(type) to get a pointer to the 16 byte md5 hash
+ of the target type. If type is not an SPSLR target, NULL is returned. */
+
+extern const unsigned char *__spslr_target_hash(const void *);
+
+#define __SPSLR_CAT2(a, b) a##b
+#define __SPSLR_CAT(a, b) __SPSLR_CAT2(a, b)
+
+#define __spslr_target_hash_impl(T, n) \
+ ({ \
+ extern T __SPSLR_CAT(__spslr_target_hash_type_anchor_, n); \
+ __spslr_target_hash( \
+ &__SPSLR_CAT(__spslr_target_hash_type_anchor_, n)); \
+ })
+
+#define spslr_target_hash(T) __spslr_target_hash_impl(T, __COUNTER__)
+
+#endif /* CONFIG_SPSLR */
+
+#endif
diff --git a/include/sanemaker/traps.h b/include/sanemaker/traps.h
new file mode 100644
index 000000000000..bcb75198b18b
--- /dev/null
+++ b/include/sanemaker/traps.h
@@ -0,0 +1,135 @@
+#ifndef SANEMAKER_TRAPS_H
+#define SANEMAKER_TRAPS_H
+
+/* Define trap API attributes */
+
+#define SANEMAKER_TRAP_API extern __attribute__((__visibility__("default")))
+
+/* Use sanemaker_target_tag(&obj) to make sanemaker watch memops to that
object */
+
+#ifdef CONFIG_SANEMAKER
+
+SANEMAKER_TRAP_API
+void __sanemaker_target_tag_trap(const void *ptr, const unsigned char *target);
+
+#define sanemaker_target_tag(ptr, type) \
+ __sanemaker_target_tag_trap(ptr, spslr_target_hash(type))
+
+#else
+
+#define sanemaker_target_tag(ptr, type)
+
+#endif
+
+/* Use sanemaker_target_untag(&obj) to make sanemaker stop watching memops to
that object */
+
+#ifdef CONFIG_SANEMAKER
+
+SANEMAKER_TRAP_API
+void __sanemaker_target_untag_trap(const void *ptr);
+
+#define sanemaker_target_untag(ptr) __sanemaker_target_untag_trap(ptr)
+
+#else
+
+#define sanemaker_target_untag(ptr)
+
+#endif
+
+/* The sanemaker_finish_layout(&fieldarr, &target_hash) should be called
+ by spslr selfpatch when a target layout has been randomized */
+
+#ifdef CONFIG_SANEMAKER
+
+SANEMAKER_TRAP_API
+void __sanemaker_finish_layout_trap(const void *fields,
+ const unsigned char *target);
+
+#define sanemaker_finish_layout(fields, target) \
+ __sanemaker_finish_layout_trap(fields, target)
+
+#else
+
+#define sanemaker_finish_layout(fields, target)
+
+#endif
+
+/* Use sanemaker_fetch(what, default) to let sanemaker make decisions at
runtime */
+
+typedef enum {
+ SANEMAKER_FETCH_SPSLR_ENABLED = 1,
+} sanemaker_fetch_t;
+
+#ifdef CONFIG_SANEMAKER
+
+SANEMAKER_TRAP_API
+int __sanemaker_fetch_trap(sanemaker_fetch_t what, int def);
+
+#define sanemaker_fetch(what, def) __sanemaker_fetch_trap(what, def)
+
+#else
+
+#define sanemaker_fetch(what, def) (def)
+
+#endif
+
+/* Use sanemaker_signal(signal) to control sanemaker behavior */
+
+typedef enum {
+ SANEMAKER_SIGNAL_PATCH_BOUNDARY = 1, /* the image has been patched */
+ SANEMAKER_SIGNAL_PAUSE = 2,
+ SANEMAKER_SIGNAL_RESUME = 3,
+} sanemaker_signal_t;
+
+#ifdef CONFIG_SANEMAKER
+
+SANEMAKER_TRAP_API
+void __sanemaker_signal_trap(sanemaker_signal_t signal);
+
+#define sanemaker_signal(signal) __sanemaker_signal_trap(signal)
+
+#else
+
+#define sanemaker_signal(signal)
+
+#endif
+
+/* Use sanemaker_new_image(name, ptr) and sanemaker_new_image_text(image,
begin, end)
+ to allow normalization of program counters inside dynamically loaded text
segments */
+
+#ifdef CONFIG_SANEMAKER
+
+SANEMAKER_TRAP_API
+void __sanemaker_new_image_trap(const char *name, const void *base);
+
+SANEMAKER_TRAP_API
+void __sanemaker_new_image_text_trap(const char *image, const void *begin,
+ const void *end);
+
+SANEMAKER_TRAP_API
+void __sanemaker_drop_image_trap(const char *image);
+
+SANEMAKER_TRAP_API
+void __sanemaker_drop_image_text_trap(const char *image, const void *begin,
+ const void *end);
+
+#define sanemaker_new_image(name, base) __sanemaker_new_image_trap(name, base)
+
+#define sanemaker_new_image_text(image, begin, end) \
+ __sanemaker_new_image_text_trap(image, begin, end)
+
+#define sanemaker_drop_image(image) __sanemaker_drop_image_trap(image)
+
+#define sanemaker_drop_image_text(image, begin, end) \
+ __sanemaker_drop_image_text_trap(image, begin, end)
+
+#else
+
+#define sanemaker_new_image(name, base)
+#define sanemaker_new_image_text(image, begin, end)
+#define sanemaker_drop_image(image)
+#define sanemaker_drop_image_text(image, begin, end)
+
+#endif
+
+#endif
diff --git a/kernel/Makefile b/kernel/Makefile
index 1e1a31673577..cddfbff9f9f0 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -142,6 +142,8 @@ obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
obj-$(CONFIG_RESOURCE_KUNIT_TEST) += resource_kunit.o
obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
+obj-$(CONFIG_SPSLR) += spslr/
+
CFLAGS_kstack_erase.o += $(DISABLE_KSTACK_ERASE)
CFLAGS_kstack_erase.o += $(call cc-option,-mgeneral-regs-only)
obj-$(CONFIG_KSTACK_ERASE) += kstack_erase.o
diff --git a/kernel/spslr/Makefile b/kernel/spslr/Makefile
new file mode 100644
index 000000000000..a736d0aab05c
--- /dev/null
+++ b/kernel/spslr/Makefile
@@ -0,0 +1,7 @@
+obj-$(CONFIG_SPSLR) += spslr.o spslr_env.o spslr_randomizer.o
+obj-$(CONFIG_SANEMAKER) += sanemaker_traps.o
+
+CFLAGS_REMOVE_spslr.o += $(PINPOINT_PLUGIN_CFLAGS)
+CFLAGS_REMOVE_spslr_env.o += $(PINPOINT_PLUGIN_CFLAGS)
+CFLAGS_REMOVE_spslr_randomizer.o += $(PINPOINT_PLUGIN_CFLAGS)
+CFLAGS_REMOVE_sanemaker_traps.o += $(PINPOINT_PLUGIN_CFLAGS)
diff --git a/kernel/spslr/pinpoint.h b/kernel/spslr/pinpoint.h
new file mode 100644
index 000000000000..39ca81e6491d
--- /dev/null
+++ b/kernel/spslr/pinpoint.h
@@ -0,0 +1,76 @@
+#ifndef SPSLR_PINPOINT_H
+#define SPSLR_PINPOINT_H
+
+#include "spslr_env.h"
+
+/* Field must remain at its original offset during layout randomization. */
+#define SPSLR_FLAG_FIELD_FIXED 1
+
+struct spslr_unit;
+struct spslr_ipin;
+struct spslr_ipin_expr;
+struct spslr_dpin;
+struct spslr_target;
+struct spslr_target_layout;
+struct spslr_target_field;
+
+/* CU-local target reference; points into the global deduplicated target
table. */
+typedef const struct spslr_target *spslr_target_ref;
+
+/*
+ * Metadata for one compilation unit. The target array is CU-local and maps
+ * unit_target_idx values used by pins to deduplicated global target headers.
+ */
+struct spslr_unit {
+ const char *source; // Source file name
+ spslr_u64 target_cnt;
+ const spslr_target_ref *target_refs; // CU-local target ref array
+ spslr_u64 ipin_cnt;
+ const struct spslr_ipin *ipins;
+ spslr_u64 dpin_cnt;
+ const struct spslr_dpin *dpins;
+} __packed;
+
+/* Instruction patch site: address of patchable immediate/displacement bytes.
*/
+struct spslr_ipin {
+ void *addr;
+ spslr_u64 size;
+ const struct spslr_ipin_expr *expr;
+} __packed;
+
+/* Data patch site: address of an object/subobject whose layout must be
adjusted. */
+struct spslr_dpin {
+ void *addr;
+ spslr_u64 unit_target_idx;
+} __packed;
+
+/* Current simple expression: randomized offset of one field in one CU-local
target. */
+struct spslr_ipin_expr {
+ spslr_u64 unit_target_idx;
+ spslr_u64 field_idx;
+} __packed;
+
+/* Deduplicated target type descriptor, keyed by deterministic layout hash. */
+struct spslr_target {
+ unsigned char hash[16];
+ const char *name;
+ const struct spslr_target_layout *layout;
+} __packed;
+
+/* Physical layout of a target type before runtime randomization. */
+struct spslr_target_layout {
+ spslr_u64 size;
+ spslr_u64 field_cnt;
+ const struct spslr_target_field *fields;
+} __packed;
+
+/* One randomizable or fixed field/range within a target layout. */
+struct spslr_target_field {
+ const char *name;
+ spslr_u64 size;
+ spslr_u64 offset;
+ spslr_u64 alignment;
+ spslr_u64 flags;
+} __packed;
+
+#endif
diff --git a/kernel/spslr/sanemaker_traps.c b/kernel/spslr/sanemaker_traps.c
new file mode 100644
index 000000000000..63aa1a4b9182
--- /dev/null
+++ b/kernel/spslr/sanemaker_traps.c
@@ -0,0 +1,117 @@
+#include <sanemaker/traps.h>
+
+#define SANEMAKER_TRAP_FN \
+ __attribute__((__noinline__, __noclone__, __used__, \
+ __externally_visible__, \
+ __visibility__("default"), __naked__))
+
+/* Sanemaker reads object pointer from rdi and target hash pointer from rsi */
+SANEMAKER_TRAP_FN
+void __sanemaker_target_tag_trap(const void *ptr, const unsigned char *target)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_target_tag_trap_incision\n"
+ ".type __sanemaker_target_tag_trap_incision, @notype\n"
+ "__sanemaker_target_tag_trap_incision:\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads object pointer from rdi */
+SANEMAKER_TRAP_FN
+void __sanemaker_target_untag_trap(const void *ptr)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_target_untag_trap_incision\n"
+ ".type __sanemaker_target_untag_trap_incision, @notype\n"
+ "__sanemaker_target_untag_trap_incision:\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads field pointer from rdi and target hash pointer from rsi */
+SANEMAKER_TRAP_FN
+void __sanemaker_finish_layout_trap(const void *fields, const unsigned char
*target)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_finish_layout_trap_incision\n"
+ ".type __sanemaker_finish_layout_trap_incision, @notype\n"
+ "__sanemaker_finish_layout_trap_incision:\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads name from rdi and overwrites rsi to set a value */
+SANEMAKER_TRAP_FN
+int __sanemaker_fetch_trap(sanemaker_fetch_t what, int def)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_fetch_trap_incision\n"
+ ".type __sanemaker_fetch_trap_incision, @notype\n"
+ "__sanemaker_fetch_trap_incision:\n"
+ "nop\n"
+ "movl %esi, %eax\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads the signal event from rdi */
+SANEMAKER_TRAP_FN
+void __sanemaker_signal_trap(sanemaker_signal_t signal)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_signal_trap_incision\n"
+ ".type __sanemaker_signal_trap_incision, @notype\n"
+ "__sanemaker_signal_trap_incision:\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads name from rdi and base from rsi */
+SANEMAKER_TRAP_FN
+void __sanemaker_new_image_trap(const char *name, const void *base)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_new_image_trap_incision\n"
+ ".type __sanemaker_new_image_trap_incision, @notype\n"
+ "__sanemaker_new_image_trap_incision:\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads image name from rdi, begin from rsi and end from rdx */
+SANEMAKER_TRAP_FN
+void __sanemaker_new_image_text_trap(const char *image, const void *begin,
const void *end)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_new_image_text_trap_incision\n"
+ ".type __sanemaker_new_image_text_trap_incision, @notype\n"
+ "__sanemaker_new_image_text_trap_incision:\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads image name from rdi */
+SANEMAKER_TRAP_FN
+void __sanemaker_drop_image_trap(const char *image)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_drop_image_trap_incision\n"
+ ".type __sanemaker_drop_image_trap_incision, @notype\n"
+ "__sanemaker_drop_image_trap_incision:\n"
+ "ret\n"
+ );
+}
+
+/* Sanemaker reads image name from rdi, begin from rsi and end from rdx */
+SANEMAKER_TRAP_FN
+void __sanemaker_drop_image_text_trap(const char *image, const void *begin,
const void *end)
+{
+ __asm__ volatile(
+ ".globl __sanemaker_drop_image_text_trap_incision\n"
+ ".type __sanemaker_drop_image_text_trap_incision, @notype\n"
+ "__sanemaker_drop_image_text_trap_incision:\n"
+ "ret\n"
+ );
+}
+
diff --git a/kernel/spslr/spslr.c b/kernel/spslr/spslr.c
new file mode 100644
index 000000000000..5ef36731f75e
--- /dev/null
+++ b/kernel/spslr/spslr.c
@@ -0,0 +1,555 @@
+#include <linux/spslr.h>
+#include <sanemaker/traps.h>
+
+#include "spslr_randomizer.h"
+#include "spslr_env.h"
+#include "pinpoint.h"
+
+/*
+ * Runtime portion of SPSLR.
+ *
+ * This code consumes the metadata emitted by pinpoint, randomizes target
+ * layouts, rewrites static data objects, and patches instruction immediates
+ * that encode structure field offsets.
+ */
+
+#define SPSLR_SANITY_CHECK
+
+struct target_map {
+ spslr_u64 *map;
+ spslr_u64 size;
+};
+
+static void init_spslr_meta(void);
+static int spslr_targets_compatible(const struct spslr_target *begin,
+ const struct spslr_target *end);
+static enum spslr_error spslr_patch_unit(const struct spslr_unit *unit,
+ spslr_u64 *tmap_buffer,
+ void *reorder_buffer);
+static struct spslr_status spslr_patch(const struct spslr_ctx *ctx);
+static spslr_u64 spslr_target_mapping_size(void);
+static spslr_u64 *workspace_target_mapping(void *workspace);
+static void *workspace_reorder_buffer(void *workspace);
+
+static int spslr_patch_dpins(const struct spslr_dpin *dpins, spslr_u64 cnt,
+ const struct target_map *tmap,
+ void *reorder_buffer);
+static int spslr_patch_dpin(void *addr, spslr_u64 target, void
*reorder_buffer);
+static int spslr_patch_ipins(const struct spslr_ipin *ipins, spslr_u64 cnt,
+ const struct target_map *tmap);
+
+static int reorder_object(void *dst, const void *src, spslr_u64 target);
+static int spslr_calculate_ipin_value(const struct spslr_ipin_expr *expr,
+ spslr_s64 *res,
+ const struct target_map *tmap);
+
+static int spslr_map_target(const struct spslr_target *target, spslr_u64 *idx);
+static int spslr_map_targets(const struct spslr_unit *unit,
+ struct target_map *tmap);
+
+static int initialized = 0, patched = 0;
+static enum spslr_viability viable = SPSLR_VIABLE;
+
+spslr_u64 spslr_target_cnt = 0;
+const struct spslr_target *spslr_targets = NULL;
+
+/* Host image spslr metadata entry point */
+extern const struct spslr_unit SPSLR_START_UNITS_SYM[];
+extern const struct spslr_unit SPSLR_STOP_UNITS_SYM[];
+extern const struct spslr_target SPSLR_START_TARGETS_SYM[];
+extern const struct spslr_target SPSLR_STOP_TARGETS_SYM[];
+
+/*
+ * Initialize runtime randomization state.
+ *
+ * After this point target layouts are randomized, but code/data does still
+ * contain original-layout offsets until the patching entry points run.
+ */
+
+static void __init init_spslr_meta(void)
+{
+ spslr_target_cnt =
+ (spslr_u64)(SPSLR_STOP_TARGETS_SYM - SPSLR_START_TARGETS_SYM);
+ spslr_targets = SPSLR_START_TARGETS_SYM;
+}
+
+struct spslr_status __init spslr_init(void)
+{
+ if (initialized)
+ return (struct spslr_status){ .viability = viable,
+ .error = SPSLR_OK };
+
+ init_spslr_meta();
+
+ if (spslr_randomizer_init() < 0)
+ return (struct spslr_status){
+ .viability = viable,
+ .error = SPSLR_ERROR_RANDOMIZER_INIT
+ };
+
+#ifdef SPSLR_SANITY_CHECK
+ for (spslr_u64 tidx = 0; tidx < spslr_target_cnt; tidx++) {
+ if (spslr_randomizer_validate_target(tidx) < 0)
+ return (struct spslr_status){
+ .viability = viable,
+ .error = SPSLR_ERROR_INITIAL_TARGET_LAYOUT
+ };
+ }
+#endif
+
+ if (sanemaker_fetch(SANEMAKER_FETCH_SPSLR_ENABLED, 1)) {
+ if (spslr_randomize() < 0)
+ return (struct spslr_status){
+ .viability = viable,
+ .error = SPSLR_ERROR_RANDOMIZE
+ };
+ }
+
+#ifdef SPSLR_SANITY_CHECK
+ for (spslr_u64 tidx = 0; tidx < spslr_target_cnt; tidx++) {
+ if (spslr_randomizer_validate_target(tidx) < 0)
+ return (struct spslr_status){
+ .viability = viable,
+ .error = SPSLR_ERROR_RANDOMIZED_TARGET_LAYOUT
+ };
+ }
+#endif
+
+ initialized = 1;
+ return (struct spslr_status){ .viability = viable, .error = SPSLR_OK };
+}
+
+/*
+ * Calculate required workspace buffer size. This includes the reorder
+ * buffer for data pins and the storage for the mapping of local to
+ * global target indices.
+ */
+
+static spslr_u64 spslr_target_mapping_size(void)
+{
+ return spslr_target_cnt * sizeof(spslr_u64);
+}
+
+unsigned long spslr_workspace_size(const struct spslr_entry *entry)
+{
+ if (!entry || !entry->start_units || !entry->stop_units)
+ return 0;
+
+ const struct spslr_unit *start_units =
+ (const struct spslr_unit *)entry->start_units;
+ const struct spslr_unit *stop_units =
+ (const struct spslr_unit *)entry->stop_units;
+
+ spslr_u64 max_dpin_size = 0;
+ for (const struct spslr_unit *unit = start_units; unit != stop_units;
+ unit++) {
+ for (spslr_u64 dpin = 0; dpin < unit->dpin_cnt; dpin++) {
+ const struct spslr_target *target =
+ unit->target_refs[unit->dpins[dpin]
+ .unit_target_idx];
+
+ if (target->layout->size > max_dpin_size)
+ max_dpin_size = target->layout->size;
+ }
+ }
+
+ return spslr_target_mapping_size() + max_dpin_size;
+}
+
+/*
+ * Check if the given target space is compatible with that of the
+ * host.
+ */
+
+static int spslr_meta_known_target(const struct spslr_target *t)
+{
+ for (spslr_u64 i = 0; i < spslr_target_cnt; i++) {
+ if (spslr_env_memcmp(t->hash, spslr_targets[i].hash,
+ sizeof(t->hash)) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int spslr_targets_compatible(const struct spslr_target *begin,
+ const struct spslr_target *end)
+{
+ spslr_u64 cnt = (spslr_u64)(end - begin);
+ if (cnt > spslr_target_cnt)
+ return 0;
+
+ for (spslr_u64 i = 0; i < cnt; i++) {
+ if (!spslr_meta_known_target(begin + i))
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * For each CU, map local target indices to global target indices and then
+ * to host indices. Afterwards, patch ipins and dpins.
+ */
+
+static spslr_u64 *workspace_target_mapping(void *workspace)
+{
+ return (spslr_u64 *)workspace;
+}
+
+static void *workspace_reorder_buffer(void *workspace)
+{
+ return (spslr_u8 *)workspace + spslr_target_mapping_size();
+}
+
+static int spslr_map_target(const struct spslr_target *target, spslr_u64 *idx)
+{
+ for (spslr_u64 i = 0; i < spslr_target_cnt; i++) {
+ if (spslr_env_memcmp(target->hash, spslr_targets[i].hash,
+ sizeof(target->hash)) == 0) {
+ *idx = i;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int spslr_map_targets(const struct spslr_unit *unit,
+ struct target_map *tmap)
+{
+ tmap->size = 0;
+
+ for (spslr_u64 i = 0; i < unit->target_cnt; i++) {
+ if (spslr_map_target(unit->target_refs[i], tmap->map + i) < 0)
+ return -1;
+ }
+
+ tmap->size = unit->target_cnt;
+ return 0;
+}
+
+static enum spslr_error spslr_patch_unit(const struct spslr_unit *unit,
+ spslr_u64 *tmap_buffer,
+ void *reorder_buffer)
+{
+ struct target_map tmap = { .map = tmap_buffer, .size = 0 };
+
+ if (spslr_map_targets(unit, &tmap) < 0)
+ return SPSLR_ERROR_MAP_TARGETS;
+
+ if (spslr_patch_dpins(unit->dpins, unit->dpin_cnt, &tmap,
+ reorder_buffer) < 0)
+ return SPSLR_ERROR_PATCH_DPINS;
+
+ if (spslr_patch_ipins(unit->ipins, unit->ipin_cnt, &tmap) < 0)
+ return SPSLR_ERROR_PATCH_IPINS;
+
+ return SPSLR_OK;
+}
+
+static struct spslr_status spslr_patch(const struct spslr_ctx *ctx)
+{
+ enum spslr_error err = SPSLR_OK;
+ enum spslr_viability via = SPSLR_VIABLE;
+
+ spslr_u64 *target_map_buffer = NULL;
+ void *reorder_buffer = NULL;
+
+ const struct spslr_unit *start_units = NULL;
+ const struct spslr_unit *stop_units = NULL;
+ const struct spslr_target *start_targets = NULL;
+ const struct spslr_target *stop_targets = NULL;
+
+ if (!ctx || !ctx->entry.start_units || !ctx->entry.stop_units ||
+ !ctx->entry.start_targets || !ctx->entry.stop_targets ||
+ !ctx->workspace) {
+ err = SPSLR_ERROR_INCOMPLETE_CTX;
+ goto finish;
+ }
+
+ start_units = (const struct spslr_unit *)ctx->entry.start_units;
+ stop_units = (const struct spslr_unit *)ctx->entry.stop_units;
+ start_targets = (const struct spslr_target *)ctx->entry.start_targets;
+ stop_targets = (const struct spslr_target *)ctx->entry.stop_targets;
+
+ target_map_buffer = workspace_target_mapping(ctx->workspace);
+ reorder_buffer = workspace_reorder_buffer(ctx->workspace);
+
+ if (!spslr_targets_compatible(start_targets, stop_targets)) {
+ err = SPSLR_ERROR_INCOMPATIBLE_CTX;
+ goto finish;
+ }
+
+ via = SPSLR_NONVIABLE;
+
+ if (sanemaker_fetch(SANEMAKER_FETCH_SPSLR_ENABLED, 1)) {
+ for (const struct spslr_unit *unit = start_units;
+ unit != stop_units; unit++) {
+ err = spslr_patch_unit(unit, target_map_buffer,
+ reorder_buffer);
+ if (err != SPSLR_OK)
+ goto finish;
+ }
+ }
+
+ via = SPSLR_VIABLE;
+
+finish:
+ return (struct spslr_status){ .viability = via, .error = err };
+}
+
+/*
+ * Patch the main executable.
+ *
+ * Instruction pins rewrite immediate operands in text, while data pins rewrite
+ * existing static objects from original layout into randomized layout.
+ */
+
+struct spslr_status __init spslr_selfpatch(void)
+{
+ enum spslr_error err = SPSLR_OK;
+
+ spslr_u64 host_workspace_size;
+
+ struct spslr_ctx host_ctx;
+ host_ctx.entry.start_units = SPSLR_START_UNITS_SYM;
+ host_ctx.entry.stop_units = SPSLR_STOP_UNITS_SYM;
+ host_ctx.entry.start_targets = SPSLR_START_TARGETS_SYM;
+ host_ctx.entry.stop_targets = SPSLR_STOP_TARGETS_SYM;
+ host_ctx.workspace = NULL;
+
+ struct spslr_status internal_patch_status;
+
+ if (patched) {
+ err = SPSLR_ERROR_ALREADY_PATCHED;
+ goto finish;
+ }
+
+ if (!initialized) {
+ err = SPSLR_ERROR_UNINITIALIZED;
+ goto finish;
+ }
+
+ host_workspace_size = spslr_workspace_size(&host_ctx.entry);
+ host_ctx.workspace = spslr_env_malloc(host_workspace_size);
+
+ if (!host_ctx.workspace) {
+ err = SPSLR_ERROR_MEMORY;
+ goto finish;
+ }
+
+ internal_patch_status = spslr_patch(&host_ctx);
+ if (internal_patch_status.error == SPSLR_OK)
+ patched = 1;
+
+ viable = internal_patch_status.viability;
+ err = internal_patch_status.error;
+
+finish:
+ if (host_ctx.workspace)
+ spslr_env_free(host_ctx.workspace, host_workspace_size);
+
+ sanemaker_signal(SANEMAKER_SIGNAL_PATCH_BOUNDARY);
+ return (struct spslr_status){ .viability = viable, .error = err };
+}
+
+/*
+ * Patch metadata belonging to a separately loaded module.
+ *
+ * Modules reuse the target randomization state created by the main executable;
+ * they contribute only their own instruction and data patch sites.
+ */
+
+struct spslr_status spslr_patch_module(const struct spslr_ctx *m)
+{
+ if (!initialized)
+ return (struct spslr_status){
+ .viability = SPSLR_VIABLE,
+ .error = SPSLR_ERROR_UNINITIALIZED
+ };
+
+ if (!m || !m->entry.start_units || !m->entry.stop_units ||
+ !m->entry.start_targets || !m->entry.stop_targets || !m->workspace)
+ return (struct spslr_status){
+ .viability = SPSLR_VIABLE,
+ .error = SPSLR_ERROR_INCOMPLETE_CTX
+ };
+
+ struct spslr_status s = spslr_patch(m);
+ return (struct spslr_status){ .viability = (s.error == SPSLR_OK ?
+ SPSLR_VIABLE :
+ SPSLR_NONVIABLE),
+ .error = s.error };
+}
+
+/*
+ * Rewrite one object instance from original layout into randomized layout.
+ *
+ * A temporary buffer is used so overlapping source/destination field ranges do
+ * not corrupt data while fields are moved.
+ */
+
+static int reorder_object(void *dst, const void *src, spslr_u64 target)
+{
+ spslr_u64 field_count;
+ if (spslr_randomizer_get_target(target, NULL, &field_count))
+ return -1;
+
+ const spslr_u8 *src_countable = (const spslr_u8 *)src;
+ spslr_u8 *dst_countable = (spslr_u8 *)dst;
+
+ for (spslr_u64 i = 0; i < field_count; i++) {
+ struct spslr_randomizer_field_info finfo;
+ if (spslr_randomizer_get_field(
+ target, i, SPSLR_RANDOMIZER_FIELD_IDX_MODE_FINAL,
+ &finfo))
+ return -1;
+
+ spslr_env_memcpy(dst_countable + finfo.offset,
+ src_countable + finfo.initial_offset,
+ finfo.size);
+ }
+
+ return 0;
+}
+
+/*
+ * Apply data pin patches.
+ *
+ * Each pin's address already points at an existing object in original layout.
Patching
+ * converts that storage in-place to the target's randomized layout.
+ */
+
+static int spslr_patch_dpins(const struct spslr_dpin *dpins, spslr_u64 cnt,
+ const struct target_map *tmap,
+ void *reorder_buffer)
+{
+ for (spslr_u64 dpidx = 0; dpidx < cnt; dpidx++) {
+ const struct spslr_dpin *dp = &dpins[dpidx];
+
+ if (dp->unit_target_idx >= tmap->size)
+ return -1;
+
+ if (spslr_patch_dpin((void *)dp->addr,
+ tmap->map[dp->unit_target_idx],
+ reorder_buffer) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int spslr_patch_dpin(void *addr, spslr_u64 target, void *reorder_buffer)
+{
+ if (target >= spslr_target_cnt)
+ return -1;
+
+ int res = -1;
+ const struct spslr_target *t = &spslr_targets[target];
+
+ sanemaker_signal(SANEMAKER_SIGNAL_PAUSE);
+
+ spslr_env_memset(reorder_buffer, 0, t->layout->size);
+
+ if (reorder_object(reorder_buffer, addr, target) < 0)
+ goto finish;
+
+ if (spslr_env_poke_data(addr, reorder_buffer, t->layout->size) < 0)
+ goto finish;
+
+ res = 0;
+finish:
+ sanemaker_signal(SANEMAKER_SIGNAL_RESUME);
+ return res;
+}
+
+static int spslr_ipin_value_fits(spslr_u64 value, spslr_u64 size)
+{
+ if (size == 0)
+ return 0;
+
+ spslr_u64 bound = (spslr_u64)1 << (8 * size);
+ return value < bound;
+}
+
+static int spslr_patch_ipins(const struct spslr_ipin *ipins, spslr_u64 cnt,
+ const struct target_map *tmap)
+{
+ for (spslr_u64 ipidx = 0; ipidx < cnt; ipidx++) {
+ const struct spslr_ipin *ip = &ipins[ipidx];
+
+ spslr_s64 value;
+ if (spslr_calculate_ipin_value(ip->expr, &value, tmap) < 0)
+ return -1;
+
+ if (value < 0 ||
+ !spslr_ipin_value_fits((spslr_u64)value, ip->size))
+ return -1;
+
+ /*
+ * Text patching is deliberately scoped to the immediate field
only.
+ * The surrounding instruction bytes were fixed by
pinpoint/patchcompile and
+ * must not change at runtime.
+ */
+
+ switch (ip->size) {
+ case 1:
+ if (spslr_env_poke_text_8((void *)ip->addr,
+ (spslr_u8)value) < 0)
+ return -1;
+ break;
+ case 2:
+ if (spslr_env_poke_text_16((void *)ip->addr,
+ (spslr_u16)value) < 0)
+ return -1;
+ break;
+ case 4:
+ if (spslr_env_poke_text_32((void *)ip->addr,
+ (spslr_u32)value) < 0)
+ return -1;
+ break;
+ case 8:
+ if (spslr_env_poke_text_64((void *)ip->addr,
+ (spslr_u64)value) < 0)
+ return -1;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Interpret one ipin program and compute the replacement immediate value.
+ *
+ * The program describes original target/field references; this function maps
+ * them through the randomized runtime layout and returns the value written
into
+ * the instruction stream.
+ */
+
+static int spslr_calculate_ipin_value(const struct spslr_ipin_expr *expr,
+ spslr_s64 *res,
+ const struct target_map *tmap)
+{
+ if (!res)
+ return -1;
+
+ *res = 0;
+
+ if (expr->unit_target_idx >= tmap->size)
+ return -1;
+
+ spslr_u64 global_target_idx = tmap->map[expr->unit_target_idx];
+
+ struct spslr_randomizer_field_info finfo;
+ if (spslr_randomizer_get_field(global_target_idx, expr->field_idx,
+ SPSLR_RANDOMIZER_FIELD_IDX_MODE_ORIGINAL,
+ &finfo) != 0)
+ return -1;
+
+ *res = finfo.offset;
+ return 0;
+}
diff --git a/kernel/spslr/spslr_env.c b/kernel/spslr/spslr_env.c
new file mode 100644
index 000000000000..a4a2d2389dcd
--- /dev/null
+++ b/kernel/spslr/spslr_env.c
@@ -0,0 +1,74 @@
+#include "spslr_env.h"
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/random.h>
+#include <linux/memblock.h>
+
+#ifdef CONFIG_X86
+
+#include <asm/text-patching.h>
+
+static __always_inline int spslr_env_poke_text(void *dst, const void *src,
size_t n)
+{
+ text_poke_early(dst, src, n);
+ return 0;
+}
+
+#endif
+
+int spslr_env_poke_text_8(void *dst, u8 value)
+{
+ return spslr_env_poke_text(dst, &value, sizeof(value));
+}
+
+int spslr_env_poke_text_16(void *dst, u16 value)
+{
+ return spslr_env_poke_text(dst, &value, sizeof(value));
+}
+
+int spslr_env_poke_text_32(void *dst, u32 value)
+{
+ return spslr_env_poke_text(dst, &value, sizeof(value));
+}
+
+int spslr_env_poke_text_64(void *dst, u64 value)
+{
+ return spslr_env_poke_text(dst, &value, sizeof(value));
+}
+
+/*
+ * Hook runs before slab allocators are available.
+ * memblock_alloc() is the correct early-boot allocator.
+ */
+void* __init spslr_env_malloc(spslr_u64 n) {
+ size_t size = PAGE_ALIGN(n ? n : 1);
+ return memblock_alloc(size, SMP_CACHE_BYTES);
+}
+
+void __init spslr_env_free(void *ptr, spslr_u64 n) {
+ if (ptr)
+ memblock_free(ptr, PAGE_ALIGN(n ? n : 1));
+}
+
+int spslr_env_poke_data(void* dst, const void* src, spslr_u64 n) {
+ memcpy(dst, src, n);
+ return 0;
+}
+
+void spslr_env_memset(void* dst, int v, spslr_u64 n) {
+ memset(dst, v, n);
+}
+
+void spslr_env_memcpy(void* dst, const void* src, spslr_u64 n) {
+ memcpy(dst, src, n);
+}
+
+int spslr_env_memcmp(const void *x, const void *y, spslr_u64 n) {
+ return memcmp(x, y, n);
+}
+
+spslr_u64 __init spslr_env_random_u64(void) {
+ return get_random_u64(); // Hook runs after random_init_early()
+}
+
diff --git a/kernel/spslr/spslr_env.h b/kernel/spslr/spslr_env.h
new file mode 100644
index 000000000000..f48d7e02c57e
--- /dev/null
+++ b/kernel/spslr/spslr_env.h
@@ -0,0 +1,45 @@
+#ifndef SPSLR_ENV_H
+#define SPSLR_ENV_H
+
+#include <linux/types.h>
+#include <linux/stddef.h>
+#include <linux/init.h>
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#ifndef __init
+#define __init /* only required in kernel */
+#endif
+
+#ifndef __printf
+#define __printf(fmt_pos, arg_pos) \
+ __attribute__((format(printf, fmt_pos, arg_pos)))
+#endif
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+typedef uint8_t spslr_u8;
+typedef uint16_t spslr_u16;
+typedef uint32_t spslr_u32;
+typedef uint64_t spslr_u64;
+typedef int32_t spslr_s32;
+typedef int64_t spslr_s64;
+typedef uintptr_t spslr_uintptr;
+
+int spslr_env_poke_text_8(void *dst, spslr_u8 value);
+int spslr_env_poke_text_16(void *dst, spslr_u16 value);
+int spslr_env_poke_text_32(void *dst, spslr_u32 value);
+int spslr_env_poke_text_64(void *dst, spslr_u64 value);
+int spslr_env_poke_data(void *dst, const void *src, spslr_u64 n);
+void *spslr_env_malloc(spslr_u64 n);
+void spslr_env_free(void *ptr, spslr_u64 n);
+void spslr_env_memset(void *dst, int v, spslr_u64 n);
+void spslr_env_memcpy(void *dst, const void *src, spslr_u64 n);
+int spslr_env_memcmp(const void *x, const void *y, spslr_u64 n);
+spslr_u64 spslr_env_random_u64(void);
+
+#endif
diff --git a/kernel/spslr/spslr_randomizer.c b/kernel/spslr/spslr_randomizer.c
new file mode 100644
index 000000000000..879ead0a2384
--- /dev/null
+++ b/kernel/spslr/spslr_randomizer.c
@@ -0,0 +1,646 @@
+#include "spslr_randomizer.h"
+
+#include "spslr_env.h"
+#include "pinpoint.h"
+
+#include <sanemaker/traps.h>
+
+/*
+ * Target layout randomizer.
+ *
+ * The randomizer builds a permutation from original field order to randomized
+ * field order while preserving field size, alignment, and fixed-field
+ * constraints.
+ */
+
+/*
+ * Field tracks both directions of the permutation:
+ *
+ * original index -> randomized position
+ * randomized position -> original index
+ *
+ * The runtime needs both: data patching copies from original offsets to new
+ * offsets, while ipin patching maps an original field offset to its randomized
+ * offset.
+ */
+struct Field {
+ spslr_u64 offset; /* Final field offset -> fields[i].offset = offset of
field i in final layout */
+ spslr_u64 oidx; /* Original field idx -> fields[i].oidx = original
position of field i in final layout */
+ spslr_u64 fidx; /* Final field idx -> fields[i].fidx = randomized/final
position of original field i */
+};
+
+extern spslr_u64 spslr_target_cnt;
+extern const struct spslr_target *spslr_targets;
+
+static spslr_u64 *field_base_indices = NULL;
+static struct Field *fields = NULL;
+
+static int init_field_base_indices(void);
+static int init_fields_buffer(void);
+
+static const struct spslr_target_field *meta_original_field(spslr_u64 target,
+ spslr_u64 field);
+static struct Field *state_current_field_base(spslr_u64 target);
+static struct Field *state_current_field(spslr_u64 target, spslr_u64 field);
+
+static int __init init_field_base_indices(void)
+{
+ field_base_indices = (spslr_u64 *)spslr_env_malloc(sizeof(spslr_u64) *
+ spslr_target_cnt);
+ if (!field_base_indices)
+ return -1;
+
+ spslr_u64 current_field_base_idx = 0;
+ for (spslr_u64 i = 0; i < spslr_target_cnt; i++) {
+ field_base_indices[i] = current_field_base_idx;
+ current_field_base_idx += spslr_targets[i].layout->field_cnt;
+ }
+
+ return 0;
+}
+
+static const struct spslr_target_field *meta_original_field(spslr_u64 target,
+ spslr_u64 field)
+{
+ if (target >= spslr_target_cnt)
+ return NULL;
+
+ const struct spslr_target_layout *layout = spslr_targets[target].layout;
+
+ if (field >= layout->field_cnt)
+ return NULL;
+
+ return layout->fields + field;
+}
+
+static struct Field *state_current_field_base(spslr_u64 target)
+{
+ if (target >= spslr_target_cnt)
+ return NULL;
+
+ spslr_u64 field_base_idx = field_base_indices[target];
+ return fields + field_base_idx;
+}
+
+static struct Field *state_current_field(spslr_u64 target, spslr_u64 field)
+{
+ if (target >= spslr_target_cnt)
+ return NULL;
+
+ struct Field *field_base = state_current_field_base(target);
+ const struct spslr_target_layout *layout = spslr_targets[target].layout;
+
+ if (!field_base || field >= layout->field_cnt)
+ return NULL;
+
+ return field_base + field;
+}
+
+static int __init init_fields_buffer(void)
+{
+ spslr_u64 total_field_count = 0;
+ for (spslr_u64 i = 0; i < spslr_target_cnt; i++)
+ total_field_count += spslr_targets[i].layout->field_cnt;
+
+ fields = (struct Field *)spslr_env_malloc(sizeof(struct Field) *
+ total_field_count);
+ if (!fields)
+ return -1;
+
+ for (spslr_u64 i = 0; i < spslr_target_cnt; i++) {
+ spslr_u64 field_base_idx = field_base_indices[i];
+
+ for (spslr_u64 field_idx = 0;
+ field_idx < spslr_targets[i].layout->field_cnt;
+ field_idx++) {
+ const struct spslr_target_field *src_field =
+ spslr_targets[i].layout->fields + field_idx;
+ struct Field *dst_field =
+ &fields[field_base_idx + field_idx];
+
+ dst_field->offset = src_field->offset;
+ dst_field->oidx = field_idx;
+ dst_field->fidx = field_idx;
+ }
+ }
+
+ return 0;
+}
+
+int __init spslr_randomizer_init(void)
+{
+ if (init_field_base_indices() != 0)
+ return -1;
+
+ if (init_fields_buffer() != 0)
+ return -1;
+
+ return 0;
+}
+
+int spslr_randomizer_get_target(spslr_u64 target, spslr_u64 *size,
+ spslr_u64 *fieldcnt)
+{
+ if (target >= spslr_target_cnt)
+ return -1;
+
+ const struct spslr_target *t = &spslr_targets[target];
+
+ if (size)
+ *size = t->layout->size;
+
+ if (fieldcnt)
+ *fieldcnt = t->layout->field_cnt;
+
+ return 0;
+}
+
+int spslr_randomizer_get_field(spslr_u64 target, spslr_u64 field,
+ int field_idx_mode,
+ struct spslr_randomizer_field_info *info)
+{
+ if (target >= spslr_target_cnt)
+ return -1;
+
+ if (!info)
+ return 0;
+
+ const struct spslr_target *t = &spslr_targets[target];
+
+ if (field >= t->layout->field_cnt)
+ return -1;
+
+ const struct spslr_target_field *of = NULL;
+ const struct Field *rf = NULL;
+
+ switch (field_idx_mode) {
+ case SPSLR_RANDOMIZER_FIELD_IDX_MODE_ORIGINAL:
+ of = meta_original_field(target, field);
+ rf = state_current_field(
+ target, state_current_field(target, field)->fidx);
+ break;
+ case SPSLR_RANDOMIZER_FIELD_IDX_MODE_FINAL:
+ of = meta_original_field(
+ target, state_current_field(target, field)->oidx);
+ rf = state_current_field(target, field);
+ break;
+ default:
+ return -1;
+ }
+
+ info->size = of->size;
+ info->offset = rf->offset;
+ info->initial_offset = of->offset;
+ info->alignment = of->alignment;
+ info->flags = of->flags;
+
+ return 0;
+}
+
+int __init spslr_randomizer_validate_target(spslr_u64 target)
+{
+ spslr_u64 tsize, fieldcnt;
+
+ if (spslr_randomizer_get_target(target, &tsize, &fieldcnt) < 0)
+ return -1;
+
+ spslr_u64 cur_end = 0;
+
+ for (spslr_u64 i = 0; i < fieldcnt; i++) {
+ struct spslr_randomizer_field_info finfo;
+ if (spslr_randomizer_get_field(
+ target, i, SPSLR_RANDOMIZER_FIELD_IDX_MODE_FINAL,
+ &finfo) < 0)
+ return -1;
+
+ if (finfo.alignment == 0)
+ return -1;
+
+ if (finfo.offset % finfo.alignment != 0)
+ return -1;
+
+ if ((finfo.flags & SPSLR_FLAG_FIELD_FIXED) &&
+ finfo.offset != finfo.initial_offset)
+ return -1;
+
+ if (finfo.offset > tsize)
+ return -1;
+
+ /* Zero-sized metadata entries occupy no storage. */
+ if (finfo.size == 0)
+ continue;
+
+ if (finfo.offset < cur_end)
+ return -1;
+
+ /* Avoid overflow in offset + size. */
+ if (finfo.size > tsize - finfo.offset)
+ return -1;
+
+ cur_end = finfo.offset + finfo.size;
+ }
+
+ return 0;
+}
+
+// RANDOMIZATION CODE
+
+struct ShuffleRegion {
+ spslr_u64 begin;
+ spslr_u64 end;
+ spslr_u64 fill_begin;
+ spslr_u64 fill_end;
+};
+
+static spslr_u64 rand_u64(void);
+static void get_origin_region(spslr_u64 target, spslr_u64 final_idx,
+ struct ShuffleRegion *region);
+static int option_is_valid(spslr_u64 target, spslr_u64 origin_final_idx,
+ const struct ShuffleRegion *origin,
+ spslr_u64 offset);
+static int pick_shuffle_option(spslr_u64 target, spslr_u64 origin_final_idx,
+ const struct ShuffleRegion *origin,
+ spslr_u64 alignment, spslr_u64 *selected);
+static void do_swap(spslr_u64 target, spslr_u64 origin_final_idx,
+ const struct ShuffleRegion *origin_region,
+ spslr_u64 new_offset);
+static void shuffle_one_target(spslr_u64 target);
+static void shuffle_target(spslr_u64 target);
+
+static spslr_u64 __init rand_u64(void)
+{
+ return spslr_env_random_u64();
+}
+
+static void __init get_origin_region(spslr_u64 target, spslr_u64 final_idx,
+ struct ShuffleRegion *region)
+{
+ const struct spslr_target *t = &spslr_targets[target];
+ const struct Field *rf = state_current_field(target, final_idx);
+ const struct spslr_target_field *of =
+ meta_original_field(target, rf->oidx);
+
+ region->fill_begin = rf->offset;
+ region->fill_end = region->fill_begin + of->size;
+
+ if (final_idx == 0) {
+ region->begin = 0;
+ } else {
+ const struct Field *pred_rf =
+ state_current_field(target, final_idx - 1);
+ const struct spslr_target_field *pred_of =
+ meta_original_field(target, pred_rf->oidx);
+ region->begin = pred_rf->offset + pred_of->size;
+ }
+
+ if (final_idx + 1 >= t->layout->field_cnt) {
+ region->end = t->layout->size;
+ } else {
+ const struct Field *succ_rf =
+ state_current_field(target, final_idx + 1);
+ region->end = succ_rf->offset;
+ }
+}
+
+/*
+ * Check whether a proposed field move preserves layout constraints.
+ *
+ * A move is valid only if displaced fields can be packed into the freed region
+ * without violating alignment or moving fields marked fixed.
+ */
+
+static int __init option_is_valid(spslr_u64 target, spslr_u64 origin_final_idx,
+ const struct ShuffleRegion *origin,
+ spslr_u64 offset)
+{
+ const struct spslr_target *t = &spslr_targets[target];
+ const struct spslr_target_field *origin_of = meta_original_field(
+ target, state_current_field(target, origin_final_idx)->oidx);
+
+ // When placed at offset, field will occupy [offset, option_would_end)
+ spslr_u64 option_would_end = offset + origin_of->size;
+ if (option_would_end > t->layout->size)
+ return 0;
+
+ // Field may overlap with origin region. Moving field to offset truly
frees:
+ // [true_origin_region_begin, true_origin_region_end)
+ spslr_u64 true_origin_region_begin = origin->begin;
+ spslr_u64 true_origin_region_end = origin->end;
+
+ if (offset <= origin->fill_begin &&
+ option_would_end > true_origin_region_begin)
+ true_origin_region_begin = option_would_end;
+
+ if (offset >= origin->fill_begin && offset < true_origin_region_end)
+ true_origin_region_end = offset;
+
+ // Iterate over fields in target region [offset, option_would_end] and
see if they fit into true origin region
+ spslr_u64 origin_region_ptr = true_origin_region_begin;
+ for (spslr_u64 it = 0; it < t->layout->field_cnt; it++) {
+ const struct Field *rf = state_current_field(target, it);
+ const struct spslr_target_field *of =
+ meta_original_field(target, rf->oidx);
+
+ // The field being moved does not need to go into origin region
+ if (it == origin_final_idx)
+ continue;
+
+ /*
+ * Zero-sized metadata entries occupy no storage, but they still
+ * mark an ordering boundary. A moved field must neither
straddle
+ * one nor start at one: equal-offset entries have an ordering
+ * relationship that do_swap() does not preserve while
displacing
+ * fields.
+ */
+ if (of->size == 0) {
+ if (rf->offset >= offset &&
+ rf->offset < option_would_end)
+ return 0;
+
+ continue;
+ }
+
+ // Field ends before target region -> must not be moved to
origin region
+ if (rf->offset + of->size <= offset)
+ continue;
+
+ // Field starts after target region -> must not be moved to
origin region
+ if (rf->offset >= option_would_end)
+ break;
+
+ // Fixed fields in target region unconditionally deny option
+ if (of->flags & SPSLR_FLAG_FIELD_FIXED)
+ return 0;
+
+ // Field from target region must be moved to aligned position
in origin region
+ if (origin_region_ptr % of->alignment != 0)
+ origin_region_ptr +=
+ of->alignment -
+ (origin_region_ptr % of->alignment);
+
+ origin_region_ptr += of->size;
+
+ // Field does not fit into origin region -> option not possible
+ if (origin_region_ptr > true_origin_region_end)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int __init pick_shuffle_option(spslr_u64 target,
+ spslr_u64 origin_final_idx,
+ const struct ShuffleRegion *origin,
+ spslr_u64 alignment, spslr_u64 *selected)
+{
+ const struct spslr_target *t = &spslr_targets[target];
+ spslr_u64 seen = 0;
+
+ /*
+ Note: Instead of looping over entire field array for each option, loops
can be merged into one.
+ */
+
+ for (spslr_u64 offset = 0; offset < t->layout->size;
+ offset += alignment) {
+ if (!option_is_valid(target, origin_final_idx, origin, offset))
+ continue;
+
+ // Reservoir sampling -> uniform distribution with O(1) memory
consumption
+ seen++;
+ if ((rand_u64() % seen) == 0)
+ *selected = offset;
+ }
+
+ return seen ? 0 : -1;
+}
+
+/*
+ * Move one field into a new slot and repack the fields it displaced.
+ *
+ * This is not a simple pairwise swap: structure layout has byte ranges and
+ * alignment holes, so the displaced region may contain several fields.
+ */
+
+static void __init do_swap(spslr_u64 target, spslr_u64 origin_idx,
+ const struct ShuffleRegion *origin_region,
+ spslr_u64 new_offset)
+{
+ const struct spslr_target *t = &spslr_targets[target];
+ int pulled = 0;
+
+ spslr_u64 option_fill_end = new_offset + (origin_region->fill_end -
+ origin_region->fill_begin);
+
+ spslr_u64 true_origin_region_begin = origin_region->begin;
+ if (new_offset <= origin_region->fill_begin &&
+ option_fill_end > true_origin_region_begin)
+ true_origin_region_begin = option_fill_end;
+
+ spslr_u64 origin_oidx = state_current_field(target, origin_idx)->oidx;
+
+ spslr_u64 origin_region_ptr = true_origin_region_begin;
+ for (spslr_u64 it = 0; it < t->layout->field_cnt; it++) {
+ struct Field *itf = state_current_field(target, it);
+
+ if (itf->oidx == origin_oidx)
+ continue;
+
+ const struct spslr_target_field *itof =
+ meta_original_field(target, itf->oidx);
+
+ // Zero-sized metadata entries occupy no storage.
+ if (itof->size == 0)
+ continue;
+
+ if (itf->offset + itof->size <= new_offset)
+ continue;
+
+ if (itf->offset >= option_fill_end)
+ break;
+
+ spslr_u64 falign = itof->alignment;
+ if (origin_region_ptr % falign != 0)
+ origin_region_ptr +=
+ falign - (origin_region_ptr % falign);
+
+ if (!pulled) {
+ pulled = 1;
+
+ struct Field tmp = *state_current_field(target, it);
+ *state_current_field(target, it) =
+ *state_current_field(target, origin_idx);
+ *state_current_field(target, origin_idx) = tmp;
+
+ state_current_field(target, it)->offset = new_offset;
+
+ state_current_field(target, origin_idx)->offset =
+ origin_region_ptr;
+ origin_region_ptr +=
+ meta_original_field(
+ target,
+ state_current_field(target, origin_idx)
+ ->oidx)
+ ->size;
+ continue;
+ }
+
+ {
+ struct Field tmp = *state_current_field(target, it);
+
+ if (origin_idx >= it) {
+ for (spslr_u64 pull_it = it + 1;
+ pull_it <= origin_idx; pull_it++)
+ *state_current_field(target,
+ pull_it - 1) =
+ *state_current_field(target,
+ pull_it);
+
+ *state_current_field(target, origin_idx) = tmp;
+ state_current_field(target, origin_idx)->offset
=
+ origin_region_ptr;
+ origin_region_ptr +=
+ meta_original_field(
+ target,
+ state_current_field(target,
+ origin_idx)
+ ->oidx)
+ ->size;
+
+ it--; // Must still look at the element now at
it
+ } else {
+ for (spslr_u64 pull_it = it;
+ pull_it > origin_idx + (spslr_u64)pulled;
+ pull_it--)
+ *state_current_field(target, pull_it) =
+ *state_current_field(
+ target, pull_it - 1);
+
+ *state_current_field(
+ target,
+ origin_idx + (spslr_u64)pulled) = tmp;
+ state_current_field(
+ target, origin_idx + (spslr_u64)pulled)
+ ->offset = origin_region_ptr;
+ origin_region_ptr +=
+ meta_original_field(
+ target,
+ state_current_field(
+ target,
+ origin_idx +
+ (spslr_u64)
+ pulled)
+ ->oidx)
+ ->size;
+ }
+ }
+
+ pulled++;
+ }
+
+ /*
+ * The selected destination may not overlap any other field. It may be
an
+ * empty padding gap, or it may partially overlap the origin field
itself
+ * when the field slides into adjacent padding.
+ *
+ * In either case the loop above never displaces another field and
+ * `pulled` remains zero. We still need to update the origin field's
+ * offset and reinsert it at the correct position in final-offset order
so
+ * that the field array remains sorted.
+ */
+ if (!pulled) {
+ struct Field origin = *state_current_field(target, origin_idx);
+ spslr_u64 insert_idx = t->layout->field_cnt;
+
+ for (spslr_u64 it = 0; it < t->layout->field_cnt; it++) {
+ if (it == origin_idx)
+ continue;
+
+ if (state_current_field(target, it)->offset >=
+ new_offset) {
+ insert_idx = it;
+ break;
+ }
+ }
+
+ if (insert_idx > origin_idx)
+ insert_idx--;
+
+ if (origin_idx < insert_idx) {
+ for (spslr_u64 it = origin_idx + 1; it <= insert_idx;
+ it++)
+ *state_current_field(target, it - 1) =
+ *state_current_field(target, it);
+ } else if (origin_idx > insert_idx) {
+ for (spslr_u64 it = origin_idx; it > insert_idx; it--)
+ *state_current_field(target, it) =
+ *state_current_field(target, it - 1);
+ }
+
+ *state_current_field(target, insert_idx) = origin;
+ state_current_field(target, insert_idx)->offset = new_offset;
+ }
+
+ /*
+ * Rebuild original->final mapping for this target.
+ */
+ for (spslr_u64 final_idx = 0; final_idx < t->layout->field_cnt;
+ final_idx++) {
+ struct Field *rf = state_current_field(target, final_idx);
+ state_current_field(target, rf->oidx)->fidx = final_idx;
+ }
+}
+
+/*
+Note: final version should not shuffle random fields but try to shuffle each
original field idx once
+*/
+static void __init shuffle_one_target(spslr_u64 target)
+{
+ const struct spslr_target *t = &spslr_targets[target];
+ if (t->layout->field_cnt == 0)
+ return;
+
+ spslr_u64 origin_final_idx = rand_u64() % t->layout->field_cnt;
+ struct Field *origin_rf = state_current_field(target, origin_final_idx);
+ const struct spslr_target_field *origin_of =
+ meta_original_field(target, origin_rf->oidx);
+
+ /* Zero-sized entries are metadata markers, not shuffleable storage. */
+ if (origin_of->size == 0)
+ return;
+
+ if (origin_of->flags & SPSLR_FLAG_FIELD_FIXED)
+ return;
+
+ struct ShuffleRegion origin_region;
+ spslr_u64 selected_option;
+
+ get_origin_region(target, origin_final_idx, &origin_region);
+
+ if (pick_shuffle_option(target, origin_final_idx, &origin_region,
+ origin_of->alignment, &selected_option) < 0)
+ return;
+
+ do_swap(target, origin_final_idx, &origin_region, selected_option);
+}
+
+static void __init shuffle_target(spslr_u64 target)
+{
+ const struct spslr_target *t = &spslr_targets[target];
+ spslr_u64 shuffle_count = t->layout->field_cnt * 2;
+
+ for (spslr_u64 i = 0; i < shuffle_count; i++)
+ shuffle_one_target(target);
+
+ sanemaker_finish_layout(state_current_field_base(target), t->hash);
+}
+
+int __init spslr_randomize(void)
+{
+ if (!fields)
+ return -1;
+
+ for (spslr_u64 tidx = 0; tidx < spslr_target_cnt; tidx++)
+ shuffle_target(tidx);
+
+ return 0;
+}
diff --git a/kernel/spslr/spslr_randomizer.h b/kernel/spslr/spslr_randomizer.h
new file mode 100644
index 000000000000..c360750894e7
--- /dev/null
+++ b/kernel/spslr/spslr_randomizer.h
@@ -0,0 +1,29 @@
+#ifndef SPSLR_RANDOMIZER_H
+#define SPSLR_RANDOMIZER_H
+
+#include "spslr_env.h"
+#include "pinpoint.h"
+
+#define SPSLR_RANDOMIZER_FIELD_IDX_MODE_ORIGINAL 1
+#define SPSLR_RANDOMIZER_FIELD_IDX_MODE_FINAL 2
+
+struct spslr_randomizer_field_info {
+ spslr_u64 size;
+ spslr_u64 offset;
+ spslr_u64 initial_offset;
+ spslr_u64 alignment;
+ spslr_u64 flags;
+};
+
+int spslr_randomizer_init(void);
+int spslr_randomize(void);
+
+int spslr_randomizer_get_target(spslr_u64 target, spslr_u64 *size,
+ spslr_u64 *fieldcnt);
+int spslr_randomizer_get_field(spslr_u64 target, spslr_u64 field,
+ int field_idx_mode,
+ struct spslr_randomizer_field_info *info);
+
+int spslr_randomizer_validate_target(spslr_u64 target);
+
+#endif
--
2.43.0