「译文」理解现实中 Go 语言的并发漏洞 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
darluc
V2EX    Go 编程语言

「译文」理解现实中 Go 语言的并发漏洞

  •  
  •   darluc 2019 年 7 月 26 日 3521 次点击
    这是一个创建于 2446 天前的主题,其中的信息可能已经有所发展或是发生改变。

    查看全文

    对于编程(数据)模型的设计不仅会使得一些问题变得易于(或更难)解决,也会导致某些类型的漏洞更容易(或更难)产生、侦测和修复。本文的研究对象就是 Go 语言的并发机制。在深入研究之前先思考一下会更有意思,你现在对 Go 语言可能会有以下几点认识:

    • Go 语言明显是被设计为服务于并发编程的,想要使其变得更简单且不易出错
    • Go 语言确实让并发编程变更简单且不易出错了
    • Go 程序大量使用 channel 的消息传递机制,相较于共享内存的同步机制,它更不容易出错
    • Go 程序的并发漏洞更少
    • Go 语言内置的对于死锁和数据竞争的侦测能捕获所有(绝大多数)的代码漏洞

    这些陈述中的第一点是毋庸置疑的。至于其它几点,你可以先参考本文的研究数据,再重新评估一下你还会有多坚决地继续持有这些观点...

    我们使用真实的 Go 程序应用来进行对于并发漏洞的第一个系统性研究。我们研究了六个 Go 的软件(项目),包括:Docker,Kubernetes 和 gRPC。我们一共分析了 171 个并发相关的漏洞,其中超过一半是非传统的、Go 语言特有的问题。除了造成这些漏洞的根本原因外,我们还研究了它们的修复方式,通过实验重现这些漏洞,并使用两个公开的 Go 漏洞侦测工具进行了漏洞扫描。

    这六个用于研究的应用是:Docker,Kubernetes,etcd,CockroachDB,gRPC 以及 BoltDB,显然这些都是现实世界中重量级的 Go 代码。

    在继续研究它们的并发漏洞之前,我们先从研究这些应用实际是如何使用 Go 的并发原语开始。这些漏洞可以从两个主要的维度进行分类:表现行为(阻塞或非阻塞),以及造成问题的并发原语的机制(共享内存或消息传递)。我们先快速回顾一下 Go 语言的主要并发机制。

    Go 的并发机制

    Go 语言的一个主要设计目的,就是改进传统的多线程编程方式,简化并发编程使其不易出错。为了达到这个目的,Go 语言将它的多线程设计汇聚在了两点原则上:1 )使线程(称之为 goroutines,go 协程)变得轻量且易于创建; 2 )使用显式的消息传送(通过 channels 实现)进行线程通信。

    Go 协程是轻量的用户态线程(「绿色」线程)。在函数调用(包括匿名函数)前面加上 go 关键字,就能创建一个协程。匿名函数可以访问到在其之前申明的本地变量,而且它们是被共享使用的。Channels 用于在协程之间传送数据和状态,而且可以使用缓冲或不使用缓冲。当使用无缓冲 channel 的时候,一个协程在发送(或者接收)时会被阻塞,直到其它的协程进行了数据接收(或者发送)。select 语句可以让一个 go 协程同时监听多个 channel,如果多于一个 channel 可用的时候,Go 会随机选择一个分支执行。Go 语言还支持传统的同步机制原语包括互斥,条件变量和原子变量。

    Go 并发原语在实践中的应用

    这六个应用都大量使用了 Go 协程,尤其是用于匿名方法。

    在研究 gRPC 的时候,由于它既有 C 的实现也有 Go 的实现,比较起来结果就很有趣。下面的表格展示了处理相同数量的请求时,使用 gRPC-Go 和 gRPC-C 创建的协程数量比率。

    在对比表格中,go 协程相比 C 版本创建的线程有更短的生命周期,但是创建的频度更高。这种高频繁使用协程的行为是 Go 语言所推崇的。

    如果我们审视所有这些应用对并发原语的使用统计,会有一个更加令人惊讶的发现,共享内存的同步操作仍然比消息传递使用得多。

    最常出现的消息传递原语是 chan,它的使用量中在所有应用中占比 18.5% 到 43%。所以,现在的情形是传统的共享内存方式的通信还是被大量使用,与大量的消息传递原语同时并存。从漏洞的角度来看,我们拥有了不漏洞类型发生的可能性数据:共享内存通信造成的,消息传递造成的以及两者共同作用造成的!

    Go 并发漏洞

    作者搜索了这些应用的 Github 提交历史,从中找到了修复并发漏洞的提交(共 3211 个)。从这些漏洞中随机选取了 171 个用于研究。

    这些漏洞被分为阻塞漏洞和非阻塞漏洞。当一个或多个协程在执行中意外卡主无法推进时,阻塞漏洞就产生了。这个定义比死锁更宽泛,包含了循环等待以外的情况,但是不包括对其它非协程提供资源的等待。其中包含 85 个阻塞漏洞和 86 个非阻塞漏洞。

    我们还从另一个维度对漏洞进行了划分,看它们是否与共享内存保护相关( 105 个)还是和消息传递相关( 66 个)。

    阻塞型漏洞

    首先让我们来看看阻塞漏洞,其中的 42% 都与共享内存相关,另外 58% 与消息传递相关。上文提到过共享内存原语实际上比消息传递原语使用得更多。

    与普遍认为的消息传递不易犯错相比,我们的研究显示更多的阻塞漏洞是由错误的消息传递造成的,而不是由错误的共享内存保护造成的。

    共享内存相关的漏洞包括一般的常见情况,和因 Go 语言中 RWMutext 和 Wait 的实现而产生的新情况。

    对于消息传送相关的漏洞,许多都是因为 channel 丢失了发送或接受方,或者忘记了关闭 channel。

    所有消息传递引起的阻塞漏洞都与 Go 特有的消息传递语法相关,比如 channel。这些漏洞很难发现,尤其是当消息传递和其它的同步原语一起使用的时候。与一般的认识不同,消息传递会比共享内存方式造成更多的阻塞漏洞。

    在调查了这些漏洞的修复之后,会发现弄明白漏洞产生的原因之后,它们修复起来都相当简单,而且修复的类型都与造成漏洞的起因相关。这说明在 Go 语言中,使用全自动或半自动的工具修复阻塞型漏洞是很有前景的一个方向。

    Go 的内置死锁探测器只能检测到此次研究中 21 个阻塞漏洞中的两个。

    非阻塞型漏洞

    与消息传递相比,非阻塞型漏洞更多是由于共享内存的错误使用造成的。有一半的非阻塞漏洞符合「传统」的内存共享漏洞模式。还有一些漏洞是由于缺乏对 Go 语言特性的理解,尤其是前置申明的本地变量,在协程中被调用的匿名函数共享使用,以及 WaitGroup 的语法。

    Go 语言为简化多线程编程而引入的新编程模型和新工具库,可能造成更多的并发漏洞。

    消息传送型的非阻塞漏洞则相对不那么常见,“编程语言中这些复杂的消息传递机制,与其它的语言特性组合起来,可能造成这些漏洞很难被发现”

    有趣的是,修复共享内存漏洞的程序员,更喜欢使用消息传送机制来修复这些问题。

    Go 语言的数据竞争探测器可以探测出此次研究中一半的非阻塞漏洞。

    查看全文

    lhx2008
        1
    lhx2008  
       2019 年 7 月 27 日 via Android
    emmm,在说啥?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     865 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 22:29 PVG 06:29 LAX 15:29 JFK 18:29
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86