首页 > 解决方案 > 为什么不管被中断的指令如何,我的 ARM 中断处理程序中的 IRQ 延迟总是相同的?

问题描述

我正在尝试应用我在本文中读到的一种侧信道攻击,它试图从具有 cortex M4 处理器的 MCU 上的 IRQ 延迟差异推断执行状态。攻击会小心地中断发生在分支之后的指令并测量中断延迟。当不同的分支有不同长度的指令时,您可以查看中断延迟以确定中断发生在这些分支中的哪个分支并泄漏一些程序状态。

我写了一个简单的函数,我想以上述方式进行攻击。我正在使用 SysTick 计时器在正确的时间点生成中断。为了获得中断计时器的初始良好值,我使用 GDB 在目标行停止程序以查看当时的 SysTick 值。

我实现了一个非常简单的中断处理程序

  1. 从内存中加载 SysTick 计时器值
  2. 从重载值中减去这个值,得到中断后经过的时间(即 IRQ 延迟)
  3. 清除中断和
void __attribute__((interrupt("IRQ"))) SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
    SysTick->CTRL &= 0xfffffffe;                                // disable SysTick (~SysTick_CTRL_ENABLE_Msk)
    *timer_value = SysTick->VAL;                                // capture counter value (as quickly as possible)
    *timer_value = SysTick->LOAD - *timer_value;                    // subtract it from reload value to get IRQ latency
    SysTick->VAL = 0;                                           // reset initial value
}   

但是,我发现无论中断的指令如何,我总是得到相同的 IRQ 延迟。当更长的指令被中断时,我预计中断延迟会更长。

这是我为测试攻击而编写的函数

extern uint32_t *timer_value;
int sample_function(int *a, int *b){
    /*
     * function description -- store the smallest of the two value in a, if MEASURE_CYCLESS defined return the number
     * of clock cycles that have been elapsed since the timer has been started
     * r0 contains pointer to a
     * r1 contains pointer to b
     */

    __asm volatile(
        /*  push working registers */
        "PUSH {r4-r8} \n"
        /* move counter into r8 */
        "MOV r8, #10 \n"
        /* begin loop */
        "begin_loop: \n"
        /* decrement counter variable*/
        "SUB r8, r8, #1 \n"
        /* if counter variable not equal to 0, jump back to start of loop */
        "CMP r8, #0 \n"
        /* if r8 not equal to 0, jump back to begin of loop*/
        "BNE begin_loop \n"
        /* load a into r2 */
        "LDR r2, [r0] \n"
        /* load b into r3 */
        "LDR r3, [r1] \n"
        /*  store a-b in r4, setting status flags -- if result is 0 Z flag is set */
        "SUBS r4, r2, r3 \n"
        /* if a-b positive, a is larger  otherwise, b is larger (assuming a not equal to b)  */
        "BPL a_larger \n"
#ifdef SPY
        /* load address of (*timer_value) into r4 -- use of LDR pseudo-instruction places constant in a literal pool*/
        "LDR r4, =timer_value \n"
        /* Load (*timer_value) into r4 */
        "LDR r4, [r4] \n"
        /* load address of Systick VAL into r5 */
        "LDR r5, =0xe000e018 \n"
        /* Load value at address stored in R5 (= Systick Val) */
        "LDR r5, [r5] \n"
        /* Move Systick Val into adress stored at r4 (= *timer_value = address of timer_value)*/
        "STR r5, [r4] \n"
#endif
        "NOP \n"
        /*instruction that gets interrupted -- swap value*/
        "STR r2, [r1] \n"
        /* load value at this address into r0 (return value) */
        "STR r3, [r0] \n"
        "B end \n"
        "a_larger: \n"
        "MOV r0, #0 \n"              // instruction that gets interrupted
        "end: POP    {r4-r8}"
            );     // pop working registers
}

注意,#define块中的代码部分用于自动确定一个好的计时器重载值(而不是使用 GDB),但我目前没有使用我通过这种方式获得的值。我那里还有一个空循环来延迟应该被打断的指令。

被中断的指令是#define块之后的指令。当我删除NOP指令时,我仍然得到相同的中断延迟。当我增加或减少计时器值(提前或推迟一些周期中断)时,我仍然会得到相同的 IRQ 延迟。

我在这里错过了什么吗?是否有一些我不知道的行为?此外,将属性__attribute__((interrupt("IRQ"))用于中断处理程序是否重要?

标签: carm

解决方案


这是我一直在思考和评论的。

引导程序

.thumb_func
reset:
    bl notmain
    ldr r4,=0xE000E018
    ldr r0,=0xE000E010
    mov r1,#7
    str r1,[r0]
    b hang
.thumb_func
hang:   
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    b hang

设置 uart 和 systick

void notmain ( void )
{
    uart_init();
    hexstring(0x12345678);
    
    PUT32(STK_CSR,4);
    PUT32(STK_RVR,0xF40000);
    PUT32(STK_CVR,0x00000000);
    //PUT32(STK_CSR,7);
}

事件处理程序

.thumb_func
.globl systick_handler
systick_handler:
    ldr r0,[r4]
    ldr r5,[sp,#0x18]
    push {r0,lr}
    bl hexstrings
    mov r0,r5
    bl hexstring
    pop {r0,pc}

获取中断指令的定时器和地址并打印出来。

00F3FFF4 08000054 
00F3FFF4 08000056 
00F3FFF4 08000058 
00F3FFF4 0800005A 
00F3FFF4 0800005C 
00F3FFF4 0800005E 
00F3FFF4 08000054 
00F3FFF4 08000056 
00F3FFF4 08000058 
00F3FFF4 0800005A 
00F3FFF4 08000050 


08000050 <hang>:
 8000050:   bf00        nop
 8000052:   bf00        nop
 8000054:   bf00        nop
 8000056:   bf00        nop
 8000058:   bf00        nop
 800005a:   bf00        nop
 800005c:   bf00        nop
 800005e:   e7f7        b.n 8000050 <hang>

来自 ARM 的文档。

中断延迟

当正在访问的存储器没有应用等待状态时,从断言中断到执行 ISR 的第一条指令最多有 12 个周期的延迟。当实现 FPU 选项并且浮点上下文处于活动状态并且未启用延迟堆栈时,此最大延迟将增加到 29 个周期。要执行的第一条指令与堆栈推送并行获取。

最后一行我们也许可以在这里看到。您可以尝试各种指令,但这种架构能够重新启动长时间指令(读取和推送/弹出、乘法等)。我认为要看到您可能需要创建总线或共享资源争用(与指令相比)的延迟差异很大

systick 也是一个例外而不是中断,因此在延迟方面可能存在一些差异。


推荐阅读