Skip to content

Support for stack protectors. #60933

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

Merged
merged 11 commits into from
Sep 8, 2022
Merged

Conversation

eeckstein
Copy link
Contributor

@eeckstein eeckstein commented Sep 2, 2022

The main part of this PR is a new StackProtection optimization and changes in the stdlib

  • The optimization decides which functions need stack protection. It sets the needStackProtection flags on all function which contain stack-allocated values for which an buffer overflow could occur.

  • In the stdlib there are new underscored _withUnprotected..Pointer functions. These functions work the same way as their "unprotected" counterparts, except that they don't trigger stack protection for the pointers. They can be used to opt out of stack protection.

  • Also, in the stdlib, opt out of stack protection in places where there can't be buffer overflows.
    We trust the internal implementation of the stdlib to not cause any unintentional buffer overflows. In such cases we can use the "unprotected" address-to-pointer conversions. This avoids inserting stack protections where it's not needed.

For details see the commit messages.

Note: this PR does not enable stack protection by default, yet. It still needs to be enabled with the -Xfrontend -enable-stack-protector option.

rdar://93677524

@eeckstein
Copy link
Contributor Author

@swift-ci test

@eeckstein
Copy link
Contributor Author

@swift-ci benchmark

@aschwaighofer
Copy link
Contributor

Now that Swift decides whether or not we need stack protection we should force LLVM to emit stack protection when we request it:

--- a/lib/IRGen/IRGenModule.cpp
+++ b/lib/IRGen/IRGenModule.cpp
@@ -1265,7 +1265,7 @@ void IRGenModule::constructInitialFnAttributes(
     Attrs.removeAttribute(llvm::Attribute::OptimizeForSize);
   }
   if (stackProtector == StackProtectorMode::StackProtector) {
-    Attrs.addAttribute(llvm::Attribute::StackProtectStrong);
+    Attrs.addAttribute(llvm::Attribute::StackProtectReq);
     Attrs.addAttribute("stack-protector-buffer-size", llvm::utostr(8));
   }
 }

Copy link
Member

@lorentey lorentey left a comment

Choose a reason for hiding this comment

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

The stdlib parts look good! 👍

(Will stack protection also trigger on functions that use withUnsafeTemporaryAllocation?)

@eeckstein
Copy link
Contributor Author

Will stack protection also trigger on functions that use withUnsafeTemporaryAllocation?

Good point! It should, but I need to add handling of the "stackAlloc" builtin to make it work.

@eeckstein
Copy link
Contributor Author

@lorentey I added support for withUnsafeTemporaryAllocation.
Also, I added #if $BuiltinUnprotectedAddressOf around uses of the new builtins in inlinable functions.

@eeckstein
Copy link
Contributor Author

@swift-ci test

@@ -1951,7 +1951,8 @@ func _modifyAtWritableKeyPath_impl<Root, Value>(
keyPath: _unsafeUncheckedDowncast(keyPath,
to: ReferenceWritableKeyPath<Root, Value>.self))
}
return keyPath._projectMutableAddress(from: &root)
let ptr = UnsafePointer<Root>(Builtin.unprotectedAddressOf(&root))
Copy link
Contributor

@karwa karwa Sep 7, 2022

Choose a reason for hiding this comment

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

Can this use the scoped _withUnprotectedUnsafePointer function rather than using the Builtin to get an unscoped pointer?

Same comment for Random and StringSwitch. Is there a reason not to use the scoped versions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, that's a good idea

It doesn't make sense to let getAccessPathWithScope return an `EnclosingScope` as the second tuple element, because in case it's a `base`, it duplicates the `AccessBase` (which is returned in the first tuple element).
Instead just return an optional `BeginAccessInst` which is not nil if such an "scope" is found.
Indicates that stack protectors are inserted into this function to detect stack related buffer overflows.
…ndex_addr` instructions.

Also add new "unprotected" variants of the `addressof` builtins:
* `Builtin.unprotectedAddressOf`
* `Builtin.unprotectedAddressOfBorrow`
…LOptions.

... because we need it in the SIL pass pipeline, too.
Also, add Swift bridging for that option.
Those functions work the same way as their "unprotected" counterparts, except that they don't trigger stack protection for the pointers.

* `_withUnprotectedUnsafeMutablePointer`
* `_withUnprotectedUnsafePointer`
* `_withUnprotectedUnsafeBytes` (two times)
…ffer overflows.

We trust the internal implementation of the stdlib to not cause any unintentional buffer overflows.
In such cases we can use the "unprotected" address-to-pointer conversions.
This avoids inserting stack protections where it's not needed.
And make all the case identifiers lowercase.
It decides which functions need stack protection.

It sets the `needStackProtection` flags on all function which contain stack-allocated values for which an buffer overflow could occur.

Within safe swift code there shouldn't be any buffer overflows.
But if the address of a stack variable is converted to an unsafe pointer, it's not in the control of the compiler anymore.
This means, if there is any `address_to_pointer` instruction for an `alloc_stack`, such a function is marked for stack protection.
Another case is `index_addr` for non-tail allocated memory.
This pattern appears if pointer arithmetic is done with unsafe pointers in swift code.

If the origin of an unsafe pointer can only be tracked to a function argument, the pass tries to find the root stack allocation for such an argument by doing an inter-procedural analysis.
If this is not possible, the fallback is to move the argument into a temporary `alloc_stack` and do the unsafe pointer operations on the temporary.

rdar://93677524
…tack protection.

Now that Swift decides whether or not we need stack protection we should force LLVM to emit stack protection when we request it.
@eeckstein
Copy link
Contributor Author

@swift-ci test

@eeckstein
Copy link
Contributor Author

@swift-ci benchmark

eeckstein added a commit to eeckstein/swift that referenced this pull request Nov 11, 2022
The pass to decide which functions should get stack protection was added in swiftlang#60933, but was disabled by default.

This PR enables stack protection by default, but not the possibility to move arguments into temporaries - to keep the risk low.
Moving to temporaries can be enabled with the new frontend option `-enable-move-inout-stack-protector`.

rdar://93677524
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