Skip to content

Commit 966df35

Browse files
committed
Fix SILGen to emit fix_lifetime for inout-to-pointer conversion.
This fixes use-after-free miscompilation bugs that can occur when a lifetime-optimized standard library type, like Dictionary or String is converted to an UnsafePointer using implicit inout-to-pointer conversion: func ptrToDictionary(_: UnsafePointer<Dictionary<K, V>>) {} func testDictionary() { var d: Dictionary = ... ptrToDictionary(&d) } func ptrToString(_: UnsafePointer<String>) {} func testString() { var s: String = ... ptrToString(&s) } Address to pointer conversion must always be guarded by either a mark_dependence or a fix_lifetime. Use fix_lifetime for call emission in SILGen to limit the optimization impact to the narrow scope of the pointer conversion. This could negatively impact performance simply because SIL does not provide a way to scope pointer conversion. fix_lifetime is unnecessarilly conservative and prevents the elimination of dead allocations. rdar://117807309 (Fix SILGen to emit fix_lifetime for inout-to-pointer conversion)
1 parent 43e88fd commit 966df35

File tree

4 files changed

+206
-5
lines changed

4 files changed

+206
-5
lines changed

lib/SILGen/LValue.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,25 @@ struct LLVM_LIBRARY_VISIBILITY UnenforcedFormalAccess : FormalAccess {
633633
void finishImpl(SILGenFunction &SGF) override;
634634
};
635635

636+
// A formal access that keeps an LValue alive across an expression that uses an
637+
// unsafe pointer into that LValue. This supports emitLValueToPointer, which
638+
// handles InoutToPointerExpr. This formal access is nested within whatever
639+
// formal access is needed for the LValue itself and emits a fix_lifetime
640+
// instruction after the apply.
641+
struct LLVM_LIBRARY_VISIBILITY LValueToPointerFormalAccess : FormalAccess {
642+
static SILValue enter(SILGenFunction &SGF, SILLocation loc, SILValue address);
643+
644+
SILValue address;
645+
646+
LValueToPointerFormalAccess(SILLocation loc, SILValue address,
647+
CleanupHandle cleanup)
648+
: FormalAccess(sizeof(*this), FormalAccess::Unenforced, loc, cleanup),
649+
address(address) {}
650+
651+
// Only called at the end formal evaluation scope. Emit fix_lifetime.
652+
void finishImpl(SILGenFunction &SGF) override;
653+
};
654+
636655
} // namespace Lowering
637656
} // namespace swift
638657

lib/SILGen/SILGenExpr.cpp

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5922,6 +5922,67 @@ static void diagnoseImplicitRawConversion(Type sourceTy, Type pointerTy,
59225922
}
59235923
}
59245924

5925+
namespace {
5926+
/// Cleanup to insert fix_lifetime on an LValue address.
5927+
class FixLifetimeLValueCleanup : public Cleanup {
5928+
friend LValueToPointerFormalAccess;
5929+
5930+
FormalEvaluationContext::stable_iterator depth;
5931+
5932+
public:
5933+
FixLifetimeLValueCleanup() : depth() {}
5934+
5935+
LValueToPointerFormalAccess &getFormalAccess(SILGenFunction &SGF) const {
5936+
auto &access = *SGF.FormalEvalContext.find(depth);
5937+
return static_cast<LValueToPointerFormalAccess &>(access);
5938+
}
5939+
5940+
void emit(SILGenFunction &SGF, CleanupLocation l,
5941+
ForUnwind_t forUnwind) override {
5942+
getFormalAccess(SGF).finish(SGF);
5943+
}
5944+
5945+
SILValue getAddress(SILGenFunction &SGF) const {
5946+
return getFormalAccess(SGF).address;
5947+
}
5948+
5949+
void dump(SILGenFunction &SGF) const override {
5950+
#ifndef NDEBUG
5951+
llvm::errs() << "FixLifetimeLValueCleanup "
5952+
<< "State:" << getState() << " "
5953+
<< "Address: " << getAddress(SGF) << "\n";
5954+
#endif
5955+
}
5956+
};
5957+
} // end anonymous namespace
5958+
5959+
SILValue LValueToPointerFormalAccess::enter(SILGenFunction &SGF,
5960+
SILLocation loc,
5961+
SILValue address) {
5962+
auto &lowering = SGF.getTypeLowering(address->getType().getObjectType());
5963+
SILValue pointer = SGF.B.createAddressToPointer(
5964+
loc, address, SILType::getRawPointerType(SGF.getASTContext()),
5965+
/*needsStackProtection=*/ true);
5966+
if (!lowering.isTrivial()) {
5967+
assert(SGF.isInFormalEvaluationScope() &&
5968+
"Must be in formal evaluation scope");
5969+
auto &cleanup = SGF.Cleanups.pushCleanup<FixLifetimeLValueCleanup>();
5970+
CleanupHandle handle = SGF.Cleanups.getTopCleanup();
5971+
SGF.FormalEvalContext.push<LValueToPointerFormalAccess>(loc, address,
5972+
handle);
5973+
cleanup.depth = SGF.FormalEvalContext.stable_begin();
5974+
}
5975+
return pointer;
5976+
}
5977+
5978+
// Address-to-pointer conversion always requires either a fix_lifetime or
5979+
// mark_dependence. Emitting a fix_lifetime immediately after the call as
5980+
// opposed to a mark_dependence allows the lvalue's lifetime to be optimized
5981+
// outside of this narrow scope.
5982+
void LValueToPointerFormalAccess::finishImpl(SILGenFunction &SGF) {
5983+
SGF.B.emitFixLifetime(loc, address);
5984+
}
5985+
59255986
/// Convert an l-value to a pointer type: unsafe, unsafe-mutable, or
59265987
/// autoreleasing-unsafe-mutable.
59275988
ManagedValue SILGenFunction::emitLValueToPointer(SILLocation loc, LValue &&lv,
@@ -5967,9 +6028,8 @@ ManagedValue SILGenFunction::emitLValueToPointer(SILLocation loc, LValue &&lv,
59676028
// Get the lvalue address as a raw pointer.
59686029
SILValue address =
59696030
emitAddressOfLValue(loc, std::move(lv)).getUnmanagedValue();
5970-
address = B.createAddressToPointer(loc, address,
5971-
SILType::getRawPointerType(getASTContext()),
5972-
/*needsStackProtection=*/ true);
6031+
6032+
SILValue pointer = LValueToPointerFormalAccess::enter(*this, loc, address);
59736033

59746034
// Disable nested writeback scopes for any calls evaluated during the
59756035
// conversion intrinsic.
@@ -5984,7 +6044,7 @@ ManagedValue SILGenFunction::emitLValueToPointer(SILLocation loc, LValue &&lv,
59846044
getPointerProtocol());
59856045
return emitApplyOfLibraryIntrinsic(
59866046
loc, converter, subMap,
5987-
ManagedValue::forObjectRValueWithoutOwnership(address),
6047+
ManagedValue::forObjectRValueWithoutOwnership(pointer),
59886048
SGFContext())
59896049
.getAsSingleValue(*this, loc);
59906050
}

test/SILGen/pointer_conversion.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
// RUN: %target-swift-emit-silgen -module-name pointer_conversion -sdk %S/Inputs -I %S/Inputs -enable-source-import %s -enable-objc-interop | %FileCheck %s
32

43
// FIXME: rdar://problem/19648117 Needs splitting objc parts out
@@ -22,6 +21,9 @@ func takesOptConstRawPointer(_ x: UnsafeRawPointer?, and: Int) {}
2221
func takesOptOptConstRawPointer(_ x: UnsafeRawPointer??, and: Int) {}
2322
func takesMutableFunctionPointer(_ x: UnsafeMutablePointer<() -> Void>) {}
2423

24+
@_silgen_name("takeObjectPointer")
25+
func takeObjectPointer(_: UnsafePointer<AnyObject>)
26+
2527
// CHECK-LABEL: sil hidden [ossa] @$s18pointer_conversion0A9ToPointeryySpySiG_SPySiGSvtF
2628
// CHECK: bb0([[MP:%.*]] : $UnsafeMutablePointer<Int>, [[CP:%.*]] : $UnsafePointer<Int>, [[MRP:%.*]] : $UnsafeMutableRawPointer):
2729
func pointerToPointer(_ mp: UnsafeMutablePointer<Int>,
@@ -449,3 +451,31 @@ func optOptStringToOptOptPointer(string: String??) {
449451
// CHECK: br [[SOME_CONT_BB]]([[NO_VALUE]] : $Optional<Optional<UnsafeRawPointer>>, [[NO_OWNER]] : $Optional<AnyObject>)
450452
takesOptOptConstRawPointer(string, and: sideEffect1())
451453
}
454+
455+
final public class Obj {
456+
var object: AnyObject
457+
458+
init(object: AnyObject) { self.object = object }
459+
}
460+
461+
public struct RefObj {
462+
var o: Obj
463+
}
464+
465+
// CHECK-LABEL: sil [ossa] @$s18pointer_conversion20objectFieldToPointer2rcyAA6RefObjV_tF : $@convention(thin) (@guaranteed RefObj) -> () {
466+
// CHECK: bb0(%0 : @guaranteed $RefObj):
467+
// CHECK: [[B:%.*]] = begin_borrow %{{.*}} : $Obj
468+
// CHECK: [[R:%.*]] = ref_element_addr [[B]] : $Obj, #Obj.object
469+
// CHECK: [[A:%.*]] = begin_access [read] [dynamic] [[R]] : $*AnyObject
470+
// CHECK: [[P:%.*]] = address_to_pointer [stack_protection] [[A]] : $*AnyObject to $Builtin.RawPointer
471+
// CHECK: apply %{{.*}}<UnsafePointer<AnyObject>>(%{{.*}}, %7) : $@convention(thin) <τ_0_0 where τ_0_0 : _Pointer> (Builtin.RawPointer) -> @out τ_0_0
472+
// CHECK: apply {{.*}} : $@convention(thin) (UnsafePointer<AnyObject>) -> ()
473+
// CHECK: fix_lifetime [[A]] : $*AnyObject
474+
// CHECK: end_access [[A]] : $*AnyObject
475+
// CHECK: end_borrow [[B]] : $Obj
476+
// CHECK: destroy_value %{{.*}} : $Obj
477+
// CHECK: dealloc_stack %{{.*}} : $*UnsafePointer<AnyObject>
478+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion20objectFieldToPointer2rcyAA6RefObjV_tF'
479+
public func objectFieldToPointer(rc: RefObj) {
480+
takeObjectPointer(&rc.o.object)
481+
}

test/SILOptimizer/pointer_conversion.swift

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,95 @@ public func testWithMemoryRebound<T, Result>(
118118
}
119119
return try body(.init(rawPtr))
120120
}
121+
122+
@_silgen_name("takeRawPointer")
123+
func takeRawPointer(_: UnsafeRawPointer)
124+
125+
@_silgen_name("takeObjectPointer")
126+
func takeObjectPointer(_: UnsafePointer<AnyObject>)
127+
128+
@_silgen_name("takeStringPointer")
129+
func takeStringPointer(_: UnsafePointer<String>)
130+
131+
@_silgen_name("takeDictionaryPointer")
132+
func takeDictionaryPointer(_: UnsafePointer<Dictionary<Int, Int>>)
133+
134+
// Test conversion of a dictionary to a raw pointer. This is not a sane
135+
// conversion, but the compiler must still generate memory-safe code.
136+
//
137+
// A dictionary is an eager-move type, so it will be destroyed at its
138+
// last use. An address_to_pointer operation escapes the pointer, so
139+
// the compiler never sees those pointer uses. The pointer's scope
140+
// must be protected by a fix_lifetime.
141+
//
142+
// NOTE: If this test triggers a compiler error because of the unsafe
143+
// inout conversion, then we have arrived at a better world. Delete
144+
// the test. The eagerMoveToPointer test below is sufficient.
145+
//
146+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion22dictionaryToRawPointeryyF : $@convention(thin) () -> () {
147+
// CHECK: [[A:%.*]] = alloc_stack $Dictionary<Int, Int>, var, name "d"
148+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*Dictionary<Int, Int> to $Builtin.RawPointer
149+
// CHECK: [[UP:%.*]] = struct $UnsafeRawPointer ([[PTR]] : $Builtin.RawPointer)
150+
// CHECK: apply %{{.*}}([[UP]]) : $@convention(thin) (UnsafeRawPointer) -> ()
151+
// CHECK: [[D:%.*]] = load %0 : $*Dictionary<Int, Int>
152+
// CHECK: fix_lifetime [[D]] : $Dictionary<Int, Int>
153+
// CHECK: release_value [[D]] : $Dictionary<Int, Int>
154+
// CHECK: dealloc_stack [[A]] : $*Dictionary<Int, Int>
155+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion22dictionaryToRawPointeryyF'
156+
public func dictionaryToRawPointer() {
157+
var d = [1:1]
158+
takeRawPointer(&d)
159+
}
160+
161+
// Test conversion of a non-trivial eager-move type to a raw pointer.
162+
// This currently only applies to Dictionary, but converting a
163+
// dictionary to a pointer will likely be a compiler error in the
164+
// future. So force an eagerMove type here.
165+
//
166+
// An eager-move type will be destroyed at its last use. An
167+
// address_to_pointer operation escapes the pointer, so the compiler
168+
// never sees those pointer uses. The pointer's scope must be
169+
// protected by a fix_lifetime.
170+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion18eagerMoveToPointer1oyyXln_tF : $@convention(thin) (@owned AnyObject) -> () {
171+
// CHECK: [[A:%.*]] = alloc_stack [moveable_value_debuginfo] $AnyObject, var, name "o"
172+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*AnyObject to $Builtin.RawPointer
173+
// CHECK: [[UP:%.*]] = struct $UnsafePointer<AnyObject> ([[PTR]] : $Builtin.RawPointer)
174+
// CHECK: apply %{{.*}}([[UP]]) : $@convention(thin) (UnsafePointer<AnyObject>) -> ()
175+
// CHECK: [[O:%.*]] = load [[A]] : $*AnyObject
176+
// CHECK: fix_lifetime [[O]] : $AnyObject
177+
// CHECK: strong_release [[O]] : $AnyObject
178+
// CHECK: dealloc_stack [[A]] : $*AnyObject
179+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion18eagerMoveToPointer1oyyXln_tF'
180+
public func eagerMoveToPointer(@_eagerMove o: consuming AnyObject ) {
181+
takeObjectPointer(&o)
182+
}
183+
184+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion15stringToPointer2ssySS_tF : $@convention(thin) (@guaranteed String) -> () {
185+
// CHECK: [[A:%.*]] = alloc_stack $String, var, name "s"
186+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*String to $Builtin.RawPointer
187+
// CHECK: [[UP:%.*]] = struct $UnsafePointer<String> ([[PTR]] : $Builtin.RawPointer)
188+
// CHECK: apply {{.*}}([[UP]]) : $@convention(thin) (UnsafePointer<String>) -> ()
189+
// CHECK: [[S:%.*]] = load [[A]] : $*String
190+
// CHECK: fix_lifetime [[S]] : $String
191+
// CHECK: release_value [[S]] : $String
192+
// CHECK: dealloc_stack [[A]] : $*String
193+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion15stringToPointer2ssySS_tF'
194+
public func stringToPointer(ss: String) {
195+
var s = ss
196+
takeStringPointer(&s)
197+
}
198+
199+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion19dictionaryToPointer2ddySDyS2iG_tF : $@convention(thin) (@guaranteed Dictionary<Int, Int>) -> () {
200+
// CHECK: [[A:%.*]] = alloc_stack $Dictionary<Int, Int>, var, name "d"
201+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*Dictionary<Int, Int> to $Builtin.RawPointer
202+
// CHECK: [[UP:%.*]] = struct $UnsafePointer<Dictionary<Int, Int>> ([[PTR]] : $Builtin.RawPointer)
203+
// CHECK: apply %{{.*}}([[UP]]) : $@convention(thin) (UnsafePointer<Dictionary<Int, Int>>) -> ()
204+
// CHECK: [[D:%.*]] = load [[A]] : $*Dictionary<Int, Int>
205+
// CHECK: fix_lifetime [[D]] : $Dictionary<Int, Int>
206+
// CHECK: release_value [[D]] : $Dictionary<Int, Int>
207+
// CHECK: dealloc_stack [[A]] : $*Dictionary<Int, Int>
208+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion19dictionaryToPointer2ddySDyS2iG_tF'
209+
public func dictionaryToPointer(dd: Dictionary<Int, Int>) {
210+
var d = dd
211+
takeDictionaryPointer(&d)
212+
}

0 commit comments

Comments
 (0)