Skip to content

Commit ac8a48b

Browse files
author
Joe Shajrawi
committed
Fixes a bug in SILCombiner that caused a use-after-free
1 parent c0dbec0 commit ac8a48b

File tree

5 files changed

+201
-7
lines changed

5 files changed

+201
-7
lines changed

lib/SIL/SILVerifier.cpp

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,9 +2396,39 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
23962396
break;
23972397
case SILInstructionKind::DestroyAddrInst:
23982398
return true;
2399-
case SILInstructionKind::UncheckedAddrCastInst:
2400-
// Escaping use lets be conservative here.
2399+
case SILInstructionKind::UncheckedAddrCastInst: {
2400+
// Don't be too conservative here, we have a new case:
2401+
// sil-combine producing a new code pattern for devirtualizer
2402+
// open_existential_addr immutable_access -> witness_method
2403+
// witness_method gets transformed into unchecked_addr_cast
2404+
// we are "OK" If one of the new users is an non-consuming apply
2405+
// we are also "OK" if we have a single non-consuming user
2406+
auto isCastToNonConsuming = [=](UncheckedAddrCastInst *I) -> bool {
2407+
for (auto *use : I->getUses()) {
2408+
auto *inst = use->getUser();
2409+
switch (inst->getKind()) {
2410+
case SILInstructionKind::ApplyInst:
2411+
case SILInstructionKind::TryApplyInst:
2412+
case SILInstructionKind::PartialApplyInst:
2413+
if (!isConsumingOrMutatingApplyUse(use))
2414+
return true;
2415+
break;
2416+
case SILInstructionKind::LoadInst:
2417+
case SILInstructionKind::DebugValueAddrInst:
2418+
if (I->hasOneUse())
2419+
return true;
2420+
break;
2421+
default:
2422+
break;
2423+
}
2424+
}
2425+
return false;
2426+
};
2427+
if (isCastToNonConsuming(dyn_cast<UncheckedAddrCastInst>(inst))) {
2428+
break;
2429+
}
24012430
return true;
2431+
}
24022432
case SILInstructionKind::CheckedCastAddrBranchInst:
24032433
if (cast<CheckedCastAddrBranchInst>(inst)->getConsumptionKind() !=
24042434
CastConsumptionKind::CopyOnSuccess)

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,12 @@ class SILCombine : public SILFunctionTransform {
341341
/// The entry point to the transformation.
342342
void run() override {
343343
auto *AA = PM->getAnalysis<AliasAnalysis>();
344+
auto *DA = PM->getAnalysis<DominanceAnalysis>();
344345

345346
// Create a SILBuilder with a tracking list for newly added
346347
// instructions, which we will periodically move to our worklist.
347348
SILBuilder B(*getFunction(), &TrackingList);
348-
SILCombiner Combiner(B, AA, getOptions().RemoveRuntimeAsserts);
349+
SILCombiner Combiner(B, AA, DA, getOptions().RemoveRuntimeAsserts);
349350
bool Changed = Combiner.runOnFunction(*getFunction());
350351
assert(TrackingList.empty() &&
351352
"TrackingList should be fully processed by SILCombiner");

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ class SILCombiner :
114114

115115
AliasAnalysis *AA;
116116

117+
DominanceAnalysis *DA;
118+
117119
/// Worklist containing all of the instructions primed for simplification.
118120
SILCombineWorklist Worklist;
119121

@@ -133,11 +135,12 @@ class SILCombiner :
133135
CastOptimizer CastOpt;
134136

135137
public:
136-
SILCombiner(SILBuilder &B, AliasAnalysis *AA, bool removeCondFails)
137-
: AA(AA), Worklist(), MadeChange(false), RemoveCondFails(removeCondFails),
138-
Iteration(0), Builder(B),
138+
SILCombiner(SILBuilder &B, AliasAnalysis *AA, DominanceAnalysis *DA,
139+
bool removeCondFails)
140+
: AA(AA), DA(DA), Worklist(), MadeChange(false),
141+
RemoveCondFails(removeCondFails), Iteration(0), Builder(B),
139142
CastOpt(/* ReplaceInstUsesAction */
140-
[&](SingleValueInstruction *I, ValueBase * V) {
143+
[&](SingleValueInstruction *I, ValueBase *V) {
141144
replaceInstUsesWith(*I, V);
142145
},
143146
/* EraseAction */

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,23 @@ getConformanceAndConcreteType(ASTContext &Ctx,
844844
ConcreteTypeDef, NewSelf);
845845
}
846846

847+
static bool isUseAfterFree(SILValue val, SILInstruction *Apply,
848+
DominanceInfo *DT) {
849+
for (auto Use : val->getUses()) {
850+
auto *User = Use->getUser();
851+
if (!isa<DeallocStackInst>(User) && !isa<DestroyAddrInst>(User) &&
852+
!isa<DeinitExistentialAddrInst>(User)) {
853+
continue;
854+
}
855+
if (!DT->properlyDominates(Apply, User)) {
856+
// we have use-after-free - Conservative heuristic
857+
// Non conservative solution would require data flow analysis
858+
return true;
859+
}
860+
}
861+
return false;
862+
}
863+
847864
/// Propagate information about a concrete type from init_existential_addr
848865
/// or init_existential_ref into witness_method conformances and into
849866
/// apply instructions.
@@ -920,6 +937,16 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
920937
default:
921938
break;
922939
}
940+
// check if using the value in the apply would cause use-after-free
941+
auto *DT = DA->get(AI.getFunction());
942+
auto *apply = AI.getInstruction();
943+
auto op = InitExistential->getOperand(0);
944+
if (isUseAfterFree(op, apply, DT)) {
945+
return nullptr;
946+
}
947+
if (isUseAfterFree(NewSelf, apply, DT)) {
948+
return nullptr;
949+
}
923950
}
924951
// Create a new apply instruction that uses the concrete type instead
925952
// of the existential type.

test/SILOptimizer/sr-5068.sil

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// RUN: %target-sil-opt -assume-parsing-unqualified-ownership-sil %s -sil-combine | %FileCheck %s
2+
3+
sil_stage canonical
4+
5+
import Builtin
6+
import Swift
7+
import SwiftShims
8+
9+
class UIViewController {
10+
}
11+
12+
class ExternalFramework {
13+
func objects<T>(_ type: T.Type) -> Results<T> where T : Object
14+
}
15+
16+
class Object {
17+
}
18+
19+
final class Results<T: Object> {
20+
}
21+
22+
public protocol ReadonlyExternalFramework {
23+
func objects<T>(_ type: T.Type) -> Results<T> where T : Object
24+
}
25+
26+
extension ExternalFramework : ReadonlyExternalFramework {
27+
}
28+
29+
public enum ExternalFrameworkProvider {
30+
static func readonly() -> ReadonlyExternalFramework
31+
}
32+
33+
public enum ModelDataService {
34+
public static func getCurrent(from ExternalFramework: ReadonlyExternalFramework) -> Model?
35+
public static func count(in ExternalFramework: ReadonlyExternalFramework) -> Int
36+
}
37+
38+
var events: [String]
39+
40+
var currentModel: Model { get }
41+
42+
public class Model : Object {
43+
@sil_stored var id: Int { get set }
44+
override init()
45+
init(value: Any)
46+
deinit
47+
}
48+
49+
sil @ExternalFrameworkInit : $@convention(method) (@thick ExternalFramework.Type) -> (@owned ExternalFramework, @error Error)
50+
51+
sil @ResultsCountGetter : $@convention(method) <τ_0_0 where τ_0_0 : Object> (@guaranteed Results<τ_0_0>) -> Int
52+
53+
sil @GetRawPointer : $@convention(thin) () -> Builtin.RawPointer
54+
55+
sil @GetOptionalModel : $@convention(method) (@in ReadonlyExternalFramework, @thin ModelDataService.Type) -> @owned Optional<Model>
56+
57+
// CHECK-LABEL: sil hidden @BuggyFunction : $@convention(thin) () -> @owned Model {
58+
// CHECK: [[ALLOC:%.*]] = alloc_stack $ReadonlyExternalFramework, let, name "ExternalFramework"
59+
// CHECK-NEXT: [[INITE:%.*]] = init_existential_addr [[ALLOC]] : $*ReadonlyExternalFramework, $ExternalFramework
60+
// CHECK: [[TALLOC1:%.*]] = alloc_stack $ReadonlyExternalFramework
61+
// CHECK-NEXT: copy_addr [[ALLOC]] to [initialization] [[TALLOC1]] : $*ReadonlyExternalFramework
62+
// CHECK-NEXT: apply {{%.*}}([[TALLOC1]], {{%.*}}) : $@convention(method) (@in ReadonlyExternalFramework, @thin ModelDataService.Type) -> @owned Optional<Model>
63+
// CHECK: [[TALLOC2:%.*]] = alloc_stack $ReadonlyExternalFramework
64+
// CHECK-NEXT: copy_addr [[ALLOC]] to [initialization] [[TALLOC2]] : $*ReadonlyExternalFramework
65+
// CHECK-NEXT: destroy_addr [[ALLOC]]
66+
// CHECK: [[OEADDR:%.*]] = open_existential_addr immutable_access [[TALLOC2]] : $*ReadonlyExternalFramework
67+
sil hidden @BuggyFunction : $@convention(thin) () -> @owned Model {
68+
bb0:
69+
%0 = alloc_stack $ReadonlyExternalFramework, let, name "ExternalFramework" // users: %1, %166, %72, %163, %71, %168, %10
70+
%1 = init_existential_addr %0 : $*ReadonlyExternalFramework, $ExternalFramework // user: %6
71+
// function_ref ExternalFramework.__allocating_init()
72+
%2 = function_ref @ExternalFrameworkInit : $@convention(method) (@thick ExternalFramework.Type) -> (@owned ExternalFramework, @error Error) // user: %4
73+
%3 = metatype $@thick ExternalFramework.Type // user: %4
74+
try_apply %2(%3) : $@convention(method) (@thick ExternalFramework.Type) -> (@owned ExternalFramework, @error Error), normal bb1, error bb2 // id: %4
75+
76+
// %5 // user: %6
77+
bb1(%5 : $ExternalFramework): // Preds: bb0
78+
store %5 to %1 : $*ExternalFramework // id: %6
79+
// function_ref static ModelDataService.getCurrent(from:)
80+
%7 = function_ref @GetOptionalModel : $@convention(method) (@in ReadonlyExternalFramework, @thin ModelDataService.Type) -> @owned Optional<Model> // user: %11
81+
%8 = metatype $@thin ModelDataService.Type // user: %11
82+
%9 = alloc_stack $ReadonlyExternalFramework // users: %12, %11, %10
83+
copy_addr %0 to [initialization] %9 : $*ReadonlyExternalFramework // id: %10
84+
%11 = apply %7(%9, %8) : $@convention(method) (@in ReadonlyExternalFramework, @thin ModelDataService.Type) -> @owned Optional<Model> // user: %13
85+
dealloc_stack %9 : $*ReadonlyExternalFramework // id: %12
86+
switch_enum %11 : $Optional<Model>, case #Optional.some!enumelt.1: bb4, case #Optional.none!enumelt: bb3 // id: %13
87+
88+
// %14 // user: %15
89+
bb2(%14 : $Error): // Preds: bb0
90+
%15 = builtin "unexpectedError"(%14 : $Error) : $()
91+
unreachable // id: %16
92+
93+
bb3: // Preds: bb1
94+
// function_ref events.unsafeMutableAddressor
95+
%17 = function_ref @GetRawPointer : $@convention(thin) () -> Builtin.RawPointer // user: %18
96+
%18 = apply %17() : $@convention(thin) () -> Builtin.RawPointer // user: %19
97+
%19 = pointer_to_address %18 : $Builtin.RawPointer to [strict] $*Array<String> // users: %158, %156, %152, %151
98+
%70 = alloc_stack $ReadonlyExternalFramework // users: %106, %99, %73, %107, %71
99+
copy_addr %0 to [initialization] %70 : $*ReadonlyExternalFramework // id: %71
100+
destroy_addr %0 : $*ReadonlyExternalFramework // id: %72
101+
debug_value_addr %70 : $*ReadonlyExternalFramework, let, name "ExternalFramework", argno 1 // id: %73
102+
%75 = integer_literal $Builtin.Word, 1 // user: %78
103+
%76 = integer_literal $Builtin.Int64, 1 // user: %77
104+
%77 = struct $Int (%76 : $Builtin.Int64) // user: %81
105+
%78 = alloc_ref [tail_elems $Any * %75 : $Builtin.Word] $_ContiguousArrayStorage<Any> // user: %81
106+
%79 = metatype $@thin Array<Any>.Type // user: %81
107+
%99 = open_existential_addr immutable_access %70 : $*ReadonlyExternalFramework to $*@opened("ED59E038-AA25-11E7-9DEA-685B3593C496") ReadonlyExternalFramework // users: %102, %102, %100
108+
%100 = witness_method $@opened("ED59E038-AA25-11E7-9DEA-685B3593C496") ReadonlyExternalFramework, #ReadonlyExternalFramework.objects!1 : <Self where Self : ReadonlyExternalFramework><T where T : Object> (Self) -> (T.Type) -> Results<T>, %99 : $*@opened("ED59E038-AA25-11E7-9DEA-685B3593C496") ReadonlyExternalFramework : $@convention(witness_method) <τ_0_0 where τ_0_0 : ReadonlyExternalFramework><τ_1_0 where τ_1_0 : Object> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @owned Results<τ_1_0> // type-defs: %99; user: %102
109+
%101 = metatype $@thick Model.Type // user: %102
110+
%102 = apply %100<@opened("ED59E038-AA25-11E7-9DEA-685B3593C496") ReadonlyExternalFramework, Model>(%101, %99) : $@convention(witness_method) <τ_0_0 where τ_0_0 : ReadonlyExternalFramework><τ_1_0 where τ_1_0 : Object> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @owned Results<τ_1_0> // type-defs: %99; users: %105, %104
111+
// function_ref Results.count.getter
112+
%103 = function_ref @ResultsCountGetter : $@convention(method) <τ_0_0 where τ_0_0 : Object> (@guaranteed Results<τ_0_0>) -> Int // user: %104
113+
%104 = apply %103<Model>(%102) : $@convention(method) <τ_0_0 where τ_0_0 : Object> (@guaranteed Results<τ_0_0>) -> Int // user: %108
114+
strong_release %102 : $Results<Model> // id: %105
115+
destroy_addr %70 : $*ReadonlyExternalFramework // id: %106
116+
dealloc_stack %70 : $*ReadonlyExternalFramework // id: %107
117+
%159 = tuple ()
118+
%160 = alloc_ref [objc] $Model // users: %162, %161
119+
dealloc_stack %0 : $*ReadonlyExternalFramework // id: %163
120+
br bb5(%160 : $Model) // id: %164
121+
122+
// %165 // users: %169, %167
123+
bb4(%165 : $Model): // Preds: bb1
124+
destroy_addr %0 : $*ReadonlyExternalFramework // id: %166
125+
debug_value %165 : $Model, let, name "user" // id: %167
126+
dealloc_stack %0 : $*ReadonlyExternalFramework // id: %168
127+
br bb5(%165 : $Model) // id: %169
128+
129+
// %170 // user: %171
130+
bb5(%170 : $Model): // Preds: bb3 bb4
131+
return %170 : $Model // id: %171
132+
} // end sil function 'BuggyFunction'
133+

0 commit comments

Comments
 (0)