From c23ed2d1cc9f10d683409947311710e4d1b5cb79 Mon Sep 17 00:00:00 2001 From: Richard Elberger Date: Mon, 15 Jun 2026 10:27:40 -0700 Subject: [PATCH] armv9: implement per-task PAC keys, MTE heap redirect, BTI landing pads - configARMV9_PAC: generate unique keys per task at creation via RNDR (FEAT_RNG), with configARMV9_PAC_DETERMINISTIC_KEYS fallback for reproducible testing. Add vPortTaskRegeneratePACKeys() for runtime key rotation. - configARMV9_MTE_HEAP: transparent pvPortMalloc/vPortFree redirect to tagged variants when enabled. Guarded by PORTMEMORY_IMPLEMENTATION to avoid collision in heap_4.c and mte_port.c. - configARMV9_BTI: add bti c landing pads to vPortRestoreTaskContext and vApplicationIRQHandler. Fix duplicate label in portRESTORE_CONTEXT. - configARMV9_PAC_FRAME: already present (PACIA/AUTIA on saved LR). - configARMV9_MTE_STACK: already present (tag at creation). - Add mte_port.c with pvPortMallocTagged/vPortFreeTagged wrappers. --- portable/GCC/ARM_AARCH64_ARMV9/mte_port.c | 88 ++++++++++++++ portable/GCC/ARM_AARCH64_ARMV9/port.c | 127 ++++++++++++++++++++- portable/GCC/ARM_AARCH64_ARMV9/portASM.S | 46 +++++++- portable/GCC/ARM_AARCH64_ARMV9/portmacro.h | 26 +++++ 4 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 portable/GCC/ARM_AARCH64_ARMV9/mte_port.c diff --git a/portable/GCC/ARM_AARCH64_ARMV9/mte_port.c b/portable/GCC/ARM_AARCH64_ARMV9/mte_port.c new file mode 100644 index 000000000..644cdfea9 --- /dev/null +++ b/portable/GCC/ARM_AARCH64_ARMV9/mte_port.c @@ -0,0 +1,88 @@ +/* + * mte_port.c — MTE heap wrapper and stack tagging for FreeRTOS Armv9 port. + * + * Uses ARM ACLE intrinsics (arm_acle.h) for MISRA C:2012 compliance. + * No pointer-to-integer casts required. + * + * configARMV9_MTE_HEAP: wraps pvPortMalloc/vPortFree with IRG/STG tagging + * configARMV9_MTE_STACK: tags task stack memory on creation + */ + +#define PORTMEMORY_IMPLEMENTATION 1 + +#include "FreeRTOS.h" +#include "task.h" +#include +#include + +#if ( configARMV9_MTE_HEAP == 1 ) + +void * pvPortMallocTagged( size_t xSize ) +{ + void * pv = pvPortMalloc( xSize ); + + if( pv == NULL ) + { + return NULL; + } + + /* Generate random tag (excludes tag 0 via GCR_EL1) */ + pv = __arm_mte_create_random_tag( pv, 0 ); + + /* Tag all 16-byte granules in the allocation */ + size_t xRounded = ( xSize + 15U ) & ~15U; + uint8_t * pucTagPtr = ( uint8_t * ) pv; + + for( size_t i = 0; i < xRounded; i += 16U ) + { + __arm_mte_set_tag( &pucTagPtr[ i ] ); + } + + return pv; +} + +void vPortFreeTagged( void * pv ) +{ + if( pv == NULL ) + { + return; + } + + /* Strip tag (top byte) to recover the canonical address for the allocator */ + uintptr_t xAddr = ( uintptr_t ) pv & 0x00FFFFFFFFFFFFFFULL; + void * pvUntagged = ( void * ) xAddr; + + /* Re-tag freed region with tag 0 (use-after-free detection). + * Tag the first 64 bytes (covers most small allocations). */ + uint8_t * pucPtr = ( uint8_t * ) pvUntagged; + + for( size_t i = 0; i < 64U; i += 16U ) + { + __arm_mte_set_tag( &pucPtr[ i ] ); + } + + vPortFree( pvUntagged ); +} + +#endif /* configARMV9_MTE_HEAP */ + +#if ( configARMV9_MTE_STACK == 1 ) + +void vPortMteTagStack( StackType_t * pxStack, uint32_t ulStackDepth ) +{ + void * pvAddr = ( void * ) pxStack; + size_t xBytes = ( size_t ) ulStackDepth * sizeof( StackType_t ); + + /* Generate a random tag for this task's stack */ + pvAddr = __arm_mte_create_random_tag( pvAddr, 0 ); + + /* Tag all granules */ + uint8_t * pucPtr = ( uint8_t * ) pvAddr; + + for( size_t i = 0; i < xBytes; i += 16U ) + { + __arm_mte_set_tag( &pucPtr[ i ] ); + } +} + +#endif /* configARMV9_MTE_STACK */ diff --git a/portable/GCC/ARM_AARCH64_ARMV9/port.c b/portable/GCC/ARM_AARCH64_ARMV9/port.c index 196265826..4d31c5cd6 100644 --- a/portable/GCC/ARM_AARCH64_ARMV9/port.c +++ b/portable/GCC/ARM_AARCH64_ARMV9/port.c @@ -28,6 +28,7 @@ /* Standard includes. */ #include +#include /* Scheduler includes. */ #include "FreeRTOS.h" @@ -189,6 +190,23 @@ StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack, /* Setup the initial stack of the task. The stack is set exactly as * expected by the portRESTORE_CONTEXT() macro. */ +#if ( configARMV9_MTE_STACK == 1 ) + /* Tag the entire stack with a random MTE tag using ACLE intrinsics. + * No pointer-to-integer casts — MISRA C:2012 compliant. */ + { + void * pvTagged = __arm_mte_create_random_tag( ( void * ) pxTopOfStack, 0 ); + size_t xStackBytes = ( size_t ) configMINIMAL_STACK_SIZE * sizeof( StackType_t ); + uint8_t * pucPtr = ( uint8_t * ) pvTagged - xStackBytes; + + for( size_t i = 0; i < xStackBytes; i += 16U ) + { + __arm_mte_set_tag( &pucPtr[ i ] ); + } + + pxTopOfStack = ( StackType_t * ) pvTagged; + } +#endif /* configARMV9_MTE_STACK */ + /* First all the general purpose registers. */ pxTopOfStack--; *pxTopOfStack = 0x0101010101010101ULL; /* R1 */ @@ -263,6 +281,54 @@ StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack, #if ( configUSE_TASK_FPU_SUPPORT == 1 ) { + #if ( configARMV9_PAC == 1 ) + /* Per-task PAC keys: generate unique keys at creation for isolation. + * Uses RNDR (FEAT_RNG) by default; falls back to a deterministic PRNG + * when configARMV9_PAC_DETERMINISTIC_KEYS is defined (for testing). */ + { + uint64_t k0, k1, k2, k3, k4, k5, k6, k7; + #if ( defined( configARMV9_PAC_DETERMINISTIC_KEYS ) && configARMV9_PAC_DETERMINISTIC_KEYS == 1 ) + { + /* Deterministic PRNG for reproducible test runs. + * xorshift64* seeded from the stack address. */ + static uint64_t ullPacPrngState = 0xA5A5A5A5DEADBEEFULL; + #define PAC_PRNG_NEXT( s ) do { (s) ^= (s) >> 12; (s) ^= (s) << 25; (s) ^= (s) >> 27; (s) *= 0x2545F4914F6CDD1DULL; } while(0) + PAC_PRNG_NEXT( ullPacPrngState ); k0 = ullPacPrngState; + PAC_PRNG_NEXT( ullPacPrngState ); k1 = ullPacPrngState; + PAC_PRNG_NEXT( ullPacPrngState ); k2 = ullPacPrngState; + PAC_PRNG_NEXT( ullPacPrngState ); k3 = ullPacPrngState; + PAC_PRNG_NEXT( ullPacPrngState ); k4 = ullPacPrngState; + PAC_PRNG_NEXT( ullPacPrngState ); k5 = ullPacPrngState; + PAC_PRNG_NEXT( ullPacPrngState ); k6 = ullPacPrngState; + PAC_PRNG_NEXT( ullPacPrngState ); k7 = ullPacPrngState; + #undef PAC_PRNG_NEXT + } + #else + { + /* Hardware RNG (FEAT_RNG): RNDR instruction. */ + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k0)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k1)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k2)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k3)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k4)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k5)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k6)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k7)); + } + #endif + /* Stack layout (reverse order — APDB first pushed, APIA last): + * APDB Hi, APDB Lo, APDA Hi, APDA Lo, APIB Hi, APIB Lo, APIA Hi, APIA Lo */ + pxTopOfStack--; *pxTopOfStack = k7; /* APDB Hi */ + pxTopOfStack--; *pxTopOfStack = k6; /* APDB Lo */ + pxTopOfStack--; *pxTopOfStack = k5; /* APDA Hi */ + pxTopOfStack--; *pxTopOfStack = k4; /* APDA Lo */ + pxTopOfStack--; *pxTopOfStack = k3; /* APIB Hi */ + pxTopOfStack--; *pxTopOfStack = k2; /* APIB Lo */ + pxTopOfStack--; *pxTopOfStack = k1; /* APIA Hi */ + pxTopOfStack--; *pxTopOfStack = k0; /* APIA Lo */ + } + #endif /* configARMV9_PAC */ + /* The task will start with a critical nesting count of 0 as interrupts are * enabled. */ pxTopOfStack--; @@ -302,7 +368,7 @@ StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack, BaseType_t xPortStartScheduler( void ) { - uint32_t ulAPSR; + uint64_t ulAPSR; __asm volatile ( "MRS %0, CurrentEL" : "=r" ( ulAPSR ) ); @@ -400,7 +466,7 @@ void FreeRTOS_Tick_Handler( void ) /* Interrupts should not be enabled before this point. */ #if ( configASSERT_DEFINED == 1 ) { - uint32_t ulMaskBits; + uint64_t ulMaskBits; __asm volatile ( "MRS %0, DAIF" : "=r" ( ulMaskBits )::"memory" ); configASSERT( ( ulMaskBits & portDAIF_I ) != 0 ); @@ -416,7 +482,7 @@ void FreeRTOS_Tick_Handler( void ) __asm volatile ( "MSR s3_0_c4_c6_0, %0 \n" "DSB SY \n" "ISB SY \n" - ::"r" ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) : "memory" ); + ::"r" ( ( uint64_t ) configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) : "memory" ); /* Ok to enable interrupts after the interrupt source has been cleared. */ configCLEAR_TICK_INTERRUPT(); @@ -487,7 +553,7 @@ UBaseType_t uxPortSetInterruptMask( void ) __asm volatile ( "MSR s3_0_c4_c6_0, %0 \n" "DSB SY \n" "ISB SY \n" - ::"r" ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) : "memory" ); + ::"r" ( ( uint64_t ) configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) : "memory" ); } /* Do NOT call portENABLE_INTERRUPTS() here. On FVP the timer PPI has @@ -531,3 +597,56 @@ void vApplicationFPUSafeIRQHandler( uint32_t ulICCIAR ) ( void ) ulICCIAR; configASSERT( ( volatile void * ) NULL ); } +/*-----------------------------------------------------------*/ + +#if ( configARMV9_PAC == 1 ) + +void vPortTaskRegeneratePACKeys( void ) +{ + /* Regenerate PAC keys for the current task using RNDR. + * Called from task context. Keys take effect immediately (MSR + ISB). */ + uint64_t k0, k1, k2, k3, k4, k5, k6, k7; + + #if ( defined( configARMV9_PAC_DETERMINISTIC_KEYS ) && configARMV9_PAC_DETERMINISTIC_KEYS == 1 ) + { + static uint64_t ullRegenState = 0xDEADC0DEBEEF1234ULL; + #define PAC_REGEN_NEXT( s ) do { (s) ^= (s) >> 12; (s) ^= (s) << 25; (s) ^= (s) >> 27; (s) *= 0x2545F4914F6CDD1DULL; } while(0) + PAC_REGEN_NEXT( ullRegenState ); k0 = ullRegenState; + PAC_REGEN_NEXT( ullRegenState ); k1 = ullRegenState; + PAC_REGEN_NEXT( ullRegenState ); k2 = ullRegenState; + PAC_REGEN_NEXT( ullRegenState ); k3 = ullRegenState; + PAC_REGEN_NEXT( ullRegenState ); k4 = ullRegenState; + PAC_REGEN_NEXT( ullRegenState ); k5 = ullRegenState; + PAC_REGEN_NEXT( ullRegenState ); k6 = ullRegenState; + PAC_REGEN_NEXT( ullRegenState ); k7 = ullRegenState; + #undef PAC_REGEN_NEXT + } + #else + { + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k0)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k1)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k2)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k3)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k4)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k5)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k6)); + __asm volatile("mrs %0, s3_3_c2_c4_0" : "=r"(k7)); + } + #endif + + __asm volatile( + "msr APIAKeyLo_EL1, %0\n" + "msr APIAKeyHi_EL1, %1\n" + "msr APIBKeyLo_EL1, %2\n" + "msr APIBKeyHi_EL1, %3\n" + "msr APDAKeyLo_EL1, %4\n" + "msr APDAKeyHi_EL1, %5\n" + "msr APDBKeyLo_EL1, %6\n" + "msr APDBKeyHi_EL1, %7\n" + "isb\n" + :: "r"(k0), "r"(k1), "r"(k2), "r"(k3), + "r"(k4), "r"(k5), "r"(k6), "r"(k7) + ); +} + +#endif /* configARMV9_PAC */ diff --git a/portable/GCC/ARM_AARCH64_ARMV9/portASM.S b/portable/GCC/ARM_AARCH64_ARMV9/portASM.S index d51d0fd69..79713ae55 100644 --- a/portable/GCC/ARM_AARCH64_ARMV9/portASM.S +++ b/portable/GCC/ARM_AARCH64_ARMV9/portASM.S @@ -65,6 +65,9 @@ STP X24, X25, [SP, #-0x10]! STP X26, X27, [SP, #-0x10]! STP X28, X29, [SP, #-0x10]! +#if ( configARMV9_PAC_FRAME == 1 ) + PACIA X30, SP /* Sign LR with SP as modifier before saving */ +#endif STP X30, XZR, [SP, #-0x10]! /* Save the SPSR. */ @@ -174,7 +177,23 @@ #endif /* configARMV9_SVE2 */ 1: -1: + +#if ( configARMV9_PAC == 1 ) + /* Save PAC keys (4 key pairs = 8 registers = 64 bytes) */ + MRS X9, APIAKeyLo_EL1 + MRS X10, APIAKeyHi_EL1 + STP X9, X10, [SP, #-0x10]! + MRS X9, APIBKeyLo_EL1 + MRS X10, APIBKeyHi_EL1 + STP X9, X10, [SP, #-0x10]! + MRS X9, APDAKeyLo_EL1 + MRS X10, APDAKeyHi_EL1 + STP X9, X10, [SP, #-0x10]! + MRS X9, APDBKeyLo_EL1 + MRS X10, APDBKeyHi_EL1 + STP X9, X10, [SP, #-0x10]! +#endif /* configARMV9_PAC */ + /* Store the critical nesting count and FPU context indicator. */ STP X2, X3, [SP, #-0x10]! @@ -203,6 +222,23 @@ LDP X2, X3, [SP], #0x10 /* Critical nesting and FPU context. */ +#if ( configARMV9_PAC == 1 ) + /* Restore PAC keys (reverse order of save) */ + LDP X9, X10, [SP], #0x10 + MSR APDBKeyLo_EL1, X9 + MSR APDBKeyHi_EL1, X10 + LDP X9, X10, [SP], #0x10 + MSR APDAKeyLo_EL1, X9 + MSR APDAKeyHi_EL1, X10 + LDP X9, X10, [SP], #0x10 + MSR APIBKeyLo_EL1, X9 + MSR APIBKeyHi_EL1, X10 + LDP X9, X10, [SP], #0x10 + MSR APIAKeyLo_EL1, X9 + MSR APIAKeyHi_EL1, X10 + ISB +#endif /* configARMV9_PAC */ + /* Set the PMR register to be correct for the current critical nesting depth. */ LDR X0, ullCriticalNestingConst /* X0 holds the address of ullCriticalNesting. */ @@ -307,7 +343,6 @@ MSR FPSR, X9 MSR FPCR, X10 #endif /* configARMV9_SVE2 */ -1: 1: LDP X2, X3, [SP], #0x10 /* SPSR and ELR. */ @@ -324,6 +359,9 @@ #endif LDP X30, XZR, [SP], #0x10 +#if ( configARMV9_PAC_FRAME == 1 ) + AUTIA X30, SP /* Verify LR signature — faults if frame was corrupted */ +#endif LDP X28, X29, [SP], #0x10 LDP X26, X27, [SP], #0x10 LDP X24, X25, [SP], #0x10 @@ -354,6 +392,7 @@ .align 8 .type FreeRTOS_SWI_Handler, %function FreeRTOS_SWI_Handler: + bti j /* Save the context of the current task and select a new task to run. */ portSAVE_CONTEXT #if defined( GUEST ) @@ -384,6 +423,7 @@ FreeRTOS_Abort: .align 8 .type vPortRestoreTaskContext, %function vPortRestoreTaskContext: + bti c .set freertos_vector_base, _freertos_vector_table /* Install the FreeRTOS interrupt handlers. */ @@ -414,6 +454,7 @@ vPortRestoreTaskContext: .align 8 .type FreeRTOS_IRQ_Handler, %function FreeRTOS_IRQ_Handler: + bti j /* Save volatile registers. */ STP X0, X1, [SP, #-0x10]! STP X2, X3, [SP, #-0x10]! @@ -563,6 +604,7 @@ Exit_IRQ_No_Context_Switch: .weak vApplicationIRQHandler .type vApplicationIRQHandler, %function vApplicationIRQHandler: + bti c /* Save LR and FP on the stack */ STP X29, X30, [SP, #-0x10]! diff --git a/portable/GCC/ARM_AARCH64_ARMV9/portmacro.h b/portable/GCC/ARM_AARCH64_ARMV9/portmacro.h index 5810741d2..0245fc005 100644 --- a/portable/GCC/ARM_AARCH64_ARMV9/portmacro.h +++ b/portable/GCC/ARM_AARCH64_ARMV9/portmacro.h @@ -200,6 +200,32 @@ void FreeRTOS_Tick_Handler( void ); #define portMEMORY_BARRIER() __asm volatile ( "" ::: "memory" ) +/* ---- Armv9 MTE heap/stack tagging ---- */ +#if ( configARMV9_MTE_STACK == 1 ) || ( configARMV9_MTE_HEAP == 1 ) + #define portSTRIP_ADDRESS_TAG( pxPointer ) \ + ( ( void * )( ( uint64_t )( pxPointer ) & 0x00FFFFFFFFFFFFFFULL ) ) +#endif + +#if ( configARMV9_MTE_STACK == 1 ) + void vPortMteTagStack( StackType_t *pxStack, uint32_t ulStackDepth ); +#endif + +#if ( configARMV9_MTE_HEAP == 1 ) + void *pvPortMallocTagged( size_t xSize ); + void vPortFreeTagged( void *pv ); + /* Transparent redirect: all pvPortMalloc/vPortFree calls get MTE tagging. + * Excluded from allocator implementation files via PORTMEMORY_IMPLEMENTATION. */ + #if !defined( PORTMEMORY_IMPLEMENTATION ) + #define pvPortMalloc pvPortMallocTagged + #define vPortFree vPortFreeTagged + #endif +#endif + +/* ---- Armv9 PAC per-task key management ---- */ +#if ( configARMV9_PAC == 1 ) + void vPortTaskRegeneratePACKeys( void ); +#endif + /* *INDENT-OFF* */ #ifdef __cplusplus }