上次在内核net_device设备框架的一个缺陷文章中,描述了当前内核net_device框架的一个缺陷。后来内核的net模块的负责人David提交了一个commit “net: Fix inconsistent teardown and release of private netdev state”。这个commit关键的一点,就是给已经很庞大的net_device结构新增一个布尔变量“needs_free_netdev”。这个变量用于在函数netdev_run_todo中,判断是否需要释放netdev。这个变量的赋值,一般是在驱动的setup回调函数中赋值为true。
在这个commit提交之后,net_device的注册,就新增了一个“陷阱”。一个不小心,就会中招,会在某些特定的情况下,导致内核panic。
下面请看一个“问题”驱动,就是常见的vlan driver。
这段代码来自于register_vlan_device,当使用vconfig创建一个vlan设备时,就会调用到该函数。在上面的截图中,其代码逻辑使用的是常用的错误处理风格:使用goto跳转到对应的error handler。
这里的逻辑也很简单,先调用alloc_netdev申请了一个新的new_dev,然后调用register_vlan_dev注册netdev。如果失败,就跳转到out_free_newdev释放掉new_dev占用的资源和内存。
函数register_vlan_dev也比较简单,下面是其代码片断。
其错误处理风格,依然是Linux内核的标准方式。如netdev_upper_dev_link失败的话,就跳转到out_unregister_netdev,进行unregister。
这两个函数分开看,都没有问题。但结合在一起时,bug就出现了。不知道是否有敏锐的同学已经想到了问题所在,可以结合本文的开头,那个新加入的needs_free_netdev变量。
请看vlan_setup函数的实现。
在这个函数中,dev->needs_free_netdev被设置为True。这意味着当unregister netdev后,内核会在netdev_run_todo调用free_netdev释放资源。这意味着vlan的driver有一个double free的Bug。
register_vlan_dev函数的netdev_upper_dev_link失败后,内核调用unregister_netdevice进行清理,然后在register_vlan_device中,因为register_vlan_dev返回失败,会调用free_netdev释放资源。可是,由于vlan_setup设置了needs_free_netdev,这意味着内核稍后还会针对这个netdev再次调用free_netdev。这就引发了一个double free的bug。
为了验证自己的想法,我人为修改了register_vlan_dev,让netdev_upper_dev_link肯定返回失败。这样就可以轻易验证自己的猜测。果然,内核直接panic了。
不过coredump与我预期的不同,直接崩溃在vlan的ioctl中了。也就说,这个panic不是我期望的double free。而是下面的代码导致的。
在此例中,内核调用free_netdev时,dev的reg_state状态是NETREG_UNREGISTERING。因此触发了BUG_ON条件,直接导致内核panic。如果去掉了这个BUG_ON,在此处不直接panic,就会引发double free的问题。这样的内存Bug,查起来就痛苦多了。所以,内存错误的bug,还是越早暴露越早。
总结一下,register_netdevice引发的这个panic陷阱。调用free_netdev函数,必须保证该netdev,没有成功注册过,即register_netdevice没有被调用或者没有执行成功。一旦设备通过register_netdevice注册成功过,就绝不能直接调用free_netdev释放资源。
写驱动的同学如果不注意这个“陷阱”,并且没有测试到错误处理,就很可能引发内核崩溃的Bug了。
PS:
或者搜索free_netdev关键字,查找类似的bug,祝大家成功。