资源耗尽漏洞
初始调查
触发崩溃的输入反汇编为以下汇编代码:
|
|
这个特定的指令集会导致内存泄漏。程序执行步骤如下:
- 将r0(初始为0)增加255
- 如果r0小于等于8355838,跳回前一条指令
- 这个循环将执行32767次(总共65534条指令)
- 将r9设置为r3 * 2^3(由于r3初始为0,结果将为0)
- 调用不存在的函数
- 不存在的函数应该触发未知符号错误
有问题的优化
在jit.rs的第420行有一个优化,Solana应用此优化来减少更新指令计数器的频率。他们只在到达块末尾或调用时更新或检查指令计数器。
问题出现在漏洞补丁中,维护者将第1279行移动到第1275行。在原始指令顺序中,调用过程如下:
- 调用失败,因为符号未解析
- 报告未解析符号,调用report_unresolved_symbol函数
- 该函数在堆分配字符串中返回调用的符号名称(或"Unknown")
- 更新pc
- 验证指令计数,覆盖未解析符号错误并终止执行
由于未解析符号错误被覆盖,对堆分配String的引用丢失且从未释放,导致内存泄漏。
武器化
通过创建恶意ELF文件,可以控制report_unresolved_symbol中的name字符串大小:
|
|
使用超长的函数名,每次执行可以泄漏1MiB内存而不是仅仅7字节。
持久性.rodata损坏漏洞
初始调查
触发崩溃的输入反汇编为:
|
|
JIT以Ok(0)终止,而解释器以访问违规错误终止。访问地址4294967296(0x100000000)对应程序代码区域。
x86的诅咒
问题出现在jit.rs:1490处错误大小的内存操作数。发出的cmp指令是cmp DWORD PTR [rax+0x19], 0x0
,但应该使用8位操作数。
x86 cmp操作数大小问题:opcode 0x81只定义为16、32和64位寄存器操作数。如果要使用8位寄存器操作数,需要使用0x80 opcode变体。
概念证明
创建PoC演示只读数据可以被持久修改:
|
|
程序输出显示修改是持久的:
|
|
影响
如果BPF链上程序存在错误的偏移量或用户控制的偏移量,恶意用户可以修改程序的只读数据。在最坏情况下,这可能导致替换资金金额、钱包地址等。
报告和奖励
两个漏洞都报告给Solana,每个漏洞获得10万美元奖励。第一个漏洞被分类为拒绝服务(非RPC),第二个漏洞虽然作者认为应该获得更高奖励,但也被分类为拒绝服务(非RPC)。
Solana在支付方面表现出极大的灵活性,允许将奖金直接捐赠给德州农工大学网络安全俱乐部,而不是强制使用锁定的SOL代币。