diff mbox series

[v9,03/17] target/riscv: save and restore elp state on priv transitions

Message ID 20240826152949.294506-4-debug@rivosinc.com
State New
Headers show
Series riscv support for control flow integrity extensions | expand

Commit Message

Deepak Gupta Aug. 26, 2024, 3:29 p.m. UTC
elp state is recorded in *status on trap entry (less privilege to higher
privilege) and restored in elp from *status on trap exit (higher to less
privilege).

Additionally this patch introduces a forward cfi helper function to
determine if current privilege has forward cfi is enabled or not based on
*envcfg (for U, VU, S, VU, HS) or mseccfg csr (for M). For qemu-user, a
new field `ufcfien` is introduced which is by default set to false and
helper function returns value deposited in `ufcfien` for qemu-user.

Signed-off-by: Deepak Gupta <debug@rivosinc.com>
Co-developed-by: Jim Shu <jim.shu@sifive.com>
Co-developed-by: Andy Chiu <andy.chiu@sifive.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
---
 target/riscv/cpu.c        |  5 ++++
 target/riscv/cpu.h        |  2 ++
 target/riscv/cpu_helper.c | 55 +++++++++++++++++++++++++++++++++++++++
 target/riscv/op_helper.c  | 18 +++++++++++++
 4 files changed, 80 insertions(+)

Comments

Richard Henderson Aug. 27, 2024, 12:33 a.m. UTC | #1
On 8/27/24 01:29, Deepak Gupta wrote:
> diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
> index 8e1f05e5b1..083d405516 100644
> --- a/target/riscv/cpu.c
> +++ b/target/riscv/cpu.c
> @@ -1022,6 +1022,11 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
>       env->load_res = -1;
>       set_default_nan_mode(1, &env->fp_status);
>   
> +#ifdef CONFIG_USER_ONLY
> +    /* qemu-user for riscv, fcfi is off by default */
> +    env->ufcfien = false;
> +#endif
...
> @@ -226,6 +226,7 @@ struct CPUArchState {
>       bool      elp;
>   #ifdef CONFIG_USER_ONLY
>       uint32_t elf_flags;
> +    bool ufcfien;
>   #endif

Thinking about this more, I think adding separate controls for user-only is a bad 
precedent to set.  You said you are adding these because senvcfg/menvcfg are ifdefed: 
well, that should be the thing that we fix.

The only real user of *envcfg that I see so far is check_zicbo_envcfg, which does not use 
the same switch statement as this:

> +    switch (env->priv) {
> +    case PRV_U:
> +        if (riscv_has_ext(env, RVS)) {
> +            return env->senvcfg & MENVCFG_LPE;
> +        }
> +        return env->menvcfg & MENVCFG_LPE;
> +    case PRV_S:
> +        if (env->virt_enabled) {
> +            return env->henvcfg & HENVCFG_LPE;
> +        }
> +        return env->menvcfg & MENVCFG_LPE;
> +    case PRV_M:
> +        return env->mseccfg & MSECCFG_MLPE;
> +    default:
> +        g_assert_not_reached();
> +    }

I think your function should look more like check_zicbo_envcfg: (1) PRV_U may be either U 
or VU, and different tests apply; (2) M-mode disable means that no lower level may be enabled.

It would be nice if you could generalize check_zicbo_envcfg to apply to your new use as 
well.  Perhaps some tri-state function to say "enabled", "disabled", "virtual disabled".


r~
Deepak Gupta Aug. 27, 2024, 12:52 a.m. UTC | #2
On Tue, Aug 27, 2024 at 10:33:04AM +1000, Richard Henderson wrote:
>On 8/27/24 01:29, Deepak Gupta wrote:
>>diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
>>index 8e1f05e5b1..083d405516 100644
>>--- a/target/riscv/cpu.c
>>+++ b/target/riscv/cpu.c
>>@@ -1022,6 +1022,11 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
>>      env->load_res = -1;
>>      set_default_nan_mode(1, &env->fp_status);
>>+#ifdef CONFIG_USER_ONLY
>>+    /* qemu-user for riscv, fcfi is off by default */
>>+    env->ufcfien = false;
>>+#endif
>...
>>@@ -226,6 +226,7 @@ struct CPUArchState {
>>      bool      elp;
>>  #ifdef CONFIG_USER_ONLY
>>      uint32_t elf_flags;
>>+    bool ufcfien;
>>  #endif
>
>Thinking about this more, I think adding separate controls for 
>user-only is a bad precedent to set.  You said you are adding these 
>because senvcfg/menvcfg are ifdefed: well, that should be the thing 
>that we fix.

If a binary is compiled with zicbo, it'll have those instructions in user
binary. I am not sure how those binaries will run on qemu-user (or if it was
tested with qemu-user)

In case of zicfilp + zicfiss we are runnning binaries compiled with zicfilp
and zicfiss with qemu-user and qemu-system both. Use of qemu-user to generate
traces for feeding into CPU modeling is quite useful. Thus mechanism to track
if landing pad and shadow stack is enabled for current user task (in case of
qemu-user) is very useful.

senvcfg and menvcfg belong to S and M state and don't actually mean anything
for qemu-user. However if that's how it is for arm as well (i.e. exposing system
state for qemu-user), then probably there is precedent.
But it looks like a much larger exercise to me.

>
>The only real user of *envcfg that I see so far is check_zicbo_envcfg, 
>which does not use the same switch statement as this:
>
>>+    switch (env->priv) {
>>+    case PRV_U:
>>+        if (riscv_has_ext(env, RVS)) {
>>+            return env->senvcfg & MENVCFG_LPE;
>>+        }
>>+        return env->menvcfg & MENVCFG_LPE;
>>+    case PRV_S:
>>+        if (env->virt_enabled) {
>>+            return env->henvcfg & HENVCFG_LPE;
>>+        }
>>+        return env->menvcfg & MENVCFG_LPE;
>>+    case PRV_M:
>>+        return env->mseccfg & MSECCFG_MLPE;
>>+    default:
>>+        g_assert_not_reached();
>>+    }
>
>I think your function should look more like check_zicbo_envcfg: (1) 
>PRV_U may be either U or VU, and different tests apply; (2) M-mode 
>disable means that no lower level may be enabled.

In case of landing pad, (2) doesn't hold true. Each mode can independently
enable landing pad for next lower mode even if it wasn't enabled for current
mode. Reason being that you can have a firmware which is still not landing pad
enabled and you would want to avoid landing pad related faults in M mode but
still want to make sure S mode and U mode can enable landing pad for themselves.
Same goes with respect to S mode, S mode can have its landing pad disabled while
it can enable landing pad for U mode.

Although logic for shadow stack enable is same as zicbo. Shadow stack enable requires
target software to opt-in by having instructions compiled in and doesn't impact 
behavior of existing instructions.

>
>It would be nice if you could generalize check_zicbo_envcfg to apply 
>to your new use as well.  Perhaps some tri-state function to say 
>"enabled", "disabled", "virtual disabled".

So while shadow stack and zicbo are similar in terms of enabling. Landing pad
enable differs. 

You still want me to generalize *envcfg flow?
>
>
>r~
Richard Henderson Aug. 27, 2024, 1:34 a.m. UTC | #3
On 8/27/24 10:52, Deepak Gupta wrote:
> senvcfg and menvcfg belong to S and M state and don't actually mean anything
> for qemu-user.

Certainly they do, in that you obviously have arch_prctl calls that adjust them.  The fact 
that you've modeled those so far as separate variables is part of what is confusing.

> However if that's how it is for arm as well (i.e. exposing system
> state for qemu-user), then probably there is precedent.

I think having special paths for qemu-user in target/arch/ is maintenance burden, and 
should be avoided if possible.  The best way to reason with user-only is to treat it just 
like the full machine, and set the fields just like the real bios and kernel would do.

> So while shadow stack and zicbo are similar in terms of enabling. Landing pad
> enable differs.
> You still want me to generalize *envcfg flow?

Hmm.  I'll leave that to riscv maintainers; perhaps a third user is required to show 
generality...


r~
Alistair Francis Aug. 27, 2024, 3:53 a.m. UTC | #4
On Tue, Aug 27, 2024 at 11:36 AM Richard Henderson
<richard.henderson@linaro.org> wrote:
>
> On 8/27/24 10:52, Deepak Gupta wrote:
> > senvcfg and menvcfg belong to S and M state and don't actually mean anything
> > for qemu-user.
>
> Certainly they do, in that you obviously have arch_prctl calls that adjust them.  The fact
> that you've modeled those so far as separate variables is part of what is confusing.
>
> > However if that's how it is for arm as well (i.e. exposing system
> > state for qemu-user), then probably there is precedent.
>
> I think having special paths for qemu-user in target/arch/ is maintenance burden, and
> should be avoided if possible.  The best way to reason with user-only is to treat it just
> like the full machine, and set the fields just like the real bios and kernel would do.

I agree about avoiding adding these special variables.

Can't we just use the extension check to enable it for userspace?
Exposing the *envcfg CSRs to userspace seems tricky as everything is
currently built with the S/M CSRs removed from user builds.

Alistair

>
> > So while shadow stack and zicbo are similar in terms of enabling. Landing pad
> > enable differs.
> > You still want me to generalize *envcfg flow?
>
> Hmm.  I'll leave that to riscv maintainers; perhaps a third user is required to show
> generality...
>
>
> r~
>
Richard Henderson Aug. 27, 2024, 3:58 a.m. UTC | #5
On 8/27/24 13:53, Alistair Francis wrote:
> Exposing the *envcfg CSRs to userspace seems tricky as everything is
> currently built with the S/M CSRs removed from user builds.

It is as simple as moving them out of ifdefs, then initializing them as needed in reset 
for CONFIG_USER_ONLY.  That's what we do for Arm.


r~
Alistair Francis Aug. 27, 2024, 4:03 a.m. UTC | #6
On Tue, Aug 27, 2024 at 1:58 PM Richard Henderson
<richard.henderson@linaro.org> wrote:
>
> On 8/27/24 13:53, Alistair Francis wrote:
> > Exposing the *envcfg CSRs to userspace seems tricky as everything is
> > currently built with the S/M CSRs removed from user builds.
>
> It is as simple as moving them out of ifdefs, then initializing them as needed in reset
> for CONFIG_USER_ONLY.  That's what we do for Arm.

Is that really better though?

Then we have these CSRs that are included in the build, so people can
write code that checks the CSRs, but they are never actually changed.

I guess it simplified the CONFIG_USER_ONLY checks, which is handy and
your original point. But it seems like it is clunky that we have these
CSRs that are kind of fake

Alistair

>
>
> r~
Richard Henderson Aug. 27, 2024, 4:29 a.m. UTC | #7
On 8/27/24 14:03, Alistair Francis wrote:
> On Tue, Aug 27, 2024 at 1:58 PM Richard Henderson
> <richard.henderson@linaro.org> wrote:
>>
>> On 8/27/24 13:53, Alistair Francis wrote:
>>> Exposing the *envcfg CSRs to userspace seems tricky as everything is
>>> currently built with the S/M CSRs removed from user builds.
>>
>> It is as simple as moving them out of ifdefs, then initializing them as needed in reset
>> for CONFIG_USER_ONLY.  That's what we do for Arm.
> 
> Is that really better though?
> 
> Then we have these CSRs that are included in the build, so people can
> write code that checks the CSRs, but they are never actually changed.
> 
> I guess it simplified the CONFIG_USER_ONLY checks, which is handy and
> your original point. But it seems like it is clunky that we have these
> CSRs that are kind of fake

They're not fake.  They're a reflection of how the system-mode kernel configures the 
system-mode user environment.

The u[bf]cfien variables introduced in this patch set are an indication of this.  Within 
this patch set they're always false.  But the intent is to implement the (proposed) prctl 
syscalls that will set them to true (on hold waiting for kernel abi to land upstream, but 
were present in an earlier patch set revision.)

The correct implementation of those syscalls, in my opinion, is to set the corresponding 
[ms]envcfg bits.  Just as linux-user/aarch64/target_prctl.h does for SVE et al.


r~
Alistair Francis Aug. 27, 2024, 5:47 a.m. UTC | #8
On Tue, Aug 27, 2024 at 2:29 PM Richard Henderson
<richard.henderson@linaro.org> wrote:
>
> On 8/27/24 14:03, Alistair Francis wrote:
> > On Tue, Aug 27, 2024 at 1:58 PM Richard Henderson
> > <richard.henderson@linaro.org> wrote:
> >>
> >> On 8/27/24 13:53, Alistair Francis wrote:
> >>> Exposing the *envcfg CSRs to userspace seems tricky as everything is
> >>> currently built with the S/M CSRs removed from user builds.
> >>
> >> It is as simple as moving them out of ifdefs, then initializing them as needed in reset
> >> for CONFIG_USER_ONLY.  That's what we do for Arm.
> >
> > Is that really better though?
> >
> > Then we have these CSRs that are included in the build, so people can
> > write code that checks the CSRs, but they are never actually changed.
> >
> > I guess it simplified the CONFIG_USER_ONLY checks, which is handy and
> > your original point. But it seems like it is clunky that we have these
> > CSRs that are kind of fake
>
> They're not fake.  They're a reflection of how the system-mode kernel configures the
> system-mode user environment.
>
> The u[bf]cfien variables introduced in this patch set are an indication of this.  Within
> this patch set they're always false.  But the intent is to implement the (proposed) prctl
> syscalls that will set them to true (on hold waiting for kernel abi to land upstream, but
> were present in an earlier patch set revision.)
>
> The correct implementation of those syscalls, in my opinion, is to set the corresponding
> [ms]envcfg bits.  Just as linux-user/aarch64/target_prctl.h does for SVE et al.

Yeah, that actually does sound better.

That sounds like the way to go then

Alistair
diff mbox series

Patch

diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 8e1f05e5b1..083d405516 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -1022,6 +1022,11 @@  static void riscv_cpu_reset_hold(Object *obj, ResetType type)
     env->load_res = -1;
     set_default_nan_mode(1, &env->fp_status);
 
+#ifdef CONFIG_USER_ONLY
+    /* qemu-user for riscv, fcfi is off by default */
+    env->ufcfien = false;
+#endif
+
 #ifndef CONFIG_USER_ONLY
     if (cpu->cfg.debug) {
         riscv_trigger_reset_hold(env);
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index f966c36a31..7be0fa30f7 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -226,6 +226,7 @@  struct CPUArchState {
     bool      elp;
 #ifdef CONFIG_USER_ONLY
     uint32_t elf_flags;
+    bool ufcfien;
 #endif
 
 #ifndef CONFIG_USER_ONLY
@@ -530,6 +531,7 @@  void riscv_cpu_set_geilen(CPURISCVState *env, target_ulong geilen);
 bool riscv_cpu_vector_enabled(CPURISCVState *env);
 void riscv_cpu_set_virt_enabled(CPURISCVState *env, bool enable);
 int riscv_env_mmu_index(CPURISCVState *env, bool ifetch);
+bool cpu_get_fcfien(CPURISCVState *env);
 G_NORETURN void  riscv_cpu_do_unaligned_access(CPUState *cs, vaddr addr,
                                                MMUAccessType access_type,
                                                int mmu_idx, uintptr_t retaddr);
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index 6709622dd3..12484ca7d2 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -33,6 +33,7 @@ 
 #include "cpu_bits.h"
 #include "debug.h"
 #include "tcg/oversized-guest.h"
+#include "pmp.h"
 
 int riscv_env_mmu_index(CPURISCVState *env, bool ifetch)
 {
@@ -63,6 +64,34 @@  int riscv_env_mmu_index(CPURISCVState *env, bool ifetch)
 #endif
 }
 
+bool cpu_get_fcfien(CPURISCVState *env)
+{
+    /* no cfi extension, return false */
+    if (!env_archcpu(env)->cfg.ext_zicfilp) {
+        return false;
+    }
+#ifdef CONFIG_USER_ONLY
+    return env->ufcfien;
+#else
+    switch (env->priv) {
+    case PRV_U:
+        if (riscv_has_ext(env, RVS)) {
+            return env->senvcfg & MENVCFG_LPE;
+        }
+        return env->menvcfg & MENVCFG_LPE;
+    case PRV_S:
+        if (env->virt_enabled) {
+            return env->henvcfg & HENVCFG_LPE;
+        }
+        return env->menvcfg & MENVCFG_LPE;
+    case PRV_M:
+        return env->mseccfg & MSECCFG_MLPE;
+    default:
+        g_assert_not_reached();
+    }
+#endif
+}
+
 void cpu_get_tb_cpu_state(CPURISCVState *env, vaddr *pc,
                           uint64_t *cs_base, uint32_t *pflags)
 {
@@ -546,6 +575,15 @@  void riscv_cpu_swap_hypervisor_regs(CPURISCVState *env)
     }
     bool current_virt = env->virt_enabled;
 
+    /*
+     * If zicfilp extension available and henvcfg.LPE = 1,
+     * then apply SPELP mask on mstatus
+     */
+    if (env_archcpu(env)->cfg.ext_zicfilp &&
+        get_field(env->henvcfg, HENVCFG_LPE)) {
+        mstatus_mask |= SSTATUS_SPELP;
+    }
+
     g_assert(riscv_has_ext(env, RVH));
 
     if (current_virt) {
@@ -1754,6 +1792,11 @@  void riscv_cpu_do_interrupt(CPUState *cs)
     if (env->priv <= PRV_S && cause < 64 &&
         (((deleg >> cause) & 1) || s_injected || vs_injected)) {
         /* handle the trap in S-mode */
+        /* save elp status */
+        if (cpu_get_fcfien(env)) {
+            env->mstatus = set_field(env->mstatus, MSTATUS_SPELP, env->elp);
+        }
+
         if (riscv_has_ext(env, RVH)) {
             uint64_t hdeleg = async ? env->hideleg : env->hedeleg;
 
@@ -1802,6 +1845,11 @@  void riscv_cpu_do_interrupt(CPUState *cs)
         riscv_cpu_set_mode(env, PRV_S);
     } else {
         /* handle the trap in M-mode */
+        /* save elp status */
+        if (cpu_get_fcfien(env)) {
+            env->mstatus = set_field(env->mstatus, MSTATUS_MPELP, env->elp);
+        }
+
         if (riscv_has_ext(env, RVH)) {
             if (env->virt_enabled) {
                 riscv_cpu_swap_hypervisor_regs(env);
@@ -1833,6 +1881,13 @@  void riscv_cpu_do_interrupt(CPUState *cs)
         riscv_cpu_set_mode(env, PRV_M);
     }
 
+    /*
+     * Interrupt/exception/trap delivery is asynchronous event and as per
+     * zicfilp spec CPU should clear up the ELP state. No harm in clearing
+     * unconditionally.
+     */
+    env->elp = false;
+
     /*
      * NOTE: it is not necessary to yield load reservations here. It is only
      * necessary for an SC from "another hart" to cause a load reservation
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index 2baf5bc3ca..5848aaf437 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -313,6 +313,15 @@  target_ulong helper_sret(CPURISCVState *env)
 
     riscv_cpu_set_mode(env, prev_priv);
 
+    /*
+     * If forward cfi enabled for new priv, restore elp status
+     * and clear spelp in mstatus
+     */
+    if (cpu_get_fcfien(env)) {
+        env->elp = get_field(env->mstatus, MSTATUS_SPELP);
+    }
+    env->mstatus = set_field(env->mstatus, MSTATUS_SPELP, 0);
+
     return retpc;
 }
 
@@ -357,6 +366,15 @@  target_ulong helper_mret(CPURISCVState *env)
         riscv_cpu_set_virt_enabled(env, prev_virt);
     }
 
+    /*
+     * If forward cfi enabled for new priv, restore elp status
+     * and clear mpelp in mstatus
+     */
+    if (cpu_get_fcfien(env)) {
+        env->elp = get_field(env->mstatus, MSTATUS_MPELP);
+    }
+    env->mstatus = set_field(env->mstatus, MSTATUS_MPELP, 0);
+
     return retpc;
 }