hi all, I’ve discovered several stack corruption bugs related to async
/await
which can be reproduced in simple test programs compiled with recent nightly toolchains. i have confirmed that two three four of these bugs are present in the 5.5-RELEASE toolchain.
-
stack corruption when using values returned by
async
closuresthe return values of
async
closures are clobbered at a chaotic, but deterministic memory offset.present in 5.5-RELEASE? present in debug builds? present in release builds? yes no yes reproduction:
// async-return-value-corruption.swift
@main
enum Main
{
actor A
{
init()
{
}
func a(_ f:@Sendable () async -> (Int, (Int, Int, Int, Int))?)
async -> Void
{
guard let (head, tail):(Int, (Int, Int, Int, Int)) = await f()
else
{
return
}
print((head, tail))
return
}
}
static
func p() async -> Bool
{
true
}
static
func main() async
{
while true
{
let a:A = .init()
async let task:Void = a.a
{
if await Self.p()
{
return (0, (0, 0, 0 ,0))
}
else
{
return nil
}
}
await task
}
}
}
$ swiftc --version
Swift version 5.5 (swift-5.5-RELEASE)
Target: x86_64-unknown-linux-gnu
$ swiftc -parse-as-library -O async-return-value-corruption.swift
$ ./async-return-value-corruption
(139787763716080, (0, 0, 0, 0))
(139787629498352, (0, 0, 0, 0))
(139787965042672, (0, 0, 0, 0))
(139787763716080, (0, 0, 0, 0))
(139787629498352, (0, 0, 0, 0))
...
-
segmentation faults when using
async let
relatively simple usages of
async let
suffer from segmentation faults, on both debug, and release builds. while i originally believed this was limited to usage ofasync let
in themain
function, i have since also observed this issue in a variety of other contexts.present in 5.5-RELEASE? present in debug builds? present in release builds? yes yes yes reproduction:
// async-let-segfault.swift
@main
enum Main
{
static
func foo() async -> [Void]
{
try? await Task.sleep(nanoseconds: 1)
return []
}
static
func main() async
{
async let task:Void =
{
() async -> () in
try? await Task.sleep(nanoseconds: 1)
}()
while true
{
let _:[Void] = await Self.foo()
}
}
}
$ swiftc --version
Swift version 5.5 (swift-5.5-RELEASE)
Target: x86_64-unknown-linux-gnu
$ swiftc -O -parse-as-library async-let-segfault.swift
async-let-segfault.swift:23:15: warning: will never be executed
await task
^
async-let-segfault.swift:19:15: note: condition always evaluates to true
while true
^
$ ./async-let-segfault
Segmentation fault (core dumped)
-
stack corruption when passing enums to actor-isolated methods
the enum values received by an actor-isolated method are not the same values that were passed by the caller. i have observed this issue in both debug and release builds, but is vastly more common and reproducible in debug builds.
thankfully, it does not affect the 5.5-RELEASE toolchain,but all of the recent nightlies, includingDEVELOPMENT-SNAPSHOT-2021-09-23-a
, are affected.(update) after further testing, i’ve found that the 5.5-RELEASE toolchain is indeed affected by this bug. it occurs in both debug and release builds. see this follow-up for details.
present in 5.5-RELEASE? present in debug builds? present in release builds? noyesyes sometimesyesreproduction:
// async-stack-corruption.swift
struct Users
{
enum Access
{
case guest
case admin(Int)
case developer(Int, Int, Int, Int)
}
actor State
{
init()
{
}
func set(permissions:(user:Int, access:Access?))
{
print(permissions)
}
}
let state:State = .init()
func set(permissions:(user:Int, access:Access?)) async
{
await self.state.set(permissions: permissions)
}
}
@main
enum Main
{
static
func main() async
{
let users:Users = .init()
let stream:AsyncStream<Int> = .init
{
for i in 0 ..< 10
{
$0.yield(i)
}
$0.finish()
}
for await i:Int in stream
{
await users.set(permissions: (i, .guest))
}
}
}
$ swiftc --version
Swift version 5.6-dev (LLVM ae102eaadf2d38c, Swift be2d00b32742678)
Target: x86_64-unknown-linux-gnu
$ swiftc -parse-as-library async-stack-corruption.swift
$ ./async-stack-corruption
(user: 0, access: Optional(main.Users.Access.admin(0)))
(user: 1, access: Optional(main.Users.Access.admin(0)))
(user: 2, access: Optional(main.Users.Access.admin(0)))
(user: 3, access: Optional(main.Users.Access.admin(0)))
(user: 4, access: Optional(main.Users.Access.admin(0)))
(user: 5, access: Optional(main.Users.Access.admin(0)))
(user: 6, access: Optional(main.Users.Access.admin(0)))
(user: 7, access: Optional(main.Users.Access.admin(0)))
(user: 8, access: Optional(main.Users.Access.admin(0)))
(user: 9, access: Optional(main.Users.Access.admin(0)))
i have encountered additional memory corruption bugs, including some weirdness with calling instance methods on actor-isolated properties (thread), but these 3 are the issues i had time to isolate and reproduce this week. i have filed them as:
in my view, bug (1) is by far the most dangerous, as it occurs silently, and affects the 5.5 release toolchain. like bug (3), bug (1) also represents a potential security vulnerability, though it is probably not easily exploitable in naturally occurring code.
(update) now that i’ve found that bug (3) does occur in binaries built with the 5.5 release toolchain, i am now even more concerned about bug (3) than i was about bug (1). because both of them turn into segfaults depending on minor code changes, it is possible that bug (2), which i have so far only been able to reproduce as a segfault, could also manifest as a silent vulnerability.
i would advise that people using concurrency features should not ship anything compiled with the 5.5 toolchain, until these issues are fixed and a patch is released.
(update) it looks like there is yet another issue with task groups and SIMD types with wide alignments. see this followup. i have confirmed this issue affects the 5.5-RELEASE toolchain as well. i was able to reproduce it on both debug and release builds.
-
present in 5.5-RELEASE? present in debug builds? present in release builds? yes yes yes reproduction:
@main
enum Main
{
static
func main() async
{
await withTaskGroup(of: SIMD4<Int32>.self)
{
(group:inout TaskGroup) in
group.addTask
{
return SIMD4<Int32>.init(repeating: 0)
}
}
}
}
$ swiftc --version
Swift version 5.5 (swift-5.5-RELEASE)
Target: x86_64-unknown-linux-gnu
$ swiftc -O -parse-as-library async-stack-corruption-simd.swift
$ ./async-stack-corruption-simd
Segmentation fault (core dumped)