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

Use shadowstack in SWT cloned module #224

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

Pavel-Durov
Copy link

@Pavel-Durov Pavel-Durov commented Jan 20, 2025

The ShadowStack pass, which transforms allocas into shadow stack pointers, is currently ignoring all unoptimised functions (code reference). However, we require these functions to utilise shadow stack pointers, as tracing is performed within them.

Furthermore, the unoptimised main function must be adjusted to use the same shadow stack instance as the optimised main function. This adjustment is essential because the unoptimised and optimised main functions need to share variables stored on the stack, facilitating the transitions between the versions.

This pull request ensures that both the optimised and unoptimised main functions use the same shadow stack. There is no need to establish a mapping between the allocas of the two functions, as the ordering of allocas remains consistent by definition; the functions are direct copies of one another.

Note: once we have the control point transition implemented, we can remove the shadow stack pointers from the optimised functions completely.

Example of variables in __yk_unopt_main declared before this change:

define dso_local i32 @__yk_unopt_main(i32 noundef %0, ptr noundef %1) local_unnamed_addr #0 !dbg !84 {
  call void @__yk_trace_basicblock(i32 9, i32 0)
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca ptr, align 8
  %6 = alloca ptr, align 8
  %7 = alloca %struct.YkLocation, align 8
  %8 = alloca i32, align 4
  %9 = alloca i32, align 4

Variables in __yk_unopt_main declared after this change:

define dso_local i32 @__yk_unopt_main(i32 noundef %0, ptr noundef %1) local_unnamed_addr #0 !dbg !83 {
  call void @__yk_trace_basicblock(i32 9, i32 0)
  %3 = load ptr, ptr @shadowstack_0, align 8
  %4 = getelementptr i8, ptr %3, i32 0
  %5 = getelementptr i8, ptr %3, i32 4
  %6 = getelementptr i8, ptr %3, i32 8
  %7 = alloca ptr, align 8
  %8 = alloca %struct.YkLocation, align 8
  %9 = getelementptr i8, ptr %3, i32 16
  store i32 0,  %30) #6, !dbg !131, !srcloc !84
  br label %31, !dbg !132

@Pavel-Durov Pavel-Durov requested a review from ptersilie January 20, 2025 20:22
Copy link
Author

@Pavel-Durov Pavel-Durov left a comment

Choose a reason for hiding this comment

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

We need to change:

if (F.getName() == MAIN || F.getName().startswith(YK_CLONE_PREFIX) == false) {

@Pavel-Durov Pavel-Durov force-pushed the swt-module-clone-shadowstack branch 2 times, most recently from a97d80f to 459fc46 Compare January 21, 2025 16:24
@Pavel-Durov Pavel-Durov force-pushed the swt-module-clone-shadowstack branch from 459fc46 to 78c196a Compare January 21, 2025 16:46
@Pavel-Durov
Copy link
Author

We need to change:

if (F.getName() == MAIN || F.getName().startswith(YK_CLONE_PREFIX) == false) {

We can skip inserting shadowstacks into unopt version once we'll have the cp transition in place.
Current yk SWT is dependent on these shadowstacks.

// If we have two control points, we need to update the cloned main
// function as well.
if (controlPointCount == 2) {
Function *MainClone = M.getFunction(YK_UNOPT_MAIN);

Choose a reason for hiding this comment

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

We still use the word clone in a couple of variables and comments here. I think we should get rid of it entirely and always say if we are talking about opt or unopt. Clone is just too confusing, especially as we've changed at least once which one the clone is and I can never remember.

Copy link
Author

Choose a reason for hiding this comment

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

Replaced.

// Load the current shadow stack pointer from the global variable.
LoadInst *LoadedSSPtr = BuilderClone.CreateLoad(Int8PtrTy, SSGlobal);
// Replace all allocas in MainClone with the loaded shadow stack pointer.
rewriteAllocas(DL, MainCloneAllocas, LoadedSSPtr);
Copy link

@ptersilie ptersilie Jan 22, 2025

Choose a reason for hiding this comment

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

We need at the very least a comment explaining why we believe that allocas in unopt will match the allocas in opt. But ideally we have asserts here that make sure. We might want to optimise the opt version slightly in the future which could change the variable order and then things will go wrong.

Copy link
Author

Choose a reason for hiding this comment

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

Added assertions 👉 fabd8f9

@Pavel-Durov Pavel-Durov changed the title Reuse shadowstack in swt cloned functions. Use shadowstack in SWT cloned module Jan 24, 2025
@ptersilie ptersilie self-assigned this Jan 24, 2025
@ltratt
Copy link

ltratt commented Jan 24, 2025

I'm a dummy, so the updated PR description is still too sparse for me. Can we see why we're doing this (not just how) please?

@Pavel-Durov
Copy link
Author

I'm a dummy, so the updated PR description is still too sparse for me. Can we see why we're doing this (not just how) please?

It's been updated, is it clear?

for (size_t i = 0; i < MainAllocas.size(); i++) {
auto &[optAI, optOffset] = MainAllocas[i];
auto &[unoptAI, unoptOffset] = UnoptMainAllocas[i];
assert(optOffset == unoptOffset && "Alloca offsets must match");
Copy link
Author

Choose a reason for hiding this comment

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

Any other values that need to be asserted here?
I noticed that the types are not always the same...
This assertion fails:

assert(optAI->getAllocatedType() == unoptAI->getAllocatedType() &&
               "Alloca types must match");

Choose a reason for hiding this comment

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

I think that should do. But it's weird that the types don't match. I wasn't expecting that to happen.

Copy link
Author

Choose a reason for hiding this comment

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

ok, yeah it's only in on specific yk test ang_tests::sdiv.c

Mismatched alloca types at index 7:
  Optimized alloca type: 0x70
  Unoptimized alloca type: 0x55e49c008e90

Choose a reason for hiding this comment

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

Yeah that's weird. If you do Instr->dump() it will give your more information which two instructions aren't matching.

@ptersilie
Copy link

It's been updated, is it clear?

It will do for now. Though I believe you got the two code blocks the wrong way around. Personally, I probably would have described the change more, rather than showing the code blocks, to give a little more context what is going on. Although a small example certainly doesn't hurt. For example, I might have written something like:

The shadowstack pass, which turns allocas into shadowstack pointers, ignores all optimised functions, since these aren't traced and thus don't require a shadow stack. We forgot, however, that the optimised main function is an exception since it needs to share variables stored on the stack with __yk_unopt_main so that we can transition between the two.

With this PR, we use the shadow stack for the optimised main function as well, e.g. allocas like %4 = alloca i32, align 4 get turned into accesses into the shadow stack like %4 = getelementptr i8, ptr %3, i32 0. There is no need to create a mapping between the variables of main and __yk_unopt_main since by definition the ordering is the same because the functions are copies of each other.

@Pavel-Durov
Copy link
Author

It's been updated, is it clear?

It will do for now. Though I believe you got the two code blocks the wrong way around. Personally, I probably would have described the change more, rather than showing the code blocks, to give a little more context what is going on. Although a small example certainly doesn't hurt. For example, I might have written something like:

The shadowstack pass, which turns allocas into shadowstack pointers, ignores all optimised functions, since these aren't traced and thus don't require a shadow stack. We forgot, however, that the optimised main function is an exception since it needs to share variables stored on the stack with __yk_unopt_main so that we can transition between the two.

With this PR, we use the shadow stack for the optimised main function as well, e.g. allocas like %4 = alloca i32, align 4 get turned into accesses into the shadow stack like %4 = getelementptr i8, ptr %3, i32 0. There is no need to create a mapping between the variables of main and __yk_unopt_main since by definition the ordering is the same because the functions are copies of each other.

Thank you, updated.

@ptersilie
Copy link

Thank you, updated.

Excellent. This looks good now.

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.

3 participants