Skip to content

Commit ddc69e7

Browse files
authored
Merge pull request #62057 from eeckstein/enable-stack-protection
SILOptimizer: enable stack protection by default
2 parents baa1d49 + ef302ce commit ddc69e7

File tree

10 files changed

+270
-177
lines changed

10 files changed

+270
-177
lines changed

SwiftCompilerSources/Sources/Optimizer/ModulePasses/StackProtection.swift

Lines changed: 91 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,24 @@ private func log(_ message: @autoclosure () -> String) {
2828
/// Within safe swift code there shouldn't be any buffer overflows. But if the address
2929
/// of a stack variable is converted to an unsafe pointer, it's not in the control of
3030
/// the compiler anymore.
31-
/// This means, if there is any `address_to_pointer` instruction for an `alloc_stack`,
32-
/// such a function is marked for stack protection.
33-
/// Another case is `index_addr` for non-tail allocated memory. This pattern appears if
34-
/// pointer arithmetic is done with unsafe pointers in swift code.
31+
/// This means, if an `alloc_stack` ends up at an `address_to_pointer [stack_protection]`,
32+
/// the `alloc_stack`'s function is marked for stack protection.
33+
/// Another case is `index_addr [stack_protection]` for non-tail allocated memory. This
34+
/// pattern appears if pointer arithmetic is done with unsafe pointers in swift code.
3535
///
3636
/// If the origin of an unsafe pointer can only be tracked to a function argument, the
3737
/// pass tries to find the root stack allocation for such an argument by doing an
38-
/// inter-procedural analysis. If this is not possible, the fallback is to move the
39-
/// argument into a temporary `alloc_stack` and do the unsafe pointer operations on
40-
/// the temporary.
38+
/// inter-procedural analysis. If this is not possible and the `enableMoveInoutStackProtection`
39+
/// option is set, the fallback is to move the argument into a temporary `alloc_stack`
40+
/// and do the unsafe pointer operations on the temporary.
4141
let stackProtection = ModulePass(name: "stack-protection", {
4242
(context: ModulePassContext) in
4343

4444
if !context.options.enableStackProtection {
4545
return
4646
}
4747

48-
var optimization = StackProtectionOptimization()
48+
var optimization = StackProtectionOptimization(enableMoveInout: context.options.enableMoveInoutStackProtection)
4949
optimization.processModule(context)
5050
})
5151

@@ -60,13 +60,15 @@ let functionStackProtection = FunctionPass(name: "function-stack-protection", {
6060
return
6161
}
6262

63-
var optimization = StackProtectionOptimization()
63+
var optimization = StackProtectionOptimization(enableMoveInout: context.options.enableMoveInoutStackProtection)
6464
optimization.process(function: function, context)
6565
})
6666

6767
/// The optimization algorithm.
6868
private struct StackProtectionOptimization {
6969

70+
private let enableMoveInout: Bool
71+
7072
// The following members are nil/not used if this utility is used on function-level.
7173

7274
private var moduleContext: ModulePassContext?
@@ -76,7 +78,11 @@ private struct StackProtectionOptimization {
7678
// Functions (other than the currently processed one) which need stack protection,
7779
// are added to this array in `findOriginsInCallers`.
7880
private var needStackProtection: [Function] = []
79-
81+
82+
init(enableMoveInout: Bool) {
83+
self.enableMoveInout = enableMoveInout
84+
}
85+
8086
/// The main entry point if running on module-level.
8187
mutating func processModule(_ moduleContext: ModulePassContext) {
8288
self.moduleContext = moduleContext
@@ -147,6 +153,8 @@ private struct StackProtectionOptimization {
147153
case .yes:
148154
// For example:
149155
// %baseAddr = alloc_stack $T
156+
log("local: \(function.name) -- \(instruction)")
157+
150158
function.setNeedsStackProtection(context)
151159

152160
case .decidedInCaller(let arg):
@@ -157,7 +165,7 @@ private struct StackProtectionOptimization {
157165
defer { worklist.deinitialize() }
158166
worklist.push(arg)
159167

160-
if !findOriginsInCallers(&worklist) {
168+
if findOriginsInCallers(&worklist) == NeedInsertMoves.yes {
161169
// We don't know the origin of the function argument. Therefore we need to do the
162170
// conservative default which is to move the value to a temporary stack location.
163171
if let beginAccess = scope {
@@ -179,22 +187,19 @@ private struct StackProtectionOptimization {
179187

180188
// If the object is passed as an argument to its function, add those arguments
181189
// to the worklist.
182-
switch worklist.push(rootsOf: obj) {
183-
case .failed:
184-
// If we cannot find the roots, the object is most likely not stack allocated.
185-
return
186-
case .succeeded(let foundStackAlloc):
187-
if foundStackAlloc {
188-
// The object is created by an `alloc_ref [stack]`.
189-
function.setNeedsStackProtection(context)
190-
}
190+
let (_, foundStackAlloc) = worklist.push(rootsOf: obj)
191+
if foundStackAlloc {
192+
// The object is created by an `alloc_ref [stack]`.
193+
log("objectIfStackPromoted: \(function.name) -- \(instruction)")
194+
195+
function.setNeedsStackProtection(context)
191196
}
192197
// In case the (potentially) stack allocated object is passed via an argument,
193198
// process the worklist as we do for indirect arguments (see above).
194199
// For example:
195200
// bb0(%0: $Class):
196201
// %baseAddr = ref_element_addr %0 : $Class, #Class.field
197-
if !findOriginsInCallers(&worklist),
202+
if findOriginsInCallers(&worklist) == NeedInsertMoves.yes,
198203
let beginAccess = scope {
199204
// We don't know the origin of the object. Therefore we need to do the
200205
// conservative default which is to move the value to a temporary stack location.
@@ -206,15 +211,26 @@ private struct StackProtectionOptimization {
206211
break
207212
}
208213
}
209-
214+
215+
/// Return value of `findOriginsInCallers()`.
216+
enum NeedInsertMoves {
217+
// Not all call sites could be identified, and if moves are enabled (`enableMoveInout`)
218+
// the original argument should be moved to a temporary.
219+
case yes
220+
221+
// Either all call sites could be identified, which means that stack protection is done
222+
// in the callers, or moves are not enabled (`enableMoveInout` is false).
223+
case no
224+
}
225+
210226
/// Find all origins of function arguments in `worklist`.
211227
/// All functions, which allocate such an origin are added to `self.needStackProtection`.
212228
/// Returns true if all origins could be found and false, if there are unknown origins.
213-
private mutating func findOriginsInCallers(_ worklist: inout ArgumentWorklist) -> Bool {
229+
private mutating func findOriginsInCallers(_ worklist: inout ArgumentWorklist) -> NeedInsertMoves {
214230

215231
guard let moduleContext = moduleContext else {
216232
// Don't do any inter-procedural analysis when used on function-level.
217-
return false
233+
return enableMoveInout ? .yes : .no
218234
}
219235

220236
// Put the resulting functions into a temporary array, because we only add them to
@@ -230,28 +246,33 @@ private struct StackProtectionOptimization {
230246
while let arg = worklist.pop() {
231247
let f = arg.function
232248
let uses = functionUses.getUses(of: f)
233-
if uses.hasUnknownUses {
234-
return false
249+
if uses.hasUnknownUses && enableMoveInout {
250+
return NeedInsertMoves.yes
235251
}
236252

237253
for useInst in uses {
238254
guard let fri = useInst as? FunctionRefInst else {
239-
return false
255+
if enableMoveInout {
256+
return NeedInsertMoves.yes
257+
}
258+
continue
240259
}
241260

242261
for functionRefUse in fri.uses {
243-
guard let apply = functionRefUse.instruction as? ApplySite else {
244-
return false
245-
}
246-
guard let callerArgIdx = apply.callerArgIndex(calleeArgIndex: arg.index) else {
247-
return false
262+
guard let apply = functionRefUse.instruction as? ApplySite,
263+
let callerArgIdx = apply.callerArgIndex(calleeArgIndex: arg.index) else {
264+
if enableMoveInout {
265+
return NeedInsertMoves.yes
266+
}
267+
continue
248268
}
249269
let callerArg = apply.arguments[callerArgIdx]
250270
if callerArg.type.isAddress {
251271
// It's an indirect argument.
252272
switch callerArg.accessBase.isStackAllocated {
253273
case .yes:
254274
if !callerArg.function.needsStackProtection {
275+
log("alloc_stack in caller: \(callerArg.function.name) -- \(callerArg)")
255276
newFunctions.push(callerArg.function)
256277
}
257278
case .no:
@@ -266,36 +287,38 @@ private struct StackProtectionOptimization {
266287
case .objectIfStackPromoted(let obj):
267288
// If the object is passed as an argument to its function,
268289
// add those arguments to the worklist.
269-
switch worklist.push(rootsOf: obj) {
270-
case .failed:
271-
return false
272-
case .succeeded(let foundStackAlloc):
273-
if foundStackAlloc && !obj.function.needsStackProtection {
274-
// The object is created by an `alloc_ref [stack]`.
275-
newFunctions.push(obj.function)
276-
}
290+
let (foundUnknownRoots, foundStackAlloc) = worklist.push(rootsOf: obj)
291+
if foundUnknownRoots && enableMoveInout {
292+
return NeedInsertMoves.yes
293+
}
294+
if foundStackAlloc && !obj.function.needsStackProtection {
295+
// The object is created by an `alloc_ref [stack]`.
296+
log("object in caller: \(obj.function.name) -- \(obj)")
297+
newFunctions.push(obj.function)
277298
}
278299
case .unknown:
279-
return false
300+
if enableMoveInout {
301+
return NeedInsertMoves.yes
302+
}
280303
}
281304
} else {
282305
// The argument is an object. If the object is itself passed as an argument
283306
// to its function, add those arguments to the worklist.
284-
switch worklist.push(rootsOf: callerArg) {
285-
case .failed:
286-
return false
287-
case .succeeded(let foundStackAlloc):
288-
if foundStackAlloc && !callerArg.function.needsStackProtection {
289-
// The object is created by an `alloc_ref [stack]`.
290-
newFunctions.push(callerArg.function)
291-
}
307+
let (foundUnknownRoots, foundStackAlloc) = worklist.push(rootsOf: callerArg)
308+
if foundUnknownRoots && enableMoveInout {
309+
return NeedInsertMoves.yes
310+
}
311+
if foundStackAlloc && !callerArg.function.needsStackProtection {
312+
// The object is created by an `alloc_ref [stack]`.
313+
log("object arg in caller: \(callerArg.function.name) -- \(callerArg)")
314+
newFunctions.push(callerArg.function)
292315
}
293316
}
294317
}
295318
}
296319
}
297320
needStackProtection.append(contentsOf: newFunctions)
298-
return true
321+
return NeedInsertMoves.no
299322
}
300323

301324
/// Moves the value of an indirect argument to a temporary stack location, if possible.
@@ -366,9 +389,16 @@ private struct StackProtectionOptimization {
366389
/// Worklist for inter-procedural analysis of function arguments.
367390
private struct ArgumentWorklist : ValueUseDefWalker {
368391
var walkUpCache = WalkerCache<SmallProjectionPath>()
392+
393+
// Used in `push(rootsOf:)`
369394
private var foundStackAlloc = false
395+
private var foundUnknownRoots = false
370396

397+
// Contains arguments which are already handled and don't need to be put into the worklist again.
398+
// Note that this cannot be a `ValueSet`, because argument can be from different functions.
371399
private var handled = Set<FunctionArgument>()
400+
401+
// The actual worklist.
372402
private var list: Stack<FunctionArgument>
373403

374404
init(_ context: PassContext) {
@@ -385,21 +415,15 @@ private struct ArgumentWorklist : ValueUseDefWalker {
385415
}
386416
}
387417

388-
enum PushResult {
389-
case failed
390-
case succeeded(foundStackAlloc: Bool)
391-
}
392-
393418
/// Pushes all roots of `object`, which are function arguments, to the worklist.
394-
/// Returns `.succeeded(true)` if some of the roots are `alloc_ref [stack]` instructions.
395-
mutating func push(rootsOf object: Value) -> PushResult {
419+
/// If the returned `foundUnknownRoots` is true, it means that not all roots of `object` could
420+
/// be tracked to a function argument.
421+
/// If the returned `foundStackAlloc` than at least one found root is an `alloc_ref [stack]`.
422+
mutating func push(rootsOf object: Value) -> (foundUnknownRoots: Bool, foundStackAlloc: Bool) {
396423
foundStackAlloc = false
397-
switch walkUp(value: object, path: SmallProjectionPath(.anything)) {
398-
case .continueWalk:
399-
return .succeeded(foundStackAlloc: foundStackAlloc)
400-
case .abortWalk:
401-
return .failed
402-
}
424+
foundUnknownRoots = false
425+
_ = walkUp(value: object, path: SmallProjectionPath(.anything))
426+
return (foundUnknownRoots, foundStackAlloc)
403427
}
404428

405429
mutating func pop() -> FunctionArgument? {
@@ -413,15 +437,12 @@ private struct ArgumentWorklist : ValueUseDefWalker {
413437
if ar.canAllocOnStack {
414438
foundStackAlloc = true
415439
}
416-
return .continueWalk
417440
case let arg as FunctionArgument:
418-
if handled.insert(arg).0 {
419-
list.push(arg)
420-
}
421-
return .continueWalk
422-
default:
423-
return .abortWalk
441+
push(arg)
442+
default:
443+
foundUnknownRoots = true
424444
}
445+
return .continueWalk
425446
}
426447
}
427448

@@ -491,7 +512,6 @@ private extension Instruction {
491512
private extension Function {
492513
func setNeedsStackProtection(_ context: PassContext) {
493514
if !needsStackProtection {
494-
log("needs protection: \(name)")
495515
set(needStackProtection: true, context)
496516
}
497517
}

SwiftCompilerSources/Sources/Optimizer/PassManager/Options.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ struct Options {
1818
var enableStackProtection: Bool {
1919
SILOptions_enableStackProtection(_bridged) != 0
2020
}
21+
22+
var enableMoveInoutStackProtection: Bool {
23+
SILOptions_enableMoveInoutStackProtection(_bridged) != 0
24+
}
2125
}

include/swift/AST/SILOptions.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ class SILOptions {
129129
bool EnablePerformanceAnnotations = false;
130130

131131
/// Enables the emission of stack protectors in functions.
132-
bool EnableStackProtection = false;
132+
bool EnableStackProtection = true;
133+
134+
/// Like `EnableStackProtection` and also enables moving of values to
135+
/// temporaries for stack protection.
136+
bool EnableMoveInoutStackProtection = false;
133137

134138
/// Controls whether or not paranoid verification checks are run.
135139
bool VerifyAll = false;

include/swift/Option/FrontendOptions.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,9 @@ def enable_stack_protector :
11041104
def disable_stack_protector :
11051105
Flag<["-"], "disable-stack-protector">,
11061106
HelpText<"Disable the stack-protector">;
1107+
def enable_move_inout_stack_protector :
1108+
Flag<["-"], "enable-move-inout-stack-protector">,
1109+
HelpText<"Enable the stack protector by moving values to temporaries">;
11071110

11081111
def enable_new_llvm_pass_manager :
11091112
Flag<["-"], "enable-new-llvm-pass-manager">,

include/swift/SILOptimizer/OptimizerBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ OptionalBridgedFunction
184184
PassContext_loadFunction(BridgedPassContext context, llvm::StringRef name);
185185

186186
SwiftInt SILOptions_enableStackProtection(BridgedPassContext context);
187+
SwiftInt SILOptions_enableMoveInoutStackProtection(BridgedPassContext context);
187188

188189
SWIFT_END_NULLABILITY_ANNOTATIONS
189190

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1825,6 +1825,9 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
18251825
Opts.EnableStackProtection =
18261826
Args.hasFlag(OPT_enable_stack_protector, OPT_disable_stack_protector,
18271827
Opts.EnableStackProtection);
1828+
Opts.EnableMoveInoutStackProtection =
1829+
Args.hasFlag(OPT_enable_move_inout_stack_protector, OPT_disable_stack_protector,
1830+
Opts.EnableMoveInoutStackProtection);
18281831
Opts.VerifyAll |= Args.hasArg(OPT_sil_verify_all);
18291832
Opts.VerifyNone |= Args.hasArg(OPT_sil_verify_none);
18301833
Opts.DebugSerialization |= Args.hasArg(OPT_sil_debug_serialization);

lib/SILOptimizer/PassManager/PassManager.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,3 +1638,8 @@ SwiftInt SILOptions_enableStackProtection(BridgedPassContext context) {
16381638
SILModule *mod = castToPassInvocation(context)->getPassManager()->getModule();
16391639
return mod->getOptions().EnableStackProtection;
16401640
}
1641+
1642+
SwiftInt SILOptions_enableMoveInoutStackProtection(BridgedPassContext context) {
1643+
SILModule *mod = castToPassInvocation(context)->getPassManager()->getModule();
1644+
return mod->getOptions().EnableMoveInoutStackProtection;
1645+
}

0 commit comments

Comments
 (0)