多线程作为提升效率的一个手段,在当今越来越被广泛使用。然而由于并发引起的缺陷也越来越突出。数据竞争就是其中的一个主要缺陷类型。它是多个线程同时访问同一段内存,至少有一个是写访问。在由并发引起的缺陷中,数据竞争是最难暴露的缺陷之一,不仅是由于数据竞争触发时机比较苛刻,也由于引起的后果不确定。这两点导致数据竞争难以被动态分析方法捕获和重现。除此之外,数据竞争的修复也是一大困难,据统计[1],71%的非死锁并发缺陷不是通过简单的加锁解决的,开发者需要考虑正确性、效率以及其他因素才决定一个修复策略。所以越早检测到数据竞争缺陷,相比于其他缺陷越能够降低开发成本。

Transmission:

https://github.com/transmission/transmission/issues/141

在下图中的报告中,我们发现在第128行创建了一个新的线程执行ThreadFunc函数,并将t作参数传递给ThreadFunc,随后在第129行使用了t的一个成员。

blog_dr_1

进入ThreadFunc我们发现,该函数在第106行调用了tr_free函数,而tr_free函数的主要用处是释放其参数所指向的内存。由此我们可以发现,如果该程序的线程调度顺序是ThreadFunc一直执行,而第129行的pthread_detach被推后到线程执行完tr_free函数,则会触发Use After free。

blog_dr_2
blog_dr_3

Axel

https://github.com/eribertomota/axel/issues/58

Axel是在Linux/Unix下的下载加速器。我们报告了其中关于search_sortlist的数据竞争。报告的路径一共有5个关键点,第一点和第四点在如下图的第237行和238行。在这个报告中,我们发现问题可能出在search_getspeeds和search_sortlist函数中,并且都是针对search这个参数。

blog_dr_4

search_getspeeds的部分细节见下图。201行创建了线程,在search_speedtest函数和search_sortlist函数中,有对search参数的数据竞争。

blog_dr_5

正常情况下,由于第193行的循环退出条件,除非所有线程都需要得到speed信息才会跳出循环。这样就可以保证search_sortlist函数无法与线程函数search_speedtest并发,从而不会发生数据竞争类错误。

但是问题出现在第209行,我们发现一旦线程创建失败就会直接调用return,此时在此之前创建的线程依旧执行,进而线程函数search_speedtest中的与其参数相关的变量都有可能与search_sortlist中的qsort函数中results数据竞争,导致最终结果出错。

blog_dr_6
blog_dr_7

[1] Lu, Shan, et al. "Learning from mistakes: a comprehensive study on real world concurrency bug characteristics." ACM Sigplan Notices. Vol. 43. No. 3. ACM, 2008.