JeremyGUILLAUME/CVA6_cache_locking

GitHub: JeremyGUILLAUME/CVA6_cache_locking

该项目在 CVA6 RISC-V 处理器的数据缓存上实现了基于分区的缓存锁定机制,作为防御缓存侧信道攻击的硬件级对策。

Stars: 1 | Forks: 0

# 背景 这项工作专注于分区技术,这是一种针对基于缓存的侧信道攻击的防御策略。 它包括在缓存中将敏感进程与非可信进程进行隔离,以防止它们的数据与攻击者的数据发生冲突,预计这会使缓存攻击变得极其困难,甚至无法实现。 Wang 等人提出了 PLcache [1],一种基于分区的防御措施。 然而,后续的研究表明,PLcache 仍然容易受到缓存攻击 [2]。 为了解决这些局限性,Gaudin 等人 [2] 引入了一种细粒度的缓存锁定机制。 该机制引入了两条指令,用于在缓存中锁定和解锁敏感数据。 它的实现依赖于修改缓存替换策略。 从 LRU 策略开始,引入了一个额外的状态来将缓存行标记为锁定状态,从而在锁定处于活动状态时防止它们被驱逐。 为了证明这种锁定机制的安全有效性,它在一个简单的 cv32e40p RISC-V 内核上进行了模拟。 本项目的目标是在更复杂的、能够运行 Linux 等操作系统的处理器上展示这种防御措施的可行性,以便在更真实的条件下研究基于缓存的攻击。 为此选择了 OpenHW Group 的 CVA6 处理器,因为它是一个成熟且文档齐全的 RISC-V 实现。 它具有一个 8 路组相联数据缓存,并采用随机替换策略。 # CVA6 锁定机制概述 cv32e40p 和 CVA6 之间的主要区别在于缓存替换策略 (RP)。 这种差异尤为重要,因为在 cv32e40p 上实现的原始锁定机制依赖于 LRU 替换策略,而 CVA6 实现的是随机替换策略。 为了在 CVA6 上实现此机制,我们提出了一种解决方案,即为每个缓存行添加一个锁定位,以指示其处于锁定状态。 第一个挑战是在处理 CVA6 中使用的随机替换策略的同时,确保锁定的缓存行永远不会被选中进行驱逐。 另一个困难是 CVA6 更高的架构复杂性,它具有六级流水线和部分乱序执行的能力。 因此,实现该机制需要研究用 SystemVerilog 实现的 CVA6 的内部结构,以确保锁定请求信号与解码后的指令一起在流水线中正确传播,并且确保正确地锁定(和解锁)缓存行。 ## 集成到缓存子系统中 下图展示了为集成锁定机制而对 CVA6 数据缓存所做的修改。 与加载请求类似,锁定(或解锁)请求由控制单元处理,控制单元会检查数据是否存在于缓存中(缓存命中或未命中)。 在发生缓存未命中的情况下,请求被转发给未命中单元,该单元从主内存中读取数据并将其存入缓存,同时将锁定位设置为 1(对于解锁请求则设置为 0)。 在发生缓存命中的情况下,控制单元会检查锁定位的当前值是否与锁定(或解锁)请求匹配。 如果不匹配,则强制让该请求通过未命中单元,以便用请求的值更新锁定位。 为了处理锁定位,在未命中单元中添加了一个锁定处理单元。 ![Image 1](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/ef4800b7a7083113.png) ## 锁定处理单元 为了处理存在锁定缓存行情况下的随机替换策略 (RP),我们在未命中单元中添加了一个锁定处理单元。 该单元确保选择一个未锁定的路进行替换。 此外,在收到锁定请求的情况下,它会检查是否允许锁定,即在锁定操作之后,给定组中是否至少有一条路保持未锁定状态。 锁定处理单元的原理图如下图所示: ![Image 2](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/cf3a79074c083119.png) 在最初的设计中,LFSR 生成 3 个随机位,用作随机选择进行替换的路的索引。 在我们的设计中,LFSR 被扩展为生成 13 个随机位(图中的 "LFSR_13"):4 个随机路索引 + 1 个随机位。 锁定处理单元首先测试 4 个随机索引中是否有一个指向未锁定的路。 为此,4 个多路复用器在其输入端获取锁定位的值 ("lck_bits"),并在输出端返回各自随机索引对应的锁定位。 LZC-4 选择第一个未锁定的索引。 如果它们全未解锁,则信号 "none" 等于 1,并选择 LZC-8 单元的输出。 这里有两个 LZC 单元,第一个选择等于 0(即未锁定)的最低有效位 (lsb)。 另一个选择等于 0 的最高有效位 (msb)。 来自 LFSR_13 的最后一个随机位用于在 msb 和 lsb 之间进行选择。 为了检查是否允许锁定,我们验证在执行锁定的情况下,lock_bits 中是否至少有一个位将保持为 0。 如果是,则将 "lock_allowed" 位设置为 1。 如原理图所示,这可以通过检查 LZC-8 单元(包括 lsb 和 msb 单元)是否返回相同的索引来简化;如果相同,则意味着只有一个缓存行 (CL) 仍未锁定,因此不允许锁定。 在锁定处理单元的输出端,我们得到了随机选择的未锁定路的索引 ("repl_way"),以及是否允许锁定 ("lock_allowed")。 # 实现 在本节中,我们描述为了包含锁定机制而对 CVA6 所做的修改。 它已在以下 CVA6 版本上实现并经过测试:2025 年 1 月 15 日的 v5.2.0 (cb5c623) 需要修改的 CVA6 部分以红色高亮显示: ![Image 3](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/21cf90dd20083127.png) 解码器经过修改以处理自定义指令:Custom0,用于处理锁定和解锁指令。 它们的处理方式与加载指令类似,但增加了一个由缓存子系统处理的锁定请求 ("lck_req")。 在缓存子系统中,对缓存存储器进行了修改,将锁定位集成到每个缓存行中。 在锁定请求期间,即使发生缓存命中,我们也会检查锁定位是否与请求的锁定状态相匹配。 如果不匹配,则强制通过未命中单元(然后是锁定处理单元)对其进行更新。 锁定位由未命中单元存储在缓存中,与缓存的数据和元数据(tag、valid 等)放在一起。 表 1 总结了 CVA6 上锁定机制的面积开销,并与最初的 cv32e40p 上的开销进行了比较。 ![Table 1](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/446052e61b083130.png) ## 修改的文件列表 ``` core/cva6.sv core/include/ariane_pkg.sv core/decoder.sv core/load_unit.sv core/cache_subsystem/wt_dcache.sv core/cache_subsystem/wt_dcache_mem.sv core/cache_subsystem/wt_dcache_ctrl.sv core/cache_subsystem/wt_dcache_missunit.sv ``` ## 修改详情 ### cva6.sv 在 localparam 类型 dcache_req_i_t 中添加 "cache_lck_req": ``` logic [1:0] cache_lck_req; // line 210 ``` ### ariane_pkg.sv 在列表中添加两种操作类型: ``` CACHE_LCK, // line 327 CACHE_ULCK, // line 328 ``` ### decoder.sv 实现 Custom0 指令以处理锁定和解锁指令: ``` // -------------------------------- // Custom // -------------------------------- riscv::OpcodeCustom0: begin // line 1072 instruction_o.fu = LOAD; imm_select = IIMM; instruction_o.rs1 = instr.itype.rs1; instruction_o.rd = instr.itype.rd; // LOCK or UNLOCK unique case (instr.itype.funct3) 3'b000: instruction_o.op = ariane_pkg::CACHE_LCK; //Lock 3'b001: instruction_o.op = ariane_pkg::CACHE_ULCK; //Unlock default: illegal_instr = 1'b1; endcase if (CVA6Cfg.RVH) begin tinst = {17'b0, instr.itype.funct3, instr.itype.rd, instr.itype.opcode}; tinst[1] = is_compressed_i ? 1'b0 : 'b1; end end ``` ### load_unit.sv 在收到锁定或解锁请求时,处理来自 "dcache_req_i_t" 的 "cache_lck_req" 信号: ``` assign req_port_o.cache_lck_req[0] = lsu_ctrl_i.operation inside {ariane_pkg::CACHE_LCK, ariane_pkg::CACHE_ULCK}; // line 219 assign req_port_o.cache_lck_req[1] = (lsu_ctrl_i.operation == ariane_pkg::CACHE_LCK)? '1 : '0; // line 220 ``` ### wt_dcache.sv 为锁定机制添加信号: ``` logic [ CVA6Cfg.DCACHE_SET_ASSOC-1:0 ] wr_lck_bits; // line 87 logic [ CVA6Cfg.DCACHE_SET_ASSOC-1:0 ] rd_lck_bits; // line 125 logic [ NumPorts-1:0 ][ CVA6Cfg.DCACHE_SET_ASSOC-1:0]lock_hit_oh; // line 109 logic [ NumPorts-1:0 ][ CVA6Cfg.DCACHE_SET_ASSOC-1:0]lock_bits; // line 110 logic [ NumPorts-1:0 ][ 1:0 ] lock_req; // line 111 ``` 为缓存存储器添加端口 (i_wt_dcache_mem): ``` // read ports .rd_lck_bits_o (rd_lck_bits), // cacheline write port .wr_lck_bits_i (wr_lck_bits), ``` 为缓存控制器添加端口 (i_wt_dcache_ctrl): ``` // lock interface .lock_hit_oh_o (lock_hit_oh[k]), // Gives the index of the way which has a hit .lock_bits_o (lock_bits[k]), .lock_req_o (lock_req[k]) // from cache mem interface .rd_lck_bits_i (rd_lck_bits) ``` 为未命中单元添加端口 (i_wt_dcache_missunit): ``` // lock handling interface .lock_hit_oh_i (lock_hit_oh), .lock_bits_i (lock_bits), .lock_req_i (lock_req), .lock_allowed_o, // to cache memory interface .wr_lck_bits_o (wr_lck_bits), ``` ### wt_dcache_mem.sv 在缓存行的 tag、有效位和数据旁边添加锁定位: ``` logic [CVA6Cfg.DCACHE_TAG_WIDTH-1+2:0] vld_tag_rdata[CVA6Cfg.DCACHE_SET_ASSOC-1:0]; assign tag_rdata[i] = vld_tag_rdata[i][CVA6Cfg.DCACHE_TAG_WIDTH-1:0]; assign rd_vld_bits_o[i] = vld_tag_rdata[i][CVA6Cfg.DCACHE_TAG_WIDTH-1+1]; assign rd_lck_bits_o[i] = vld_tag_rdata[i][CVA6Cfg.DCACHE_TAG_WIDTH-1+2]; ``` ### wt_dcache_ctrl.sv 检查地址和锁定位状态是否均发生命中: ``` ... end else if ((|rd_hit_oh_i) && cache_en_i) begin // check if we need to update the lock table if (lck_req_q[0] && ((|(rd_lck_bits_i & rd_hit_oh_i)) == ~lck_req_q[1] )) begin state_d = MISS_REQ; // going throught the miss unit is forced (even with a hit), the miss unit handles the locking logic end else begin ... ``` ### wt_dcache_missunit.sv 实现如前所述的锁定处理单元。处理要写入缓存存储器的新锁定位值。 ``` // LOCK Handling UNIT // generate random cacheline index lfsr #( .LfsrWidth(CVA6Cfg.DCACHE_SET_ASSOC_WIDTH*4 +5), .OutWidth (CVA6Cfg.DCACHE_SET_ASSOC_WIDTH*4 +1) ) i_lfsr_inv ( .clk_i (clk_i), .rst_ni(rst_ni), .en_i (update_lfsr), .out_o (lfsr_out) ); // GENRATE RND_OUT logic [3:0] lzc_4_in; logic [1:0] lzc_4_out; logic lzc_4_empty; for (genvar k = 0; k < 4; k++) begin : assign_lzc_4_in assign lzc_4_in[k] = ~lock_bits_i[miss_port_idx][lfsr_out[CVA6Cfg.DCACHE_SET_ASSOC_WIDTH*(4-k)-1:CVA6Cfg.DCACHE_SET_ASSOC_WIDTH*(4-k)-CVA6Cfg.DCACHE_SET_ASSOC_WIDTH]]; end assign lzc_4_out[0] = (~lzc_4_in[1] || lzc_4_in[2]) && ~lzc_4_in[3]; assign lzc_4_out[1] = ~(lzc_4_in[2] || lzc_4_in[3]); assign lzc_4_empty = ~(lzc_4_in[0]||lzc_4_in[1]||lzc_4_in[2]||lzc_4_in[3]); logic [CVA6Cfg.DCACHE_SET_ASSOC_WIDTH-1:0] rnd_out; for (genvar k = 0; k < CVA6Cfg.DCACHE_SET_ASSOC_WIDTH; k++) begin : assign_rnd_out assign rnd_out[k] = lfsr_out[CVA6Cfg.DCACHE_SET_ASSOC_WIDTH*lzc_4_out+k]; end // GENRATE LZC_OUT logic [CVA6Cfg.DCACHE_SET_ASSOC_WIDTH-1:0] lzc_lsb_out; logic [CVA6Cfg.DCACHE_SET_ASSOC_WIDTH-1:0] lzc_msb_out; lzc #( .WIDTH(CVA6Cfg.DCACHE_SET_ASSOC), .MODE(1'b0) ) lzc_locked_LSB ( .in_i(~lock_bits_i[miss_port_idx]), .cnt_o(lzc_lsb_out) ); lzc #( .WIDTH(CVA6Cfg.DCACHE_SET_ASSOC), .MODE(1'b1) ) lzc_locked_MSB ( .in_i(~lock_bits_i[miss_port_idx]), .cnt_o(lzc_msb_out) ); logic [CVA6Cfg.DCACHE_SET_ASSOC_WIDTH-1:0] lzc_out; assign lzc_out = (lfsr_out[CVA6Cfg.DCACHE_SET_ASSOC_WIDTH*4]) ? lzc_lsb_out : CVA6Cfg.DCACHE_SET_ASSOC-1-lzc_msb_out; // SELECT RND_OUT or LZC_OUT logic [CVA6Cfg.DCACHE_SET_ASSOC_WIDTH-1:0] rnd_ulck_way; assign rnd_ulck_way = (lzc_4_empty)? lzc_out : rnd_out; // CHECK IF THERE IS A HIT logic is_miss; lzc #( .WIDTH(CVA6Cfg.DCACHE_SET_ASSOC), .MODE(1'b0) ) lzc_rd_hit ( .in_i(lock_hit_oh_i[miss_port_idx]), .cnt_o(hit_way), .empty_o(is_miss) ); // SELECT HIT_WAY or RND_ULCK_WAY assign repl_way = (~is_miss)? hit_way: (all_ways_valid) ? rnd_ulck_way : inv_way; // CHECK IF LOCK IS ALLOWED AT REPL_WAY logic [CVA6Cfg.DCACHE_SET_ASSOC-1:0] lcked_bits_after_lck; assign lcked_bits_after_lck = ~(lock_bits_i[miss_port_idx][CVA6Cfg.DCACHE_SET_ASSOC-1:0] | (1<
标签:CVA6, RISC-V, 处理器, 硬件安全, 缓存侧信道防护, 缓存锁定