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

arm64/vmm: Preserve PSR_C64 when injecting an exception #2255

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from

Conversation

markjdb
Copy link
Contributor

@markjdb markjdb commented Nov 27, 2024

I'm not sure if this might be simplistic, but it resolves a problem I see with breakpoint injection from bhyve's gdb stub. This arises when the debugger has installed a breakpoint, and the guest triggers a breakpoint exception some other way, e.g., a dtrace FBT probe.

sys/arm64/vmm/vmm_arm64.c Outdated Show resolved Hide resolved
@jrtc27
Copy link
Member

jrtc27 commented Nov 27, 2024

This looks to be the bug @kwitaszczyk was running into, and the cause of the problem aligns with what @bsdjhb and I had managed to ascertain at the PI meeting.

@jrtc27
Copy link
Member

jrtc27 commented Nov 27, 2024

The handling of vbar_el1 also looks a bit dodgy. If CPACR_EL1.CEN[0] is 0 then CVBAR_EL1 is just interpreted as VBAR_EL1 by the architecture when trapping to EL1 (setting PCC's address to it), which means we need to derive a capability for tf_elr from elr_el1.

@jrtc27
Copy link
Member

jrtc27 commented Nov 27, 2024

The handling of vbar_el1 also looks a bit dodgy. If CPACR_EL1.CEN[0] is 0 then CVBAR_EL1 is just interpreted as VBAR_EL1 by the architecture when trapping to EL1 (setting PCC's address to it), which means we need to derive a capability for tf_elr from elr_el1.

This should be testable by running GDB against a plain FreeBSD VM, setting a breakpoint from GDB and triggering a breakpoint from within the VM, just as for the SPSR C64 issue except with a FreeBSD guest. I'm not sure how exactly I expect it to break, whether it'll get stuck in a trap loop or end up in bhyve. Hopefully at least it doesn't wedge the host, which should be true as long as some of this code is preemptible... otherwise there are other ways a malicious guest could trigger the same kinds of issues even with correct handling here.

@kwitaszczyk
Copy link
Member

This looks to be the bug @kwitaszczyk was running into, and the cause of the problem aligns with what @bsdjhb and I had managed to ascertain at the PI meeting.

Unfortunately, it doesn't seem the host kernel enters the block of if (hypctx->has_exception) in my case. It does enter it but once the guest kernel ends up in kdb_enter() after the panic.

@bsdjhb
Copy link
Collaborator

bsdjhb commented Nov 29, 2024

Can you add a trace to see if vmmops_setreg is ever called to write a value to VM_REG_GUEST_CPSR?

@kwitaszczyk
Copy link
Member

kwitaszczyk commented Dec 2, 2024

Can you add a trace to see if vmmops_setreg is ever called to write a value to VM_REG_GUEST_CPSR?

It doesn't seem vmmops_setreg() is ever called for this purpose. I've added

diff --git a/sys/arm64/vmm/vmm_arm64.c b/sys/arm64/vmm/vmm_arm64.c
index a66d5ed1ba97..23efd3e4887d 100644
--- a/sys/arm64/vmm/vmm_arm64.c
+++ b/sys/arm64/vmm/vmm_arm64.c
@@ -1447,6 +1447,12 @@ vmmops_setreg(void *vcpui, int reg, uintcap_t val)
 #endif
                *(uintcap_t *)regp = val;
                break;
+       case VM_REG_GUEST_CPSR:
+               printf("%s:%d\nval=%lu\nelr_el1=%#lp\ntf_elr=%#lp\nspsr=0x%lx\n",
+                   __func__, __LINE__, (uint64_t)val, (void *)hypctx->elr_el1,
+                   (void *)hypctx->tf.tf_elr, hypctx->tf.tf_spsr);
+               *(uint64_t *)regp = (uint64_t)val;
+               break;
        default:
                *(uint64_t *)regp = (uint64_t)val;
                break;

and I don't get anything in the serial console.

Copy link
Collaborator

@bsdjhb bsdjhb left a comment

Choose a reason for hiding this comment

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

I do think there are other things to check (CEN) that Jess noted, but this is certainly an improvement over what is there now.

@markjdb
Copy link
Contributor Author

markjdb commented Dec 18, 2024

I do think there are other things to check (CEN) that Jess noted, but this is certainly an improvement over what is there now.

I have a patch to address that comment, but had been holding off on pushing it until I could test with hybrid kernels. Now I'm looking at an apparent regression with the VHE merge after I rebased onto dev; hopefully it won't take too long to fix.

@bsdjhb
Copy link
Collaborator

bsdjhb commented Jan 25, 2025

Is this blocked on the VHE regression or can this be merged as-is?

@markjdb
Copy link
Contributor Author

markjdb commented Feb 12, 2025

The handling of vbar_el1 also looks a bit dodgy. If CPACR_EL1.CEN[0] is 0 then CVBAR_EL1 is just interpreted as VBAR_EL1 by the architecture when trapping to EL1 (setting PCC's address to it), which means we need to derive a capability for tf_elr from elr_el1.

This should be testable by running GDB against a plain FreeBSD VM, setting a breakpoint from GDB and triggering a breakpoint from within the VM, just as for the SPSR C64 issue except with a FreeBSD guest. I'm not sure how exactly I expect it to break, whether it'll get stuck in a trap loop or end up in bhyve. Hopefully at least it doesn't wedge the host, which should be true as long as some of this code is preemptible... otherwise there are other ways a malicious guest could trigger the same kinds of issues even with correct handling here.

The latest PR addresses this. Without the patch, the bhyve vcpu threads end up in a loop somewhere, but the VM is killable, the bug doesn't bring down the host.

sys/arm64/vmm/vmm_arm64.c Outdated Show resolved Hide resolved
markjdb and others added 3 commits February 12, 2025 19:21
It's not required, and causes the host to freeze when single-stepping a
guest, for reasons that I don't understand.  The problem appeared only
after we started using VHE by default.
When the CPU is configured to trap upon execution of Morello-specific
instructions or access of Morello-specific control registers, i.e., vmm is
executing a vanilla aarch64 guest, we need to derive a capability from elr_el1,
since upon exception entry the capability value of PCC is set to VBAR_ELx.
@jrtc27
Copy link
Member

jrtc27 commented Feb 13, 2025

Re MDSCR_EL1.KDE, the relevant points are:

  • nVHE calls into EL2 via a trap, which sets all of PSTATE.{D,A,I,F}
  • VHE is in EL2 so just makes a function call directly to enter_guest so only PSTATE.{I,F} are set (from vmmops_run)
  • There's a window where we have MDSCR_EL1.{SS,KDE} set whilst still in the host
  • If either MDCR_EL2.TDE or HCR_EL2.TGE is set, this means ELD is EL2 and so non-breakpoint exceptions at EL2 enabled thanks to MDSCR_EL1.KDE (breakpoints are always enabled regardless of its value)
  • Therefore we effectively end up single-stepping the host

I think though this also means that breakpoints are enabled in this window for VHE, so were one to place a guest breakpoint at an address that is in the window where we're world switching we'd see that breakpoint be hit in the host. Single-stepping with MDSCR_EL1.KDE just makes this issue more obvious because it immediately takes effect wherever you are.

Now, I agree that MDSCR_EL1.KDE is unnecessary here. ELD is always going to be EL2 already thanks to MDCR_EL2.TDE so there's no need to set it for single-stepping EL1. However, the fact that it breaks things also shows that there is this other bug with regards to PSTATE.D; MDSCR_EL1.KDE's value should be entirely irrelevant. I believe that, were one to set PSTATE.D for the VHE case like happens implicitly from the trap in the nVHE case, the single-step issue would also be fixed.

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