Wu S, Li Z, Zhou H, et al. Following the “Thread”: Toward Finding Manipulatable Bottlenecks in Blockchain Clients[C]//Proceedings of the 33rd ACM SIGSOFT International Symposium on Software Testing and Analysis. 2024: 1440-1452.
Abstract
区块链客户端是区块链网络的基础,客户端本身存在性能瓶颈,攻击者可以故意利用瓶颈制造DoS。现有工作关注了少部分瓶颈,且局限于手工分析。本文深入分析了瓶颈的主要原因,开发了Thread-Neck,在客户端运行时检测症状。ThreadNeck 将客户端建模为多个线程,描绘它们之间的相互关系,以准确刻画客户端的行为。对Geth/Besu/Reth/FISCO-BCOS的实验发现了13个可利用的瓶颈,其中6个是未知的。
代码仓库:Not Available
Intro
区块链应用:供应链、IoT、保险
客户端存在瓶颈->DoS->数量减少->51%攻击、DApp宕机
Related Attack:
- DoS相关的工作,NURGLE
- 对client插桩可能误差(记录日志对IO的影响)
挑战
- 对客户端的执行的刻画:区块链相比传统软件更加复杂、数据层、网络、共识和合约。每层有独立的设计并且有交互,如何决定划分的细粒度?按层划分可能太粗,按函数划分理解调用的语境
- 从正常状态中识别出瓶颈:难以生成对客户端的输入(需要满足密码学和共识的约束),客户端是有状态的并且和网络交互,难以穷尽所有执行。因此可行的方法是检测客户端的正常运行,寻找可能存在瓶颈的地方(好low),由于缺乏异常性能指标,这种方法也存在问题。例如,当内存和 CPU 使用等性能指标在正常操作中保持正常时,很难识别出瓶颈的细微迹象。
- 跨客户端的多样性:每个客户端的瓶颈不同,设计和实现也不同,难以用统一框架进行检测
解决方法:
- 将客户端可化为线程,根据线程的生命周期对客户端进行划分,为客户端构建了一个线程等待与唤醒图,以展示线程之间的相互关系
- 分析现有的性能诊断和优化的研究,提出了两类的瓶颈的根本原因。记录了发现了与瓶颈相关的症状,后续进行PoC验证
- 将不同编程语言的线程模型进行对比,对不同实现提出对应的策略
测试了4种客户端 (Geth/Besu/Reth/FISCO-BCOS),发现了13个可利用的瓶颈,攻击者可以DoS客户端或者对应的模块,6个未知的问题已经被开发者修复
贡献
- 首个系统研究区块链客户端瓶颈的工作
- 设计并实现ThreadNeck,准确刻画了区块链客户端的执行并帮助确认瓶颈
- 将ThreadNeck应用到4个主流客户端,发现了13个瓶颈
Background
Client
客户端维护区块链,和其他客户端通信,验证和传播交易/区块
作者认为Parity和Besu结构一致
Geth的结构(很有帮助)
可以划分为
- 网络层:管理和P2P网络的通信
- 存储层:存储以太坊网络的状态,管理账户余额、合约、合约的状态
- 执行引擎:执行交易、运行合约,包括交易池和EVM
- 接口层:提供RPC、HTTP和其他接口,外部应用程序可以和客户端交互,发交易和读取区块链数据
- 外部的consensus engine:确定规则,处理区块的产生和验证。PoS之后,共识引擎已经被分离了,通过接口层和执行层客户端接触
用户通过DApp发送交易,首先交易作为RPC发送给客户端,然后通过网络层传播交易,能够出块的节点可以将交易打包进区块,新产生的区块被广播到网络中。其他节点收到区块后使用共识引擎验证区块,在EVM中执行交易,更新状态,结果存储在客户端
Thread State
线程:程序中的执行路径,可能会存在多个状态
- running:运行
- runnable:可运行
- blocked:阻塞
Threat Model
大多数编程语言使用操作系统提供的线程,Golang的机制不同,采用goroutine,支持了更高的并发
Motivation
瓶颈是软件实现中限制了应用效率的弱点
Motivating Example
真实事件,调用extcodesize,带来严重IO负载
cancun中的策略
Root Cause
分析现有论文,总结方法
- 无效同步:用于保证线程以特定顺序执行。无效同步指使用错误,导致某些线程挂起,影响程序性能,已有工作给出了无效同步的三类主要原因
- 锁竞争:主要原因,攻击A,B和C会等待更长时间
- 负载不均衡
- IO阻塞
- 资源耗尽:如CPU和内存
为了检测无效同步类的瓶颈,需要理解线程间的交互,因此将客户端建模为线程,画出线程等待和唤醒图,分析线程之间的pattern
为了检测资源类型的瓶颈,测量线程执行路径的资源消耗
Overview
可以划分为两个阶段
- 在运行时刻画客户端,记录线程唤醒时间和其他相关信息,构造线程等待&唤醒图,将线程之间的相互关系可视化
- 分析图,监测两类瓶颈对应的症状,检测存在的瓶颈,对于检测出的线程,根据调用栈定位出测试用例。利用开发者提供的模板测试,进行PoC,测试发现的瓶颈是否会导致DoS
Building Wait&Wake Graph
有向图,记录程序执行过程中线程之间等待和唤醒的关系
- 点代表线程,包含调用栈,记录了程序的执行路径以及资源消耗
- 磁盘、网络接口认为是 虚拟线程
- 边代表唤醒关系, A->b 线程A唤醒了线程B,即B可能等待A的执行
接下来讨论如何构造这个图,根据编程语言实现 Thread 的方法
Native
基于wPerf[OSDI18],hook 内核函数 try_to_wake_up
Golang
涉及对go routine的支持
one OS thread -> multiple go routines
始终保持OS thread running
解决办法:阅读go的源码,插桩 goready
函数
利用go官方的trace和pprof测量资源利用
Merge Similar Threads
区块链客户端运行的线程非常多,为了减小图的规模,将相似的线程进行合并
因为很多线程实际执行了相同的功能(网络连接多个线程处理网络请求)
基于call stack鉴定相似的线程
线程建模为稀疏向量,计算余弦相似度
Identify the Bottlenecks
针对两类,无效同步和资源耗尽类型的瓶颈
无效同步
观察出最主要的指标是图中的循环等待,问题转化为在图中检测循环
重视被NIC(网络接口)调起的线程
去掉worker和pool之间的边
注释掉可能会漏掉一些,因此手工代码审计了注释掉的worker和pool
构造PoC很难:
- 松耦合的系统
- 需要对源码的深刻理解
方法:unit test,用class和方法名去检索
对于已知事件,策略
- 增加交易数量
- 提升交易复杂度
- 区块大小
未知事件,根据源码分析原因,哪个函数导致了瓶颈,是否被外部输入影响
资源耗尽
xxx presents a challenge.
符号执行无法观察资源占用,路径不敏感的符号执行生成的假阳多,路径敏感的符号执行可扩展性不足(区块链客户端是规模庞大的软件)
解决方法:通过线程来刻画资源占用,关注了两类线程
- 执行时间长或内存占用高
- 执行时间或内存分配变化大,表明该类线程容易受到外部输入的影响
使用标准差刻画变化性
Demo
使用Besu作为例子
下载区块头,验证,下载区块体,验证交易签名,将区块导入链
可以观察到存在两个循环,PoC的设计
观察call stack,找到入口点,构造恶意合约,修改单元测试函数
Evaluation
回答的研究问题
- 能否检测出瓶颈
- 是否比已有工作更有效
- 额外的开销有多少
- 配置会如何影响检测的效果
环境配置
和主网同步,使用现实世界中的交易作为输入
使用主网数据的原因(多样性,更贴合现实世界)
GraphQL API是什么?
运行4天
env:64-bit machine with 16 cores and 256GB memory
使用滑动窗口策略,每2小时刻画120s,频率为4000HZ(这是什么)
RQ1
7个已知,6个未知,所有都被确认,3个CVE
case study
发现IO-2,Peer,Downloader线程之间存在环
peer可以在节点即将结束同步时恶意地延迟消息
可能的缓解方法
RQ2
9%的CPU消耗,内存可不计
RQ3
和已有工作在Besu和C++对比
wPerf发现了568,未能发现本文工具未发现的
给出了不能发现的原因
RQ4
门限实验,合并同类线程的模块 和 资源占用的门限
50% 距离时会漏掉一个bug,因此采用了60%和70%
15 线程
Discussion
可以扩展到其他应用程序中
为了选择区块链作为研究对象
- 对web3社区的重要性
- 区块链的开源,有丰富的单元测试用例
- 工作原理近似,发现的瓶颈和缺陷可以迁移
缺陷:无法适用于所有软件
对于多线程软件使用IPC进行通信,无法有效监测
如果软件线程数太少,难以发现线程间同步的问题
Threat to Validity
依赖人工撰写测试用例,消耗时间较长,缺乏可扩展性
- 计划后续使用大模型基于模板生成测试用例
- 结合模糊测试增加测试用例的多样性
区块链客户端数量较少,未能支持Nethermind,未来计划支持更多客户端
- 瓶颈并不一定会导致系统性能降低,为了检测这类问题不能直接用现在的方法,需要设计新的oracle
- 代码覆盖率不高,可能因为dead code和路径不够丰富
Related work
On CPU 许多性能测量工具采用 插桩 和 采样方法实现,将代码在总体执行时间内的占比排序,缺少出花费最多时间的部分
Off CPU 比如等待的事件也可能是瓶颈
区块链客户端性能测量
高纬度的参数(吞吐量、延迟)
插桩,分析时间消耗的分布,研究交易处理中的瓶颈
每个opcode的耗时
函数的耗时
区块链客户端优化
优化共识算法
分片
合约交易并行
DAG
新的MPT的设计
Conclusion
检测区块链客户端中可利用的瓶颈
给出了两类瓶颈的根本原因
将客户端建模为多个线程,研究多个线程的关系
Q
- GraphQL API
- RPC eth_call is free?
- 存储的消耗?