从一个跨二十年的glibc bug说起
1. 缘起
这几天调gcc 7.5.0 +glibc 2.23的交叉编译工具链,由于gcc 7.5.0的默认打开Werr,偶然发现了glibc一个隐藏了二十年的世纪大bug。
这个bug在glibc 2.0版本刚开始就引入了,但直到2.25版本才最终解决,即使按glibc-2.0.1.bin.alpha-linux.tar.gz 版本的发布时间(04-Feb-1997)到glibc-2.25.tar.bz2 的发布时间(05-Feb-2017),也持续了20年加一天。
用gcc 7.5编译的时候如果使能-Wall -Werror这2个选项(-Wall 英文说明是Enable most warning messages,表示使能大多数告警上报;-Werror表示所有告警都当错误来上报,不可忽略),会报下面的错误:
nss_nisplus/nisplus-alias.c: In function '_nss_nisplus_getaliasbyname_r':
nss_nisplus/nisplus-alias.c:300:12: error: argument 1 null where non-null expected [-Werror=nonnull]
char buf[strlen (name) + 9 + tablename_len];
^~~~~~~~~~~~~
In file included from ../include/string.h:54:0,
from ../sysdeps/generic/hp-timing-common.h:40,
from ../sysdeps/x86_64/hp-timing.h:38,
from ../include/libc-internal.h:7,
from ../sysdeps/x86_64/nptl/tls.h:29,
from ../sysdeps/x86_64/atomic-machine.h:20,
from ../include/atomic.h:50,
from nss_nisplus/nisplus-alias.c:19:
../string/string.h:394:15: note: in a call to function 'strlen' declared here
extern size_t strlen (const char *__s)
^~~~~~
nss_nisplus/nisplus-alias.c:303:39: error: '%s' directive argument is null [-Werror=format-truncation=]
snprintf (buf, sizeof (buf), "[name=%s],%s", name, tablename_val);
^~
cc1: all warnings being treated as errors
如果不使能-Werror,编译器最多会上报告警,程序还是能正常编译通过。上面2个告警分别对strlen的入参和snprintf的字符串格式化参数做了非空检查,根据代码逻辑判断,两处代码如果执行到,调用的入参确实都必然是空指针。
源代码如下:
276 enum nss_status
277 _nss_nisplus_getaliasbyname_r (const char *name, struct aliasent *alias,
278 char *buffer, size_t buflen, int *errnop)
279 {
280 int parse_res;
281
282 if (tablename_val == NULL)
283 {
284 __libc_lock_lock (lock);
285
286 enum nss_status status = _nss_create_tablename (errnop);
287
288 __libc_lock_unlock (lock);
289
290 if (status != NSS_STATUS_SUCCESS)
291 return status;
292 }
293
294 if (name != NULL)
295 {
296 *errnop = EINVAL;
297 return NSS_STATUS_UNAVAIL;
298 }
299
300 char buf[strlen (name) + 9 + tablename_len];
301 int olderr = errno;
302
303 snprintf (buf, sizeof (buf), "[name=%s],%s", name, tablename_val);
304
305 nis_result *result = nis_list (buf, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
可以看出300行对应的strlen函数的入参要求非空,但由于294行做了一个非空的判断并返回,也就是说如果294行的if判断为非,那说明name指针必然为空,这时strlen来获取字符串长度就会异常。
具体会怎么异常?我们可以写个简单的例子:
1 #include <stdio.h>
2 #include <string.h>
3 int main()
4 {
5 printf("%d", strlen(NULL));
6 return 0;
7 }
默认不带任何参数的情况下,gcc会上报告警,但仍然可以编译通过,执行后会出现Segmentation fault:
1 gcc test1.c
2 test1.c: In function 'main':
3 test1.c:5:5: warning: null argument where non-null required (argument 1) [-Wnonnull]
4 printf("%d", strlen(NULL));
5 ^
6 test1.c:5:12: warning: format '%d' expects argument of type 'int', but argument 2 has type 'size_t {aka long unsigned int}' [-Wformat=]
7 printf("%d", strlen(NULL));
8 ^
9
10 ./a.out
11 Segmentation fault
编译如果加上-Wall -Werror选项会直接报error编译失败:
1 gcc -Wall -Werror test1.c
2 test1.c: In function 'main':
3 test1.c:5:5: error: null argument where non-null required (argument 1) [-Werror=nonnull]
4 printf("%d", strlen(NULL));
5 ^
6 test1.c:5:12: error: format '%d' expects argument of type 'int', but argument 2 has type 'size_t {aka long unsigned int}' [-Werror=format=]
7 printf("%d", strlen(NULL));
8 ^
9 cc1: all warnings being treated as errors
问题的直接原因还是因为libc库里面的strlen没有做空指针保护,直接访问入参对应的内存了,所以实际上就会出现空指针访问,程序异常退出。
同样的303行的snprintf也要求%s对应的参数不能是空指针,否则也会出现Segmentation fault。
从上面的分析可以看出,有一些warning实际上本身就是错误,应该作为error来处理,在glibc的漫长进化过程中,有很多执行路径可能真的没走到(如果没有100%覆盖率的单元测试,也没有完善的代码review机制,可能永远也没人会发现),或者确实不影响功能的正常发布。但这些告警指向的代码,一旦走到就会出现致命错误。
最终glibc修正代码其实也很简单,就是将294行的“if (name != NULL)”修改成了“if (name != NULL)”,一个运算符用反了。
很多影响非常大的bug,定位之后的实际修改都是简单的一两行代码的事情,但问题的关键是要发现bug并定位bug,并且在bug修正之后的波及测试工作。
这个bug之所有能持续20年没人发现,只能说明glibc中应该还有很多代码在实际场景中没有用到。
2. 编译器的进化
下面这个表格给出了不同clang或者gcc版本新增的代码静态检查的告警计数,为了显得简洁一点,clang7或者更老的clang的所有告警做了一下汇总,gcc 4或者更老的gcc版本的所有告警也做了一下汇总,从中可以看出每次大版本升级,编译器团队都给开发团队提供了一些新的工具能更多的发现自己代码bug的神器。
下面汇总的1204个告警中,有119个告警是clang和gcc都提供的,其他966个告警至少从名称上看是gcc或者clang特有的。其中clang(以clang 12来算)特有的告警检查项有803个,gcc(以gcc 9来算)有178个,单从这个指标看clang在静态检查方面是远胜于gcc的,"2012 ACM Software System Award"大奖实至名归。
不过clang本身是为了支撑llvm的,所以很多与llvm不相关的功能都是直接调用的gcc的库接口,可以认为clang是站在gcc的巨人肩膀上来发布的自己的产品。
当前各个公司都引入了很多静态检查的工具来完善代码质量,但第一步还是要把静态检查工具的老祖宗,也就是编译器,自带的静态检查功能用足用好,再考虑消除其他静态检查工具的问题比较靠谱。走好这一步,引入clang非常必要。
|
first introduced compiler version |
Count of new warning options |
|---|---|
| clang7 or older | 584 |
| clang8 | 12 |
| clang9 | 223 |
| clang10 | 55 |
| clang11 | 33 |
| clang12 | 15 |
| gcc 4 or older | 172 |
| gcc 5 | 26 |
| gcc 6 | 24 |
| gcc 7 | 35 |
| gcc 8 | 16 |
| gcc 9 | 24 |
| Grand Total | 1204 |
从一个跨二十年的glibc bug说起的更多相关文章
- Vue 全家桶 + Electron 开发的一个跨三端的应用
代码地址如下:http://www.demodashi.com/demo/11738.html GitHub Repo:vue-objccn Follow: halfrost · GitHub 利用 ...
- 『开源』仿SQLServer山寨一个 跨数据库客户端
002 Laura.SqlForever项目简单介绍 相关文章 <『练手』001 Laura.SqlForever架构基础(Laura.XtraFramework 的变迁)> <『练 ...
- 一个跨域请求的XSS续
之前讨论过,在解决post跨域请求时,采用iframe+本域代理页的形式,兼容性(当然是包括IE6啦)是最好的.上次提到,代理页面的作用是:执行本域下的回调函数.就是这个原因,给XSS带来了便利.详细 ...
- 转:一个跨WINDOWS LINUX平台的线程类
来源:http://blog.csdn.net/dengxu11/article/details/7232681 继Windows下实现一个CThread封装类之后,这里我再实现一个跨WINDOWS ...
- Spring 循环引用(一)一个循环依赖引发的 BUG
Spring 循环引用(一)一个循环依赖引发的 BUG Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环 ...
- android一个下拉放大库bug的解决过程及思考
android一个下拉放大库bug的解决过程及思考 起因 项目中要做一个下拉缩放图片的效果,搜索了下github上面,找到了两个方案. https://github.com/Frank-Zhu/Pul ...
- Microsoft SilverLightt是一个跨浏览器的、跨平台的插件,为网络带来下一代基于.NETFramework的媒体体验和丰富的交互式应用程序。
Microsoft Silverlight是一个跨浏览器的.跨平台的插件,为网络带来下一代基于.NETFramework的媒体体验和丰富的交互式应用程序.Silverlight提供灵活的编程模型,并可 ...
- 移动端H5页面开发,碰到一个字体变大的BUG
移动端H5页面开发,碰到一个字体变大的BUG webkit内核下,对不定高宽的元素可能会放大其字体.那么,就可以设置一个max-width:或者使用-webkit-text-size-adjust: ...
- 一个历时五天的 Bug
一个程序员在没有成长成为架构师之前,几乎都要跟 Bug为伴,程序员有很多时间都是花在了查找各种 Bug上. 我印象深刻的一个Bug, 是一个服务器网络框架无锁队列的 Bug .那个 Bug 连续查找了 ...
随机推荐
- 2020国防科大综述:3D点云深度学习—综述(点云形状识别部分)
目录 摘要 1.引言: 2.背景 2.1 数据集 2.2评价指标 3.3D形状分类 3.1基于多视图的方法 3.2基于体素的方法 3.3基于点的方法 3.3.1 点对多层感知机方法 3.3.2基于卷积 ...
- MegEngine TensorCore 卷积算子实现原理
作者:章晓 | 旷视 MegEngine 架构师 一.前言 2020 年 5 月 Nvidia 发布了新一代的 GPU 架构安培(Ampere).其中和深度学习关系最密切的莫过于性能强劲的第三代的 T ...
- 跟我一起写 Makefile(二)
三.make是如何工作的 在默认的方式下,也就是我们只输入make命令.那么, 1.make会在当前目录下找名字叫"Makefile"或"makefile"的文 ...
- 剖析虚幻渲染体系(10)- RHI
目录 10.1 本篇概述 10.2 RHI基础 10.2.1 FRenderResource 10.2.2 FRHIResource 10.2.3 FRHICommand 10.2.4 FRHICom ...
- 武器级工具包 Immunity Canvas 7.26 泄露事件 | 附下载地址
关于Immunity Canvas Immunity CANVAS是Immunity公司的一款商业级漏洞利用和渗透测试工具,包含了480多个以上的漏洞利用,该工具并不开源,其中文版介绍如下: &quo ...
- Kali 2.0 安装教程
本文适合KALI初学者,将详细介绍Kali Linux 2.0的安装过程. 首先我们到KALI的官网下载镜像,大家可以自己选择下载32或64位的KALI 2.0系统. KALI 官网:https:// ...
- JavaWeb学习笔记(四)
本文内容 1. 会话技术 1. Cookie 2. Session 2. JSP:入门学习 会话技术 1. 会话:一次会话中包含多次请求和响应. * 一次会话:浏览器第一次给服务器资源发送请求,会话建 ...
- 【硬件模块】RFIDSetting
The Octane SDK includes the core library by acting as a wrapper for extraction, modifying, and the a ...
- spring-cloud-sleuth+zipkin追踪服务
1, 父Maven pom 文件 <parent> <groupId>org.springframework.boot</groupId> <artifact ...
- C语言 windows下Ansi和UTF-8编码格式的转换
当我们使用MinGW-w64作为编译器在windows系统环境下进行C语言编程时,如果源代码文件(.c)保存格式为Ansi格式,则在打印汉字时不会出现乱码:反之,如果我们使用UTF-8格式保存,则会出 ...