阅读 482

Swift 5.5 爆严重堆栈损坏 BUG

原创 : 小集

最近,开发者 taylorswift 在 Swift 论坛发表了题为 《Swift 5.5 has serious stack corruption bugs!》的帖子,作者发现了几个与 async/await 相关的堆栈损坏 BUG,这些 BUG 可以在使用最新的 toolchain 进行编译时重现,并且方法很简单。作者目前确认了 5.5-RELEASE 的 toolchain 中至少存在 4 个相关 BUG。让我们一起来看看这些问题。

使用异步闭包返回的值时堆栈损坏

异步闭包的返回值在混乱但确定性的内存偏移处被破坏。以下代码可以复现这个问题:

// 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))
...
复制代码

使用 async let 时出现分段错误

相对简单的 async 用法在调试和发布版本中都会遇到分段错误。虽然作者最初认为这仅限于在 main 函数中使用 async let ,但此后作者也在各种其他上下文中观察到了这个问题。

// 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)
复制代码

将枚举传递给 actor-isolated 的方法时堆栈损坏

actor-isolated 方法接收到的枚举值与调用者传递的值不同。作者在调试和发布版本中都观察到了这个问题,但在调试版本中更为常见和可重现。所有最近的版本,包括 5.5-RELEASE、DEVELOPMENT-SNAPSHOT-2021-09-23-a,都会受到影响。

// 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)))
复制代码

作者遇到了额外的内存损坏错误,包括在 actor-isolated 的属性(线程 8)上调用实例方法时有些奇怪,但这 3 个是作者本周遇到的问题,并已将它们归档为:

  1. SR-15225 bugs.swift.org/browse/SR-1…
  2. SR-15241 bugs.swift.org/browse/SR-1…
  3. SR-15240 bugs.swift.org/browse/SR-1…

在作者看来,错误 (1) 是迄今为止最危险的,因为它是默默发生的,并且会影响 5.5 版本的工具链。与 bug (3) 一样,bug (1) 也代表了一个潜在的安全漏洞,尽管它可能不容易在自然发生的代码中被利用。

作者发现错误 (3) 确实出现在使用 5.5 版本工具链构建的二进制文件中,它们都根据微小的代码更改变成段错误,所以到目前为止只能作为段错误重现的错误 (2) 也可能表现为无形漏洞。

作者建议使用并发特性的开发人员不应该发布任何用 5.5 工具链编译的东西,直到这些问题得到修复并发布补丁。

SIMD 类型 BUG

看起来任务组和具有广泛对齐的 SIMD 类型还有另一个问题。作者已经确认这个问题也会影响 5.5-RELEASE 工具链,能够在调试和发布版本中重现它。

@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)
复制代码

目前这个帖子在论坛中引起了广泛的关注。Swift 5.5 给开发者带来了很多新特性,特别是在异步编程方面。但似乎 Swift 还有许多路要走,要想获得更广泛的应用,必须确保语言自身的稳定性,相信核心团队也意识到这些问题。

文章分类
iOS
文章标签