Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rtl] Harden lockstep enable against FI #2129

Merged
merged 1 commit into from
Jan 23, 2024

Conversation

nasahlpa
Copy link
Member

@nasahlpa nasahlpa commented Jan 17, 2024

Currently, the dual-core lockstep FI mitigation is enabled/disabled using a single bit. For transient bit-flips, this is not problematic, as one bit-flip into this signal and one bit into the Ibex is required to threaten the security of the system.

However, a permanent stuck-at-0 fault could disable the lockstep completely by targeting this signal. Then, only a single, additional fault (transient or permanent) is required.

This PR enhances the FI resilience of the Ibex lockstep by encoding this single bit into a ibex_mubi_t signal, i.e., a 4-bit multi-bit signal. Note that the MUXes:

(rst_shadow_set_d == IbexMuBiOn) ? rst_shadow_cnt_q : rst_shadow_cnt_incr;

and
(enable_cmp_q == IbexMuBiOn) & (shadow_outputs_q != core_outputs_q[0]);

are not redundant. However, as the goal is to protect the previously 1-bit select signal against stuck-at-0 faults, increasing the bit-width to 4-bit significantly increases security against this threat.

I opted for going with a ibex_mubi_t instead of a mubi4/8 as it seems that only ibex_mubi_t is frequently used in the core.

Tracked in #20779.

Full DV regression looks good.

@@ -157,7 +163,7 @@ module ibex_lockstep import ibex_pkg::*; #(
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_prim_rst_shadow_n_mux2 (
.clk0_i(rst_shadow_set_q),
.clk0_i(rst_shadow_set_single_bit),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed, because when doing this:

Suggested change
.clk0_i(rst_shadow_set_single_bit),
.clk0_i(rst_shadow_set_q[0]),

I get the IMPERFECTSCH linting error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the IMPERFECTSCH warning cause CI to fail? If not, I'd be inclined to use the rst_shadow_set_q[0] form. I notice that the warning was removed from Verilator with version 5 (https://verilator.org/guide/latest/warnings.html).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, CI fails because of this IMPERFECTSCH warning.

I would also prefer using rst_shadow_set_q[0], but I couldn't find a way of getting rid of this warning in CI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A solution: upgrade our Verilator dependency :-)

I think we can probably just do that. Would you mind filing an issue to remind us? ("Upgrade Verilator, then remove hack I had to do in ibex_lockstep.sv" or something similar)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, tracked in issue #2131.

Copy link
Contributor

@andreaskurth andreaskurth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this and working on a fix, @nasahlpa! I agree we should make changes to harden lockstep enable against FI, but I'm not sure the PR as-is suffices; please see my comments

Comment on lines 461 to 468
assign outputs_mismatch = enable_cmp_q & (shadow_outputs_q != core_outputs_q[0]);
assign outputs_mismatch =
(enable_cmp_q == IbexMuBiOn) & (shadow_outputs_q != core_outputs_q[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't single-bit FI on enable_cmp_q still be sufficient to evade this comparison? Put differently, shouldn't we use enable_cmp_q != IbexMuBiOff as condition here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. I've changed this.


always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rst_shadow_cnt_q <= '0;
enable_cmp_q <= '0;
enable_cmp_q <= IbexMuBiOff;
end else begin
rst_shadow_cnt_q <= rst_shadow_cnt_d;
enable_cmp_q <= rst_shadow_set_q;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable_cmp_q may also benefit from being implemented with a prim_flop to avoid optimization, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented enable_cmp with a prim_flop to avoid optimization. Thanks!

Comment on lines 135 to 136
assign rst_shadow_set_d =
(rst_shadow_cnt_q == LockstepOffsetW'(LockstepOffset - 1)) ? IbexMuBiOn : IbexMuBiOff;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here a MuBi is computed from a strict bit-by-bit equality, so wouldn't a single-bit FI on rst_shadow_cnt_q be sufficient to control the MuBi?

I think this could be mitigated by using a hardened counter for rst_shadow_cnt

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also recognized that the counter here could be a weak point. However, does using prim_count actually mitigate this problem? I was thinking of replacing the counter with the following:

  prim_count #(
    .Width(LockstepOffsetW)
  ) u_rst_shadow_cnt (
    .clk_i(clk_i),
    .rst_ni(rst_ni),
    .clr_i('0),
    .set_i('0), Done
    .set_cnt_i('0), Done
    .incr_en_i(rst_shadow_set_d == IbexMuBiOn),
    .decr_en_i(1'b0), Done
    .step_i(LockstepOffsetW'(1'b1)), Done
    .cnt_o(rst_shadow_cnt_q),
    .cnt_next_o(), Done
    .err_o(cnt_err)
  );

However, if a stuck-at-0 fault inside the prim_count permanently sets the incr_en signal to 0, we would have the same issue again?

Copy link
Contributor

@andreaskurth andreaskurth Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prim_count saturates and doesn't overflow, so how about setting incr_en_i as well as step_i to 1, and then use >= for comparing cnt_o to derive the MuBi value? Surely stuck-at-0 on step_i or incr_en_i would still bypass this countermeasure, but these would be tied to logic rails, which AFAIK is much more difficult to attack than bits stored in FFs/latches.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @andreaskurth .

I am not sure if we should also check whether rst_shadow_set_q contains a valid value and raise an alert if not. We do this in other places as well. However, if rst_shadow_set_q was invalid, also enable_cmp_q would become invalid eventually and the comparison was started: whenever it's not off, the comparison is enabled. So it's probably fine. We may want to document this though to prevent someone from changing this accidentally.

@nasahlpa nasahlpa force-pushed the ibex_lockstep branch 2 times, most recently from 5a6b9a3 to 2f040eb Compare January 18, 2024 12:04
Copy link
Contributor

@vogelpi vogelpi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @nasahlpa , this looks good. Adding a prim_count sounds like a good idea to me.

rtl/ibex_pkg.sv Outdated Show resolved Hide resolved
Comment on lines 135 to 136
assign rst_shadow_set_d =
(rst_shadow_cnt_q == LockstepOffsetW'(LockstepOffset - 1)) ? IbexMuBiOn : IbexMuBiOff;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @andreaskurth .

I am not sure if we should also check whether rst_shadow_set_q contains a valid value and raise an alert if not. We do this in other places as well. However, if rst_shadow_set_q was invalid, also enable_cmp_q would become invalid eventually and the comparison was started: whenever it's not off, the comparison is enabled. So it's probably fine. We may want to document this though to prevent someone from changing this accidentally.

nasahlpa added a commit to nasahlpa/ibex that referenced this pull request Jan 19, 2024
To be consistent between projects, this PR updates the Verilator
version to 4.210, which is also used by OpenTitan. The reason for
this change is that in lowRISC#2129 Verilator linting issues occured
that did not occur in OpenTitan.

Closes lowRISC#2131.

Signed-off-by: Pascal Nasahl <nasahlpa@lowrisc.org>
github-merge-queue bot pushed a commit that referenced this pull request Jan 19, 2024
To be consistent between projects, this PR updates the Verilator
version to 4.210, which is also used by OpenTitan. The reason for
this change is that in #2129 Verilator linting issues occured
that did not occur in OpenTitan.

Closes #2131.

Signed-off-by: Pascal Nasahl <nasahlpa@lowrisc.org>
@nasahlpa nasahlpa mentioned this pull request Jan 19, 2024
@nasahlpa
Copy link
Member Author

Thanks for the PR @nasahlpa , this looks good. Adding a prim_count sounds like a good idea to me.

Thanks for all the helpful suggestions! I've updated the code and this PR is ready for a second round of reviews :-)

Copy link
Contributor

@rswarbrick rswarbrick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of last nitty questions, but this looks really good to me.

@@ -331,6 +331,12 @@ module core_ibex_tb_top;
run_test();
end

// Disable the assertion for prim_count in case lockstep (set by SecureIbex) is enabled.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this "assert connected" flag is an ugly feature of the design of the prim. Yuck!

But can we get it wired up without hacking things by using something like the ASSERT_ERROR_TRIGGER_ALERT macro? I suspect not, because that macro seems to have an opinion about how alerts should be signaled (true in OpenTitan; not true for Ibex, I think).

If that doesn't work, could we add a bit of explanation in this comment about what we're working around?


assign rst_shadow_cnt_incr = rst_shadow_cnt_q + 1'b1;
// When the LockstepOffset counter value is reached, activate the lockstep
// comparison. We do not explicitly check whether rst_shadow_set forms a valid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Does this need to say "rst_shadow_set_q" here?

Currently, the dual-core lockstep FI mitigation is enabled/disabled
using a single bit.
For transient bit-flips, this is not problematic, as one bit-flip
into this signal and one bit into the Ibex is required to threaten
the security of the system.

However, a permanent stuck-at-0 fault could disable the lockstep
completely by targeting this signal. Then, only a single, additional
fault (transient or permanent) is required.

This PR enhances the FI resilience of the Ibex lockstep by encoding
this single bit into a ibex_mubi_t signal, i.e., a 4-bit multi-bit
signal.

Signed-off-by: Pascal Nasahl <nasahlpa@lowrisc.org>
Copy link
Contributor

@andreaskurth andreaskurth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for implementing our feedback @nasahlpa 👍

Copy link
Contributor

@vogelpi vogelpi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @nasahlpa !

@vogelpi vogelpi added this pull request to the merge queue Jan 23, 2024
Merged via the queue into lowRISC:master with commit 8ec0c6f Jan 23, 2024
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants