免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 4603 | 回复: 14
打印 上一主题 下一主题

[网络子系统] 又发现一个Linux Kernel的Bug [复制链接]

论坛徽章:
3
射手座
日期:2014-08-18 12:15:53戌狗
日期:2014-08-22 09:53:36寅虎
日期:2014-08-22 14:15:29
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2014-08-15 17:55 |只看该作者 |倒序浏览
本帖最后由 gaojl0728 于 2014-08-15 18:03 编辑

以前发布过一个linux kernel 内存泄露的bug没什么反响:一个Linux Kernel Memory Leak的定位过程
http://bbs.chinaunix.net/forum.p ... mp;fromuid=29434317

今天再来一个内核IP协议栈的Bug, 不索罗直接给了:
dst_release 通过引用计数来管理dst_entry 内存, 当引用计数递减到0, 就释放dst_entry,
atomic_dec_return虽然能保证dst->__refcnt递减操作是原子的,但是不能保证newrefcnt赋值并判断是否为0也是原子的,
这就造成了race condition, 在newrefcnt赋值之前, atomic_dec_return(&dst->__refcnt)可能已经在不同的调用路径被调用了多次,
也就是说newrefcnt有可能没有看到0值, 直接从1跳到-1, 这就造成dst_entry的内存永远不会被释放掉。
在我们的服务器环境上,能观察到看到WARN_ON(newrefcnt < 0)被触发了很多次,说明dst->__refcnt已经递减到负数。
这个bug影响所有的内核版本,包括最新的mainline
这个bug因为只有race condition发生的时候才会产生,因此极难重现,所以影响不会太大。

void dst_release(struct dst_entry *dst)
{
        if (dst) {
                int newrefcnt;

                newrefcnt = atomic_dec_return(&dst->__refcnt);
                WARN_ON(newrefcnt < 0);
                if (unlikely(dst->flags & DST_NOCACHE) && !newrefcnt) {
                        dst = dst_destroy(dst);
                        if (dst)
                                __dst_free(dst);
                }
        }
}
EXPORT_SYMBOL(dst_release);

评分

参与人数 2可用积分 +16 收起 理由
amarant + 10 赞一个!
Godbach + 6 感谢分享

查看全部评分

论坛徽章:
33
荣誉会员
日期:2011-11-23 16:44:17天秤座
日期:2014-08-26 16:18:20天秤座
日期:2014-08-29 10:12:18丑牛
日期:2014-08-29 16:06:45丑牛
日期:2014-09-03 10:28:58射手座
日期:2014-09-03 16:01:17寅虎
日期:2014-09-11 14:24:21天蝎座
日期:2014-09-17 08:33:55IT运维版块每日发帖之星
日期:2016-04-17 06:23:27操作系统版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-24 06:20:0015-16赛季CBA联赛之天津
日期:2016-05-06 12:46:59
2 [报告]
发表于 2014-08-15 18:44 |只看该作者
提交给 kernel team 看看呀.

论坛徽章:
0
3 [报告]
发表于 2014-08-15 20:20 |只看该作者
因为newrefcnt是local变量,所以虽然有你说的WARN_ON()会被触发,但是dst还是会被释放的,应该没有memory leak.

假设thread 1把__refcnt减为0,在thread1的newrefcnt被赋值前,thread2进来又做了一次dst_release,因为是-1,所以不会free dst。thread 1重新运行的时候,他的newrefcnt还0,所以它还是会free dst。

我觉得这个WARN_ON()的本义是认为__refcnt不应该会减为负数,肯定是有的地方没有get dst,导致__refcnt不匹配了。

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
4 [报告]
发表于 2014-08-15 23:48 |只看该作者
回复 1# gaojl0728

感谢分享。可以 report 给 kernel 啊。


   

论坛徽章:
0
5 [报告]
发表于 2014-08-16 18:47 |只看该作者
没看出有什么问题,应该是get和put之类没配对好。

论坛徽章:
0
6 [报告]
发表于 2014-08-17 13:26 |只看该作者
回复 3# eexplorer


    因为newrefcnt是local变量,所以虽然有你说的WARN_ON()会被触发,但是dst还是会被释放的,应该没有memory leak.

我同意你的观点.既然有-1之类出现,必然有0的线程出现. 0的线程负责释放内存. -1的线程不需要释放.内存泄露不会有.
问题是为什么release的次数能多到产生-1,肯定别的地方有bug

论坛徽章:
7
丑牛
日期:2013-10-18 14:43:21技术图书徽章
日期:2013-11-03 09:58:03辰龙
日期:2014-01-15 22:57:50午马
日期:2014-09-15 07:04:39丑牛
日期:2014-10-16 14:25:222015年亚洲杯之伊朗
日期:2015-03-16 10:24:352015亚冠之城南
日期:2015-05-31 09:52:32
7 [报告]
发表于 2014-08-17 15:36 |只看该作者
楼主能否再描述下硬件配置和网络情况?

论坛徽章:
7
丑牛
日期:2013-10-18 14:43:21技术图书徽章
日期:2013-11-03 09:58:03辰龙
日期:2014-01-15 22:57:50午马
日期:2014-09-15 07:04:39丑牛
日期:2014-10-16 14:25:222015年亚洲杯之伊朗
日期:2015-03-16 10:24:352015亚冠之城南
日期:2015-05-31 09:52:32
8 [报告]
发表于 2014-08-17 15:47 |只看该作者
为了保证并行化竞争的最小粒度锁,这样设计可能是合理的.
多个路径竞争调用release时,如果一个路径上有-1出现,那么必定有一个路径上有0出现,最终还是被释放了.

倒是5楼说的那种配错对才算是bug.

论坛徽章:
22
丑牛
日期:2014-08-15 14:32:0015-16赛季CBA联赛之同曦
日期:2017-12-14 15:28:14黑曼巴
日期:2017-08-10 08:14:342017金鸡报晓
日期:2017-02-08 10:39:42黑曼巴
日期:2016-11-15 15:48:38CU十四周年纪念徽章
日期:2016-11-09 13:19:1015-16赛季CBA联赛之同曦
日期:2016-04-08 18:00:03平安夜徽章
日期:2015-12-26 00:06:30程序设计版块每日发帖之星
日期:2015-12-03 06:20:002015七夕节徽章
日期:2015-08-21 11:06:17IT运维版块每日发帖之星
日期:2015-08-09 06:20:002015亚冠之吉达阿赫利
日期:2015-07-03 08:39:42
9 [报告]
发表于 2014-08-17 16:40 |只看该作者
本帖最后由 amarant 于 2014-08-17 16:44 编辑

ls们说的很对呀。感觉没什么问题。但这样的话题应该鼓励!希望能多看到这一类的话题

论坛徽章:
3
射手座
日期:2014-08-18 12:15:53戌狗
日期:2014-08-22 09:53:36寅虎
日期:2014-08-22 14:15:29
10 [报告]
发表于 2014-08-18 11:18 |只看该作者
回复 3# eexplorer


    的确如你所言, 不存在竞争条件,每个调用路径下,newrefcnt看到的都是自己的值,总结起来其实是我错误的理解了atomic_dec_return
下面是我的总结分析:

dst_release 存不存在竞争条件,关键在于 atomic_dec_return 能不能保证 "递减并且返回递减之后的值" 这两步是不是原子的。
看了下atomic_dec_return反汇编代码, 看起来atomic_dec_return的确能保证"递减并且返回递减之后的值" 是个原子操作。
而我之前理解atomic_dec_return 只能保证”递减“这一步是原子的,”返回递减之后的值“之前有可能被打断(也就有可能返回之前递减被执行了多次)

我之前认为是这样的:
newrefcnt = atomic_dec_return(&dst->__refcnt);
会被分成两步执行,这两步之间可能被打断,于是产生了竞争条件。
1. dst->__refcnt原子递减1
2. dst->__refcnt赋值给newrefcnt
在执行2之前,步骤1可能会被多个并发路径调用,这样newrefcnt看到的值就有可能是被递减了多次之后的dst->__refcnt, 这存在了竞争条件。

但是实际上是这样执行的:
1. dst->__refcnt赋值给newrefcnt, 然后dst->__refcnt递减1, 注意这两个动作被合并到一起,整体是一个原子操作
2. newrefcnt递减1
这样就保证了newrefcnt看到的值是dst->__refcnt减1之后的值,不存在前面的竞争条件



在gdb下反汇编dst_release,用"disassemble dst_release"命令显示如下:
    /* %r12d 就是 newrefcnt, 先置为-1 */
   0xffffffff814fecae <+30>:    mov    $0xffffffff,%r12d
    /* %r12d 是 newrefcnt, 0x80(%rdi) 是 dst->__refcnt, xadd交换目的和源操作数,然后载入两个值的和到目的操作数
    这样xadd执行完之后newrefcnt存储的是 dst->__refcnt的旧值(减1之前的值), dst->__refcnt存放减1之后的值
    lock 前缀保证了xadd是原子操作。
    */
   0xffffffff814fecb4 <+36>:    lock xadd %r12d,0x80(%rdi)
   /*上一句是dst->__refcnt递减1,这句是newrefcnt递减1,这两句保证了atomic_dec_return不存在竞争条件*/
   0xffffffff814fecbd <+45>:    sub    $0x1,%r12d
   //如果递减为负数,就触发WARN_ON
   0xffffffff814fecc1 <+49>:    js     0xffffffff814fecfe <dst_release+110>
   //测试r12d如果为0, 也就是newrefcnt,如果为0, 就进一步判断dst->flags & DST_NOCACHE
   0xffffffff814fecc3 <+51>:    test   %r12d,%r12d
   0xffffffff814fecc6 <+54>:    je     0xffffffff814fecd8 <dst_release+72>
   0xffffffff814fecc8 <+56>:    mov    (%rsp),%rbx
   0xffffffff814feccc <+60>:    mov    0x8(%rsp),%r12
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP