[rtl] Protect core_busy_o with a multi-bit encoding

This commit protects the core_busy_o signal using a multi-bit encoding
to reduce the chances of an adversary for glitching this signal to low,
thereby putting the core to sleep and e.g. not handling an alert.

Without this commit, the glitch would only be detected once both the
main core and the shadow core wake up again and the comparison of the
core_busy_o signals continues.

This resolves lowRISC/Ibex#1827.

Signed-off-by: Pirmin Vogel <vogelpi@lowrisc.org>
This commit is contained in:
Pirmin Vogel 2022-10-21 17:23:08 +02:00
parent f385d4d6b1
commit 28935490c2
13 changed files with 142 additions and 98 deletions

View file

@ -215,7 +215,7 @@ Interfaces
| | | | instructions in the ID/EX and WB |
| | | | stages have finished. A multi-bit |
| | | | encoding scheme is used. See |
| | | | `FetchEnableOn` / `FetchEnableOff` in |
| | | | `IbexMuBiOn` / `IbexMuBiOff` in |
| | | | :file:`rtl/ibex_pkg.sv` |
+----------------------------+-------------------------+-----+----------------------------------------+
| ``core_sleep_o`` | 1 | out | Core in WFI with no outstanding data |

View file

@ -158,57 +158,57 @@ module ibex_riscv_compliance (
.DmHaltAddr (32'h00000000 ),
.DmExceptionAddr (32'h00000000 )
) u_top (
.clk_i (clk_sys ),
.rst_ni (rst_sys_n ),
.clk_i (clk_sys ),
.rst_ni (rst_sys_n ),
.test_en_i ('b0 ),
.scan_rst_ni (1'b1 ),
.ram_cfg_i ('b0 ),
.test_en_i ('b0 ),
.scan_rst_ni (1'b1 ),
.ram_cfg_i ('b0 ),
.hart_id_i (32'b0 ),
.hart_id_i (32'b0 ),
// First instruction executed is at 0x0 + 0x80
.boot_addr_i (32'h00000000 ),
.boot_addr_i (32'h00000000 ),
.instr_req_o (host_req[CoreI] ),
.instr_gnt_i (host_gnt[CoreI] ),
.instr_rvalid_i (host_rvalid[CoreI] ),
.instr_addr_o (host_addr[CoreI] ),
.instr_rdata_i (host_rdata[CoreI] ),
.instr_rdata_intg_i (ibex_instr_rdata_intg ),
.instr_err_i (host_err[CoreI] ),
.instr_req_o (host_req[CoreI] ),
.instr_gnt_i (host_gnt[CoreI] ),
.instr_rvalid_i (host_rvalid[CoreI] ),
.instr_addr_o (host_addr[CoreI] ),
.instr_rdata_i (host_rdata[CoreI] ),
.instr_rdata_intg_i (ibex_instr_rdata_intg),
.instr_err_i (host_err[CoreI] ),
.data_req_o (host_req[CoreD] ),
.data_gnt_i (host_gnt[CoreD] ),
.data_rvalid_i (host_rvalid[CoreD] ),
.data_we_o (host_we[CoreD] ),
.data_be_o (host_be[CoreD] ),
.data_addr_o (host_addr[CoreD] ),
.data_wdata_o (host_wdata[CoreD] ),
.data_wdata_intg_o ( ),
.data_rdata_i (host_rdata[CoreD] ),
.data_rdata_intg_i (ibex_data_rdata_intg ),
.data_err_i (host_err[CoreD] ),
.data_req_o (host_req[CoreD] ),
.data_gnt_i (host_gnt[CoreD] ),
.data_rvalid_i (host_rvalid[CoreD] ),
.data_we_o (host_we[CoreD] ),
.data_be_o (host_be[CoreD] ),
.data_addr_o (host_addr[CoreD] ),
.data_wdata_o (host_wdata[CoreD] ),
.data_wdata_intg_o ( ),
.data_rdata_i (host_rdata[CoreD] ),
.data_rdata_intg_i (ibex_data_rdata_intg ),
.data_err_i (host_err[CoreD] ),
.irq_software_i (1'b0 ),
.irq_timer_i (1'b0 ),
.irq_external_i (1'b0 ),
.irq_fast_i (15'b0 ),
.irq_nm_i (1'b0 ),
.irq_software_i (1'b0 ),
.irq_timer_i (1'b0 ),
.irq_external_i (1'b0 ),
.irq_fast_i (15'b0 ),
.irq_nm_i (1'b0 ),
.scramble_key_valid_i ('0 ),
.scramble_key_i ('0 ),
.scramble_nonce_i ('0 ),
.scramble_req_o ( ),
.scramble_key_valid_i ('0 ),
.scramble_key_i ('0 ),
.scramble_nonce_i ('0 ),
.scramble_req_o ( ),
.debug_req_i ('b0 ),
.crash_dump_o ( ),
.double_fault_seen_o ( ),
.debug_req_i ('b0 ),
.crash_dump_o ( ),
.double_fault_seen_o ( ),
.fetch_enable_i (ibex_pkg::FetchEnableOn),
.alert_minor_o ( ),
.alert_major_internal_o ( ),
.alert_major_bus_o ( ),
.core_sleep_o ( )
.fetch_enable_i (ibex_pkg::IbexMuBiOn ),
.alert_minor_o ( ),
.alert_major_internal_o ( ),
.alert_major_bus_o ( ),
.core_sleep_o ( )
);
// SRAM block for instruction and data storage

View file

@ -11,7 +11,7 @@ interface core_ibex_dut_probe_if(input logic clk);
logic ebreak;
logic dret;
logic mret;
ibex_pkg::fetch_enable_t fetch_enable;
ibex_pkg::ibex_mubi_t fetch_enable;
logic core_sleep;
logic alert_minor;
logic alert_major_internal;

View file

@ -171,10 +171,10 @@ class core_ibex_base_test extends uvm_test;
enable_irq_seq = cfg.enable_irq_single_seq || cfg.enable_irq_multiple_seq;
phase.raise_objection(this);
cur_run_phase = phase;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOff;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::IbexMuBiOff;
clk_vif.wait_clks(100);
load_binary_to_mem();
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOn;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::IbexMuBiOn;
send_stimulus();
wait_for_test_done();
cur_run_phase = null;
@ -282,7 +282,7 @@ class core_ibex_base_test extends uvm_test;
check_perf_stats();
// De-assert fetch enable to finish the test
clk_vif.wait_clks(10);
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOff;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::IbexMuBiOff;
// Wait some time for the remaining instruction to finish
clk_vif.wait_clks(3000);
endtask

View file

@ -258,21 +258,21 @@ class fetch_enable_seq extends core_base_new_seq#(irq_seq_item);
all_off_values = 0;
end
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOn;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::IbexMuBiOn;
super.body();
endtask: body
virtual task send_req();
ibex_pkg::fetch_enable_t fetch_enable_off;
int unsigned off_delay;
ibex_pkg::ibex_mubi_t fetch_enable_off;
int unsigned off_delay;
if (all_off_values) begin
// Randomise the MUBI fetch_enable value to be one of the many possible off values
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(fetch_enable_off,
fetch_enable_off != ibex_pkg::FetchEnableOn;)
fetch_enable_off != ibex_pkg::IbexMuBiOn;)
end else begin
// Otherwise use single fixed off value
fetch_enable_off = ibex_pkg::FetchEnableOff;
fetch_enable_off = ibex_pkg::IbexMuBiOff;
end
`DV_CHECK_STD_RANDOMIZE_WITH_FATAL(off_delay,
@ -280,7 +280,7 @@ class fetch_enable_seq extends core_base_new_seq#(irq_seq_item);
dut_vif.dut_cb.fetch_enable <= fetch_enable_off;
clk_vif.wait_clks(off_delay);
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOn;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::IbexMuBiOn;
endtask

View file

@ -26,7 +26,7 @@ class core_ibex_reset_test extends core_ibex_base_test;
clk_vif.wait_clks($urandom_range(0, 50000));
fork
begin
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOff;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::IbexMuBiOff;
clk_vif.apply_reset(.reset_width_clks (100));
end
begin
@ -40,7 +40,7 @@ class core_ibex_reset_test extends core_ibex_base_test;
end
join
// Assert fetch_enable to have the core start executing from boot address
dut_vif.dut_cb.fetch_enable <= ibex_pkg::FetchEnableOn;
dut_vif.dut_cb.fetch_enable <= ibex_pkg::IbexMuBiOn;
end
endtask

View file

@ -253,7 +253,7 @@ module ibex_simple_system (
.crash_dump_o (),
.double_fault_seen_o (),
.fetch_enable_i (ibex_pkg::FetchEnableOn),
.fetch_enable_i (ibex_pkg::IbexMuBiOn),
.alert_minor_o (),
.alert_major_internal_o (),
.alert_major_bus_o (),

View file

@ -147,11 +147,11 @@ module ibex_core import ibex_pkg::*; #(
// CPU Control Signals
// SEC_CM: FETCH.CTRL.LC_GATED
input fetch_enable_t fetch_enable_i,
input ibex_mubi_t fetch_enable_i,
output logic alert_minor_o,
output logic alert_major_internal_o,
output logic alert_major_bus_o,
output logic core_busy_o
output ibex_mubi_t core_busy_o
);
localparam int unsigned PMPNumChan = 3;
@ -368,7 +368,31 @@ module ibex_core import ibex_pkg::*; #(
// Before going to sleep, wait for I- and D-side
// interfaces to finish ongoing operations.
assign core_busy_o = ctrl_busy | if_busy | lsu_busy;
if (SecureIbex) begin : g_core_busy_secure
// For secure Ibex, the individual bits of core_busy_o are generated from different copies of
// the various busy signal.
localparam int unsigned NumBusySignals = 3;
localparam int unsigned NumBusyBits = $bits(ibex_mubi_t) * NumBusySignals;
logic [NumBusyBits-1:0] busy_bits_buf;
prim_buf #(
.Width(NumBusyBits)
) u_fetch_enable_buf (
.in_i ({$bits(ibex_mubi_t){ctrl_busy, if_busy, lsu_busy}}),
.out_o(busy_bits_buf)
);
// Set core_busy_o to IbexMuBiOn if even a single input is high.
for (genvar i = 0; i < $bits(ibex_mubi_t); i++) begin : g_core_busy_bits
if (IbexMuBiOn[i] == 1'b1) begin : g_pos
assign core_busy_o[i] = |busy_bits_buf[i*NumBusySignals +: NumBusySignals];
end else begin : g_neg
assign core_busy_o[i] = ~|busy_bits_buf[i*NumBusySignals +: NumBusySignals];
end
end
end else begin : g_core_busy_non_secure
// For non secure Ibex, synthesis is allowed to optimize core_busy_o.
assign core_busy_o = (ctrl_busy || if_busy || lsu_busy) ? IbexMuBiOn : IbexMuBiOff;
end
//////////////
// IF stage //
@ -474,22 +498,21 @@ module ibex_core import ibex_pkg::*; #(
// Multi-bit fetch enable used when SecureIbex == 1. When SecureIbex == 0 only use the bottom-bit
// of fetch_enable_i. Ensure the multi-bit encoding has the bottom bit set for on and unset for
// off so FetchEnableOn/FetchEnableOff can be used without needing to know the value of
// SecureIbex.
`ASSERT_INIT(FetchEnableSecureOnBottomBitSet, FetchEnableOn[0] == 1'b1)
`ASSERT_INIT(FetchEnableSecureOffBottomBitClear, FetchEnableOff[0] == 1'b0)
// off so IbexMuBiOn/IbexMuBiOff can be used without needing to know the value of SecureIbex.
`ASSERT_INIT(IbexMuBiSecureOnBottomBitSet, IbexMuBiOn[0] == 1'b1)
`ASSERT_INIT(IbexMuBiSecureOffBottomBitClear, IbexMuBiOff[0] == 1'b0)
// fetch_enable_i can be used to stop the core fetching new instructions
if (SecureIbex) begin : g_instr_req_gated_secure
// For secure Ibex fetch_enable_i must be a specific multi-bit pattern to enable instruction
// fetch
// SEC_CM: FETCH.CTRL.LC_GATED
assign instr_req_gated = instr_req_int & (fetch_enable_i == FetchEnableOn);
assign instr_exec = fetch_enable_i == FetchEnableOn;
assign instr_req_gated = instr_req_int & (fetch_enable_i == IbexMuBiOn);
assign instr_exec = fetch_enable_i == IbexMuBiOn;
end else begin : g_instr_req_gated_non_secure
// For non secure Ibex only the bottom bit of fetch enable is considered
logic unused_fetch_enable;
assign unused_fetch_enable = ^fetch_enable_i[$bits(fetch_enable_t)-1:1];
assign unused_fetch_enable = ^fetch_enable_i[$bits(ibex_mubi_t)-1:1];
assign instr_req_gated = instr_req_int & fetch_enable_i[0];
assign instr_exec = fetch_enable_i[0];
@ -931,7 +954,7 @@ module ibex_core import ibex_pkg::*; #(
// Keep track of the PC last seen in the ID stage when fetch is disabled
logic [31:0] pc_at_fetch_disable;
fetch_enable_t last_fetch_enable;
ibex_mubi_t last_fetch_enable;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
@ -940,7 +963,7 @@ module ibex_core import ibex_pkg::*; #(
end else begin
last_fetch_enable <= fetch_enable_i;
if ((fetch_enable_i != FetchEnableOn) && (last_fetch_enable == FetchEnableOn)) begin
if ((fetch_enable_i != IbexMuBiOn) && (last_fetch_enable == IbexMuBiOn)) begin
pc_at_fetch_disable <= pc_id;
end
end
@ -949,7 +972,7 @@ module ibex_core import ibex_pkg::*; #(
// When fetch is disabled no instructions should be executed. Once fetch is disabled either the
// ID/EX stage is not valid or the PC of the ID/EX stage must remain as it was at disable. The
// ID/EX valid should not ressert once it has been cleared.
`ASSERT(NoExecWhenFetchEnableNotOn, fetch_enable_i != FetchEnableOn |=>
`ASSERT(NoExecWhenFetchEnableNotOn, fetch_enable_i != IbexMuBiOn |=>
(~instr_valid_id || (pc_id == pc_at_fetch_disable)) && ~$rose(instr_valid_id))
`endif

View file

@ -96,11 +96,11 @@ module ibex_lockstep import ibex_pkg::*; #(
input crash_dump_t crash_dump_i,
input logic double_fault_seen_i,
input fetch_enable_t fetch_enable_i,
input ibex_mubi_t fetch_enable_i,
output logic alert_minor_o,
output logic alert_major_internal_o,
output logic alert_major_bus_o,
input logic core_busy_i,
input ibex_mubi_t core_busy_i,
input logic test_en_i,
input logic scan_rst_ni
);
@ -183,7 +183,7 @@ module ibex_lockstep import ibex_pkg::*; #(
logic [14:0] irq_fast;
logic irq_nm;
logic debug_req;
fetch_enable_t fetch_enable;
ibex_mubi_t fetch_enable;
logic ic_scr_key_valid;
} delayed_inputs_t;
@ -263,7 +263,7 @@ module ibex_lockstep import ibex_pkg::*; #(
logic irq_pending;
crash_dump_t crash_dump;
logic double_fault_seen;
logic core_busy;
ibex_mubi_t core_busy;
} delayed_outputs_t;
delayed_outputs_t [OutputsOffset-1:0] core_outputs_q;

View file

@ -653,14 +653,14 @@ package ibex_pkg;
parameter logic [SCRAMBLE_NONCE_W-1:0] RndCnstIbexNonceDefault =
64'hf79780bc735f3843;
// Fetch enable. Mult-bit signal used for security hardening. For non-secure implementation all
// bits other than the bottom bit are ignored.
typedef logic [3:0] fetch_enable_t;
// Mult-bit signal used for security hardening. For non-secure implementation all bits other than
// the bottom bit are ignored.
typedef logic [3:0] ibex_mubi_t;
// Note that if adjusting these parameters it is assumed the bottom bit is set for On and unset
// for Off. This allows the use of FetchEnableOn/FetchEnableOff to work for both secure and
// non-secure Ibex. If this assumption is broken the RTL that uses the fetch_enable signal within
// `ibex_core` may need adjusting.
parameter fetch_enable_t FetchEnableOn = 4'b0101;
parameter fetch_enable_t FetchEnableOff = 4'b1010;
// for Off. This allows the use of IbexMuBiOn/IbexMuBiOff to work for both secure and non-secure
// Ibex. If this assumption is broken the RTL that uses ibex_mubi_t types such as the fetch_enable
// and core_busy signals within `ibex_core` may need adjusting.
parameter ibex_mubi_t IbexMuBiOn = 4'b0101;
parameter ibex_mubi_t IbexMuBiOff = 4'b1010;
endpackage

View file

@ -126,7 +126,7 @@ module ibex_top import ibex_pkg::*; #(
`endif
// CPU Control Signals
input fetch_enable_t fetch_enable_i,
input ibex_mubi_t fetch_enable_i,
output logic alert_minor_o,
output logic alert_major_internal_o,
output logic alert_major_bus_o,
@ -154,7 +154,7 @@ module ibex_top import ibex_pkg::*; #(
// Clock signals
logic clk;
logic core_busy_d, core_busy_q;
ibex_mubi_t core_busy_d, core_busy_q;
logic clock_en;
logic irq_pending;
// Core <-> Register file signals
@ -189,26 +189,45 @@ module ibex_top import ibex_pkg::*; #(
logic lockstep_alert_major_internal, lockstep_alert_major_bus;
logic lockstep_alert_minor;
// Scramble signals
logic [SCRAMBLE_KEY_W-1:0] scramble_key_q;
logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_q;
logic scramble_key_valid_d, scramble_key_valid_q;
logic scramble_req_d, scramble_req_q;
logic [SCRAMBLE_KEY_W-1:0] scramble_key_q;
logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_q;
logic scramble_key_valid_d, scramble_key_valid_q;
logic scramble_req_d, scramble_req_q;
fetch_enable_t fetch_enable_buf;
ibex_mubi_t fetch_enable_buf;
/////////////////////
// Main clock gate //
/////////////////////
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
core_busy_q <= 1'b0;
end else begin
core_busy_q <= core_busy_d;
if (SecureIbex) begin : g_clock_en_secure
// For secure Ibex core_busy_q must be a specific multi-bit pattern to enable the clock.
prim_flop #(
.Width($bits(ibex_mubi_t)),
.ResetValue(IbexMuBiOff)
) u_prim_core_busy_flop (
.clk_i (clk_i),
.rst_ni(rst_ni),
.d_i (core_busy_d),
.q_o (core_busy_q)
);
assign clock_en = (core_busy_q != IbexMuBiOff) | debug_req_i | irq_pending | irq_nm_i;
end else begin : g_clock_en_non_secure
// For non secure Ibex only the bottom bit of core_busy_q is considered. Other FFs can be
// optimized away during synthesis.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
core_busy_q <= IbexMuBiOff;
end else begin
core_busy_q <= core_busy_d;
end
end
assign clock_en = core_busy_q[0] | debug_req_i | irq_pending | irq_nm_i;
logic unused_core_busy;
assign unused_core_busy = ^core_busy_q[$bits(ibex_mubi_t)-1:1];
end
assign clock_en = core_busy_q | debug_req_i | irq_pending | irq_nm_i;
assign core_sleep_o = ~clock_en;
prim_clock_gating core_clock_gate_i (
@ -223,7 +242,7 @@ module ibex_top import ibex_pkg::*; #(
////////////////////////
// Buffer security critical signals to prevent synthesis optimisation removing them
prim_buf #(.Width($bits(fetch_enable_t))) u_fetch_enable_buf (
prim_buf #(.Width($bits(ibex_mubi_t))) u_fetch_enable_buf (
.in_i (fetch_enable_i),
.out_o(fetch_enable_buf)
);
@ -771,9 +790,9 @@ module ibex_top import ibex_pkg::*; #(
logic debug_req_local;
crash_dump_t crash_dump_local;
logic double_fault_seen_local;
fetch_enable_t fetch_enable_local;
ibex_mubi_t fetch_enable_local;
logic core_busy_local;
ibex_mubi_t core_busy_local;
assign buf_in = {
hart_id_i,

View file

@ -83,7 +83,7 @@ module ibex_top_tracing import ibex_pkg::*; #(
output logic double_fault_seen_o,
// CPU Control Signals
input fetch_enable_t fetch_enable_i,
input ibex_mubi_t fetch_enable_i,
output logic alert_minor_o,
output logic alert_major_internal_o,
output logic alert_major_bus_o,

View file

@ -34,6 +34,7 @@ source syn_setup.sh
LR_DEP_SOURCES=(
"../vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_buf.sv"
"../vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_flop.sv"
)
mkdir -p "$LR_SYNTH_OUT_DIR/generated"
@ -73,6 +74,7 @@ for file in ../rtl/*.sv; do
# Make sure auto-generated primitives are resolved to generic primitives
# where available.
sed -i 's/prim_buf/prim_generic_buf/g' "$LR_SYNTH_OUT_DIR"/generated/"${module}".v
sed -i 's/prim_flop/prim_generic_flop/g' "$LR_SYNTH_OUT_DIR"/generated/"${module}".v
done
# remove tracer (not needed for synthesis)