-
Notifications
You must be signed in to change notification settings - Fork 51
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
setjmp/longjmp are Undefined Behavior in Swift #1
Comments
Thanks a lot for your comment. I did some testing, and really in the current 1.0.2 version there is a big problem with leaks caused by setjmp/longjmp which I’ve missed. So thanks for spotting it. Setjmp/longjmp is actually a tricky API and can cause leak problems if used incautiously, that’s why Apple has disabled it for Swift. But at the same time, it is a convenient OOTB solution for saving and loading CPU registers. However, this API requires very careful usage and following certain rules to avoid the above issues with ARC. I’ve already found a way to handle setjmp/longjmp leaks. I’ll need to slightly change the existing implementation and in a few days I will release a new update with fixes. So you’ll be able to check it yourself soon. I really appreciate your feedback. |
In version 1.0.4 a problem with setjmp/longjmp memory leaks was fixed. Please find a test project in the attachment. Here you can check the usage with the leak instrument and look over the memory usage indicators. You can also compare the results with version 1.0.2 and see the difference. Please LMK your feedback in case you'll test this. Thanks again for raising this issue. |
As to undefined behavior. It needs to be clarified that setjmp/longjmp has undefined behavior when used with ARC and not with Swift in general. This can also be assumed from the statement by Joe Groff. But the same applies to any other api and assembly code that change the normal function execution and return, including adjusting values of the cpu special registers. This happens because ARC can’t track such things and it can lead to leaks and memory corruption. But on the other hand, it is impossible to create proper coroutines without such modifications. The suspend/resume feature in coroutine itself implies a change of normal function execution that ARC is unable to track. Setjmp/longjmp was used in the library to minimize assembly code in the project and also because it is high-level, tested, predictable and is included in the standard C library. Several tricks were used to avoid ARC disruption when this api is applied. It’s guaranteed that when a coroutine is suspended it will always resume in the same place and only once. This ensures safe routines execution and normal ARC functioning. The only exception is the return from coroutine’s stack with longjmp after its completion. In this case, all objects that could have been captured in the stack by ARC, including weak and unowned references, will be leaked. Therefore, the implementation guarantees that no objects will be captured at the moment of returning from the coroutine. Any api that violates these statements is private and inaccessible to a user. |
I discussed this issue with Joe Groff and here is his reply:
|
Thanks for looking into this! I've decided to go ahead and add support to NIO's |
@belozierov @calebkleveter Based on this input is it this framework production ready or it will stay rather as a curiosity? |
@nonameplum The version 1.0.7 is stable and has no bugs that I am aware of. I marked it as a pre-release because in the nearest future I plan to make some changes in api. Currently I’m working on a new version that will include an improved version of In case of any issues or questions, feel free to contact me. |
@nonameplum To sum up on setjmp/longjmp Setjmp got some bad fame in early versions of swift because of memory leaks, so it was blocked and is now unavailable in swift. Therefore, the coroutine core was written in C where its usage is native and creates no problems. Joe Groff's answer fully confirms this. |
@belozierov Thanks a lot for the explanation. |
…detect mods to free blocks SwiftCoroutineDemo(39856,0x11266e600) malloc: nano zone abandoned due to inability to preallocate reserved vm space. DispatchQueue::scheduleTask() <OS_dispatch_queue_main: com.apple.main-thread> SharedCoroutineDispatcher::getFreeQueue(SwiftCoroutine.SharedCoroutineDispatcher) CoroutineContext::init(stackSize=196608, guardPage=true, SwiftCoroutine.CoroutineContext) returnEnv = 0x000060f0000042d0 stack = 0x000000010effb000 SharedCoroutineQueue::start(SwiftCoroutine.SharedCoroutineQueue) CoroutineContext::start(SwiftCoroutine.CoroutineContext) enter CoroutineContext::stackTop(SwiftCoroutine.CoroutineContext) 0x000000010f02b000 CoroutineContext::start(Optional(0x00006060000b8400)) block enter ==39856==WARNING: ASan is ignoring requested __asan_handle_no_return: stack type: default top: 0x7ff7b7045000; bottom 0x00010f028000; size: 0x7ff6a801d000 (140697357373440) False positive error reports may follow For details see google/sanitizers#189 CoroutineContext::start(SwiftCoroutine.CoroutineContext) leave DispatchQueue::scheduleTask() <OS_dispatch_queue_main: com.apple.main-thread> SharedCoroutineDispatcher::getFreeQueue(SwiftCoroutine.SharedCoroutineDispatcher) SharedCoroutineQueue::start(SwiftCoroutine.SharedCoroutineQueue) CoroutineContext::stackTop(SwiftCoroutine.CoroutineContext) 0x000000010f02b000 ================================================================= ==39856==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x00010f029ec1 at pc 0x00010949d77d bp 0x7ff7b7040e90 sp 0x7ff7b7040650 READ of size 4416 at 0x00010f029ec1 thread T0 #0 0x10949d77c in wrap_memcpy+0x16c (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x1977c) belozierov#1 0x109ef7f83 in SharedCoroutine.saveStack() SharedCoroutine.swift:79 belozierov#2 0x109eff514 in SharedCoroutineQueue.start(dispatcher:scheduler:task:) SharedCoroutineQueue.swift:39 belozierov#3 0x109efc229 in closure belozierov#1 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:27 belozierov#4 0x109ee7ed9 in OS_dispatch_queue.scheduleTask(_:) CoroutineScheduler+DispatchQueue.swift:16 belozierov#5 0x109ee88ad in protocol witness for CoroutineScheduler.scheduleTask(_:) in conformance OS_dispatch_queue <compiler-generated> belozierov#6 0x109efbf48 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:26 belozierov#7 0x109ee3616 in CoroutineScheduler._startCoroutine(_:) CoroutineScheduler.swift:51 belozierov#8 0x109ee3c78 in CoroutineScheduler.startCoroutine(in:task:) CoroutineScheduler.swift:69 belozierov#9 0x108ebe2ed in AppDelegate.test() AppDelegate.swift:43 belozierov#10 0x108ebdd77 in AppDelegate.application(_:didFinishLaunchingWithOptions:) AppDelegate.swift:18 belozierov#11 0x108ebe4b7 in @objc AppDelegate.application(_:didFinishLaunchingWithOptions:) <compiler-generated> belozierov#12 0x7fff2509a592 in -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]+0xd5 (UIKitCore:x86_64+0xcd7592) belozierov#13 0x7fff2509c283 in -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:]+0x101f (UIKitCore:x86_64+0xcd9283) belozierov#14 0x7fff250a1c23 in -[UIApplication _runWithMainScene:transitionContext:completion:]+0x4a5 (UIKitCore:x86_64+0xcdec23) belozierov#15 0x7fff246124a2 in -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:]+0xb2 (UIKitCore:x86_64+0x24f4a2) belozierov#16 0x7fff2509e2ac in -[UIApplication _compellApplicationLaunchToCompleteUnconditionally]+0x3a (UIKitCore:x86_64+0xcdb2ac) belozierov#17 0x7fff2509e64b in -[UIApplication _run]+0x391 (UIKitCore:x86_64+0xcdb64b) belozierov#18 0x7fff250a32b4 in UIApplicationMain+0x64 (UIKitCore:x86_64+0xce02b4) belozierov#19 0x7fff59d55cc1 in UIApplicationMain(_:_:_:_:)+0x61 (libswiftUIKit.dylib:x86_64+0x21cc1) belozierov#20 0x108ebf357 in static UIApplicationDelegate.main() <compiler-generated> belozierov#21 0x108ebf2a0 in static AppDelegate.$main() AppDelegate.swift:11 belozierov#22 0x108ebfc77 in main <compiler-generated> belozierov#23 0x1090f8f20 in start_sim+0x9 (dyld_sim:x86_64+0x1f20) belozierov#24 0x1125f352d (<unknown module>) 0x00010f029ec1 is located 192193 bytes inside of 200704-byte region [0x00010effb000,0x00010f02c000) allocated by thread T0 here: #0 0x1094c7bb3 in wrap_posix_memalign+0xb3 (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x43bb3) belozierov#1 0x7fff30c9e62c in swift_slowAlloc+0x4c (libswiftCore.dylib:x86_64+0x30762c) belozierov#2 0x109ede243 in CoroutineContext.init(stackSize:guardPage:) CoroutineContext.swift:32 belozierov#3 0x109eddc1f in CoroutineContext.__allocating_init(stackSize:guardPage:) CoroutineContext.swift belozierov#4 0x109efe985 in SharedCoroutineQueue.init(stackSize:) SharedCoroutineQueue.swift:28 belozierov#5 0x109efe5c3 in SharedCoroutineQueue.__allocating_init(stackSize:) SharedCoroutineQueue.swift belozierov#6 0x109efcb3d in SharedCoroutineDispatcher.getFreeQueue() SharedCoroutineDispatcher.swift:38 belozierov#7 0x109efc20d in closure belozierov#1 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:27 belozierov#8 0x109ee7ed9 in OS_dispatch_queue.scheduleTask(_:) CoroutineScheduler+DispatchQueue.swift:16 belozierov#9 0x109ee88ad in protocol witness for CoroutineScheduler.scheduleTask(_:) in conformance OS_dispatch_queue <compiler-generated> belozierov#10 0x109efbf48 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:26 belozierov#11 0x109ee3616 in CoroutineScheduler._startCoroutine(_:) CoroutineScheduler.swift:51 belozierov#12 0x109ee3c78 in CoroutineScheduler.startCoroutine(in:task:) CoroutineScheduler.swift:69 belozierov#13 0x108ebe2ed in AppDelegate.test() AppDelegate.swift:43 belozierov#14 0x108ebdd77 in AppDelegate.application(_:didFinishLaunchingWithOptions:) AppDelegate.swift:18 belozierov#15 0x108ebe4b7 in @objc AppDelegate.application(_:didFinishLaunchingWithOptions:) <compiler-generated> belozierov#16 0x7fff2509a592 in -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]+0xd5 (UIKitCore:x86_64+0xcd7592) belozierov#17 0x7fff2509c283 in -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:]+0x101f (UIKitCore:x86_64+0xcd9283) belozierov#18 0x7fff250a1c23 in -[UIApplication _runWithMainScene:transitionContext:completion:]+0x4a5 (UIKitCore:x86_64+0xcdec23) belozierov#19 0x7fff246124a2 in -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:]+0xb2 (UIKitCore:x86_64+0x24f4a2) belozierov#20 0x7fff2509e2ac in -[UIApplication _compellApplicationLaunchToCompleteUnconditionally]+0x3a (UIKitCore:x86_64+0xcdb2ac) belozierov#21 0x7fff2509e64b in -[UIApplication _run]+0x391 (UIKitCore:x86_64+0xcdb64b) belozierov#22 0x7fff250a32b4 in UIApplicationMain+0x64 (UIKitCore:x86_64+0xce02b4) belozierov#23 0x7fff59d55cc1 in UIApplicationMain(_:_:_:_:)+0x61 (libswiftUIKit.dylib:x86_64+0x21cc1) belozierov#24 0x108ebf357 in static UIApplicationDelegate.main() <compiler-generated> belozierov#25 0x108ebf2a0 in static AppDelegate.$main() AppDelegate.swift:11 belozierov#26 0x108ebfc77 in main <compiler-generated> belozierov#27 0x1090f8f20 in start_sim+0x9 (dyld_sim:x86_64+0x1f20) belozierov#28 0x1125f352d (<unknown module>) SUMMARY: AddressSanitizer: stack-buffer-overflow (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x1977c) in wrap_memcpy+0x16c Shadow bytes around the buggy address: 0x0001344ad380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad3a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad3b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad3c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0001344ad3d0: 00 00 00 00 f1 f1 f1 f1[01]f3 f3 f3 00 00 00 00 0x0001344ad3e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad3f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0001344ad420: 00 00 00 00 00 00 00 00 00 00 00 00 ca ca ca ca Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==39856==ABORTING AddressSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report. (lldb)
I was ecstatic when I saw this repo. Unfortunately, I was informed that the basis of the coroutines defined in this project are unsafe:
Further references:
https://twitter.com/search?q=from%3Ajckarter%20setjmp&src=typed_query
Unless this information is incorrect, there should probably at least be a warning in the README on this.
The text was updated successfully, but these errors were encountered: