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

implement shadow stacks #455

Merged
merged 16 commits into from
Nov 6, 2024
Merged

Conversation

Freax13
Copy link
Contributor

@Freax13 Freax13 commented Sep 12, 2024

This PR implements shadow stacks.

Shadow stacks are enabled/disabled at compile time. AFAIK all processors supporting either AMD SEV-SNP or Intel TDX support shadow stacks, so no runtime checks are implemented. This also avoids the pitfall where the hypervisor lies about shadow stack availability to maliciously lower the security of the SVSM.

KVM intercepts accesses to the MSRs used for shadow stacks, so some host-side modifications are required to make this work.

I implemented a #CP exception handler to display diagnostic information when a shadow stack-related issue occurs. We might want to disable this exception handler for release builds and cause a triple fault instead. #CP exceptions are likely a sign of something fishy going on and we might want to terminate the guest just to be sure.

Currently, shadow stacks are disabled by default for a couple of reasons:

  1. The KVM patches mentioned above are needed to make this work.
  2. We haven't implemented proper user syscalls (using syscall & sysret) yet and I don't want to hinder any efforts in that direction by forcing a proper syscall implementation to support shadow stacks out of the box. We should probably get syscalls without shadow stacks working first and once that's merged, we can add shadow stack support later and eventually enable shadow stacks by default.
  3. I haven't tested shadow-stacks together with enable-gdb yet, but I suspect this probably needs some additional work.

Copy link
Member

@joergroedel joergroedel left a comment

Choose a reason for hiding this comment

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

This is impressive, thanks for implementing this feature!

There are some open questions, one around the task-switch handling. I left a comment there. The other question is, whether there is a way to enable/disable this feature at boot-time, instead of compile time.

Finally, I found that it uses the old PageRef interface. Since the new one is merged now, this can be re-based on top of latest HEAD.

kernel/src/task/schedule.rs Outdated Show resolved Hide resolved
@Freax13 Freax13 force-pushed the feature/shadow-stack branch 2 times, most recently from 8a88f3b to cc5c763 Compare September 18, 2024 13:54
@Freax13
Copy link
Contributor Author

Freax13 commented Sep 18, 2024

There are some open questions, one around the task-switch handling. I left a comment there. The other question is, whether there is a way to enable/disable this feature at boot-time, instead of compile time.

I added a patch that tries to detect CET support at runtime by enabling CET in CR4 and catching any faults that might occur if not supported. This still doesn't technically detect shadow stack support because AFAICT CPUs could theoretically only support CET lBT (indirect branch tracking) and not CET SHSTK (shadow stacks), but in practice no such CPUs exist (and AFAICT TDX only allows turning all of CET on or off, not individual sub-parts).

Finally, I found that it uses the old PageRef interface. Since the new one is merged now, this can be re-based on top of latest HEAD.

Done.

@msft-jlange
Copy link
Contributor

It doesn't look like this change is compatible with the existing #HV handling code. For example, there is code in asm_entry_hv which specifically checks to see whether a recursive #HV has been delivered while executing an IRET sequence, and if so, it overwrites the old IRET data with the newly pushed IRET data. The shadow stack equivalent of this code must be written, but it is also inherently unsafe, because it requires popping values from the shadow stack and using the WRSS instruction to write them to a previous location on the shadow stack. Regardless of what approach we ultimately decide to take here, it is critical to support Restricted Injection correctly, and we can't allow shadow stack support to break it.

@Freax13
Copy link
Contributor Author

Freax13 commented Sep 23, 2024

It doesn't look like this change is compatible with the existing #HV handling code. For example, there is code in asm_entry_hv which specifically checks to see whether a recursive #HV has been delivered while executing an IRET sequence, and if so, it overwrites the old IRET data with the newly pushed IRET data. The shadow stack equivalent of this code must be written, but it is also inherently unsafe, because it requires popping values from the shadow stack and using the WRSS instruction to write them to a previous location on the shadow stack. Regardless of what approach we ultimately decide to take here, it is critical to support Restricted Injection correctly, and we can't allow shadow stack support to break it.

Interesting, thanks for pointing that out, I'll take a look at that. Do you know if there are KVM patches implementing restricted injection that I can test with?

@msft-jlange
Copy link
Contributor

Do you know if there are KVM patches implementing restricted injection that I can test with?

Unfortunately, there are none that I am aware of. However, I'm happy to review and test any changes you have in our environment which does support restricted injection. Let me know if you want a more thorough description of how the existing #HV handler works and interacts with the IRET flow.

@msft-jlange
Copy link
Contributor

AFAIK all processors supporting either AMD SEV-SNP or Intel TDX support shadow stacks

That's not actually true. The Zen3 architecture supports SEV-SNP but not shadow stacks.

@Freax13
Copy link
Contributor Author

Freax13 commented Sep 24, 2024

AFAIK all processors supporting either AMD SEV-SNP or Intel TDX support shadow stacks

That's not actually true. The Zen3 architecture supports SEV-SNP but not shadow stacks.

EPYC Milan (based on Zen 3) supports shadow stacks. I used an EPYC Milan based machine to test these patches.

@msft-jlange
Copy link
Contributor

EPYC Milan (based on Zen 3) supports shadow stacks. I used an EPYC Milan based machine to test these patches.

You are correct; I was misinformed.

@Freax13
Copy link
Contributor Author

Freax13 commented Sep 25, 2024

It doesn't look like this change is compatible with the existing #HV handling code. For example, there is code in asm_entry_hv which specifically checks to see whether a recursive #HV has been delivered while executing an IRET sequence, and if so, it overwrites the old IRET data with the newly pushed IRET data. The shadow stack equivalent of this code must be written, but it is also inherently unsafe, because it requires popping values from the shadow stack and using the WRSS instruction to write them to a previous location on the shadow stack. Regardless of what approach we ultimately decide to take here, it is critical to support Restricted Injection correctly, and we can't allow shadow stack support to break it.

I added a patch to adjust the shadow stack in the #HV.

Do you know if there are KVM patches implementing restricted injection that I can test with?

Unfortunately, there are none that I am aware of. However, I'm happy to review and test any changes you have in our environment which does support restricted injection. Let me know if you want a more thorough description of how the existing #HV handler works and interacts with the IRET flow.

Could you please test my patches on your environment? I tried testing by manually adding int $28 to some places, but I still don't have a proper test setup, so I might have missed something. I also came across some bugs/regressions unrelated to shadow stacks, so I opened a separate PR to fix those: #466. You might need those changes to properly test the changes in this PR.

@joergroedel joergroedel added the in-review PR is under active review and not yet approved label Sep 26, 2024
@msft-jlange
Copy link
Contributor

Could you please test my patches on your environment? I tried testing by manually adding int $28 to some places, but I still don't have a proper test setup, so I might have missed something. I also came across some bugs/regressions unrelated to shadow stacks, so I opened a separate PR to fix those: #466. You might need those changes to properly test the changes in this PR.

Other than the specific stack adjustment problem I commented on (the one that's missing), everything appears to work. The other two shadow stack adjustments are correct. And I found another problem with the #HV handler that's not addressed by your other PR so I'll submit a fix for that too. Thanks for prompting me to review this code again!

@p4zuu p4zuu mentioned this pull request Sep 30, 2024
@ereshetova
Copy link

KVM intercepts accesses to the MSRs used for shadow stacks, so some host-side modifications are required to make this work.

Not very relevant yet, but this is not true for TDX case. The CET/shadow stack related MSRs are handled natively for the guest (given that the feature is enabled for the guest during its creation via config) with no host involvement (wont be secure otherwise). TDX module ensures this. So you should be able to enable this for TDX without any host/kvm support (but enabling TD_PARAMS.XFAM[11] || TD_PARAMS.XFAM[12] - matching XSAVE[11],XSAVE[12] for a given guest).

@msft-jlange
Copy link
Contributor

There's still one issue. You changed the size of the exception frame, and you've adjusted most of the places that refer to the stack frame accordingly. However, the reference at line 151 was never updated, and it is now inaccurate. So this sequence:

        testl   $0x200, 18*8(%rcx)
        jz      postpone_hv

needs to become

        testl   $0x200, 20*8(%rcx)
        jz      postpone_hv

If I make that change locally, then everything works as expected (or would work, if all of the other #HV-related PRs were included).

@Freax13
Copy link
Contributor Author

Freax13 commented Oct 1, 2024

There's still one issue. You changed the size of the exception frame, and you've adjusted most of the places that refer to the stack frame accordingly. However, the reference at line 151 was never updated, and it is now inaccurate. So this sequence:

        testl   $0x200, 18*8(%rcx)
        jz      postpone_hv

needs to become

        testl   $0x200, 20*8(%rcx)
        jz      postpone_hv

If I make that change locally, then everything works as expected (or would work, if all of the other #HV-related PRs were included).

Good catch, should be fixed now! Thank you very much for your review!

@Freax13 Freax13 force-pushed the feature/shadow-stack branch 2 times, most recently from 2a80d26 to f1839e7 Compare October 2, 2024 15:04
@Freax13
Copy link
Contributor Author

Freax13 commented Oct 18, 2024

@joergroedel I think this PR is ready.

@msft-jlange
Copy link
Contributor

I just found one additional serious problem.

There are functions that generate page table flags to represent different page permissions. However, some of these functions will make the PTE dirty without also marking it writable, and these functions will effectively generate shadow stack permissions, which is not correct for those permission types. Specifically, all methods of PTEntryFlags other than PTEntryFlags::data() and PTEntryFlags::task_data() need to remove the DIRTY bit (anything that does not set WRITABLE should not set DIRTY). These must be fixed or else the shadow stack logic will not be secure.

@Freax13
Copy link
Contributor Author

Freax13 commented Oct 23, 2024

I just found one additional serious problem.

There are functions that generate page table flags to represent different page permissions. However, some of these functions will make the PTE dirty without also marking it writable, and these functions will effectively generate shadow stack permissions, which is not correct for those permission types. Specifically, all methods of PTEntryFlags other than PTEntryFlags::data() and PTEntryFlags::task_data() need to remove the DIRTY bit (anything that does not set WRITABLE should not set DIRTY). These must be fixed or else the shadow stack logic will not be secure.

Good catch! Should be fixed now.

When CET is enabled, pages marked as DIRTY but not WRITABLE are treated
as special pages used for storing shadow stacks. We must not use this
combination of flags for pages not meant to be used for shadow stacks.

Signed-off-by: Tom Dohrmann <[email protected]>
The initialization and pt_flags are a bit special for shadow stack
pages, so this warrants a new `VirtualMapping` implementations.

Signed-off-by: Tom Dohrmann <[email protected]>
This shadow stack is used when not using a task's shadow stack.

Signed-off-by: Tom Dohrmann <[email protected]>
The interrupt shadow stack table (ISST) is very similar to the
interrupt stack table (IST) except that it contains shadow stack
addresses instead of normal stack addresses.

Signed-off-by: Tom Dohrmann <[email protected]>
Each task needs to a normal shadow stack and shadow stack used for
exception handling.

Signed-off-by: Tom Dohrmann <[email protected]>
Some exception handlers will need to update the shadow stack, so they
need to know the shadow stack pointer at the time of the exception.

Signed-off-by: Tom Dohrmann <[email protected]>
Whenever we update the return address on the shadow stack, we'll also
need to update the return address on the shadow stack.

Signed-off-by: Tom Dohrmann <[email protected]>
The #HV handler messes with the stack frame and shadow stack needs to
be adjusted accordingly.

Signed-off-by: Tom Dohrmann <[email protected]>
Unlike the various From and Into implementations, this method can be
called in const contexts.

Signed-off-by: Tom Dohrmann <[email protected]>
We need to guard against IRQs coming in after switching to the new page tables
and before switching to the new stack.

Signed-off-by: Tom Dohrmann <[email protected]>
Each task has separate shadow stacks, so we need to switch them when
switching tasks.

Signed-off-by: Tom Dohrmann <[email protected]>
This enables shadow stacks for the BSP.

Signed-off-by: Tom Dohrmann <[email protected]>
This enables shadow stacks on the secondary APs.

Signed-off-by: Tom Dohrmann <[email protected]>
This exception handler will be executed when the CPU detects a mismatch
between the return address on the stack and the return address on the
shadow stack.

Signed-off-by: Tom Dohrmann <[email protected]>
Trusted CPUID values are hard to come by, so let's just try to enable
CET in CR4 and handle failure gracefully.

Signed-off-by: Tom Dohrmann <[email protected]>
Copy link
Member

@joergroedel joergroedel left a comment

Choose a reason for hiding this comment

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

LGTM.

@joergroedel joergroedel merged commit 6d433b7 into coconut-svsm:main Nov 6, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in-review PR is under active review and not yet approved
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants