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.
This commit is contained in:
Richard Elberger 2026-06-15 10:27:40 -07:00
parent c985868d34
commit c23ed2d1cc
4 changed files with 281 additions and 6 deletions

View file

@ -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 <stdint.h>
#include <arm_acle.h>
#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 */

View file

@ -28,6 +28,7 @@
/* Standard includes. */
#include <stdlib.h>
#include <arm_acle.h>
/* 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 */

View file

@ -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]!

View file

@ -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
}