【持续更新】C/C++ 踩坑记录(一)

这篇具有很好参考价值的文章主要介绍了【持续更新】C/C++ 踩坑记录(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

未定义行为之 NULL dereference

下面这段代码中 is_valid() 解引用了空指针 str,我们的直觉是编译运行后将迎来 SIGSEGV,然而事情并非所期望的那样。

/*
 * ub_null.c - 未定义行为演示 之 NULL dereference
 */
#include <stdio.h>
#include <string.h>

int is_valid(const char *str)
{
  if(*str == 0x80) return 1;
  if(str == NULL) return 0;
  return strcmp(str, "expected string") == 0;
}

int main(void)
{
  const char *str = NULL;
  printf("%d\n", is_valid(str));
  return 0;
}
lyazj@HelloWorld:~$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

lyazj@HelloWorld:~$ gcc -Wall -Wshadow -Wextra ub_null.c -o ub_null -O0
ub_null.c: In function ‘is_valid’:
ub_null.c:6:11: warning: comparison is always false due to limited range of data type [-Wtype-limits]
    6 |   if(*str == 0x80) return 0;
      |           ^~
lyazj@HelloWorld:~$ ./ub_null
0

结合 GCC 发出的警告,不难推断出条件表达式 *str == 0x80 在编译期被求值且相应的 if 语句被优化掉了,而且这是在 O0 的优化等级下。以下的反汇编结果验证了这一点。

lyazj@HelloWorld:~$ objdump --disassemble=is_valid -j.text ub_null

ub_null:     file format elf64-x86-64


Disassembly of section .text:

0000000000001169 <is_valid>:
    1169:	f3 0f 1e fa          	endbr64 
    116d:	55                   	push   %rbp
    116e:	48 89 e5             	mov    %rsp,%rbp
    1171:	48 83 ec 10          	sub    $0x10,%rsp
    1175:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
    1179:	48 83 7d f8 00       	cmpq   $0x0,-0x8(%rbp)
    117e:	75 07                	jne    1187 <is_valid+0x1e>
    1180:	b8 00 00 00 00       	mov    $0x0,%eax
    1185:	eb 1e                	jmp    11a5 <is_valid+0x3c>
    1187:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
    118b:	48 8d 15 72 0e 00 00 	lea    0xe72(%rip),%rdx        # 2004 <_IO_stdin_used+0x4>
    1192:	48 89 d6             	mov    %rdx,%rsi
    1195:	48 89 c7             	mov    %rax,%rdi
    1198:	e8 d3 fe ff ff       	call   1070 <strcmp@plt>
    119d:	85 c0                	test   %eax,%eax
    119f:	0f 94 c0             	sete   %al
    11a2:	0f b6 c0             	movzbl %al,%eax
    11a5:	c9                   	leave  
    11a6:	c3                   	ret    

我们在同一环境对 O3 优化等级做相同的实验,得到了相同的结果:

lyazj@HelloWorld:~$ gcc -Wall -Wshadow -Wextra ub_null.c -o ub_null -O3
ub_null.c: In function ‘is_valid’:
ub_null.c:6:11: warning: comparison is always false due to limited range of data type [-Wtype-limits]
    6 |   if(*str == 0x80) return 0;
      |           ^~
lyazj@HelloWorld:~$ ./ub_null
0
lyazj@HelloWorld:~$ objdump --disassemble=is_valid -j.text ub_null

ub_null:     file format elf64-x86-64


Disassembly of section .text:

00000000000011a0 <is_valid>:
    11a0:	f3 0f 1e fa          	endbr64 
    11a4:	48 85 ff             	test   %rdi,%rdi
    11a7:	74 27                	je     11d0 <is_valid+0x30>
    11a9:	48 83 ec 08          	sub    $0x8,%rsp
    11ad:	48 8d 35 50 0e 00 00 	lea    0xe50(%rip),%rsi        # 2004 <_IO_stdin_used+0x4>
    11b4:	e8 a7 fe ff ff       	call   1060 <strcmp@plt>
    11b9:	85 c0                	test   %eax,%eax
    11bb:	0f 94 c0             	sete   %al
    11be:	48 83 c4 08          	add    $0x8,%rsp
    11c2:	0f b6 c0             	movzbl %al,%eax
    11c5:	c3                   	ret    
    11c6:	66 2e 0f 1f 84 00 00 	cs nopw 0x0(%rax,%rax,1)
    11cd:	00 00 00 
    11d0:	31 c0                	xor    %eax,%eax
    11d2:	c3                   	ret    

接下来我们用下面的两行代码替换被优化掉的 if 语句,看看会发生什么:

char head = *str;
if(head == 0x80) return 0;
lyazj@HelloWorld:~$ gcc -Wall -Wshadow -Wextra ub_null.c -o ub_null -O0
ub_null.c: In function ‘is_valid’:
ub_null.c:10:11: warning: comparison is always false due to limited range of data type [-Wtype-limits]
   10 |   if(head == 0x80) return 0;
      |           ^~
lyazj@HelloWorld:~$ ./ub_null 
Segmentation fault
lyazj@HelloWorld:~$ objdump --disassemble=is_valid -j.text ub_null

ub_null:     file format elf64-x86-64


Disassembly of section .text:

0000000000001169 <is_valid>:
    1169:	f3 0f 1e fa          	endbr64 
    116d:	55                   	push   %rbp
    116e:	48 89 e5             	mov    %rsp,%rbp
    1171:	48 83 ec 20          	sub    $0x20,%rsp
    1175:	48 89 7d e8          	mov    %rdi,-0x18(%rbp)
    1179:	48 8b 45 e8          	mov    -0x18(%rbp),%rax
    117d:	0f b6 00             	movzbl (%rax),%eax
    1180:	88 45 ff             	mov    %al,-0x1(%rbp)
    1183:	48 83 7d e8 00       	cmpq   $0x0,-0x18(%rbp)
    1188:	75 07                	jne    1191 <is_valid+0x28>
    118a:	b8 00 00 00 00       	mov    $0x0,%eax
    118f:	eb 1e                	jmp    11af <is_valid+0x46>
    1191:	48 8b 45 e8          	mov    -0x18(%rbp),%rax
    1195:	48 8d 15 68 0e 00 00 	lea    0xe68(%rip),%rdx        # 2004 <_IO_stdin_used+0x4>
    119c:	48 89 d6             	mov    %rdx,%rsi
    119f:	48 89 c7             	mov    %rax,%rdi
    11a2:	e8 c9 fe ff ff       	call   1070 <strcmp@plt>
    11a7:	85 c0                	test   %eax,%eax
    11a9:	0f 94 c0             	sete   %al
    11ac:	0f b6 c0             	movzbl %al,%eax
    11af:	c9                   	leave  
    11b0:	c3                   	ret    

段错误如愿以偿地发生了,且是来自读取 str 处 1 字节并进行零扩展的 movzbl 指令,之前看到的编译期求值没有再次发生。

现在升高优化等级至 Og,编译期求值并优化掉第一个 if 语句的特效回归了:

lyazj@HelloWorld:~$ gcc -Wall -Wshadow -Wextra ub_null.c -o ub_null -Og
ub_null.c: In function ‘is_valid’:
ub_null.c:7:11: warning: comparison is always false due to limited range of data type [-Wtype-limits]
    7 |   if(head == 0x80) return 0;
      |           ^~
lyazj@HelloWorld:~$ ./ub_null
0
lyazj@HelloWorld:~$ objdump --disassemble=is_valid -j.text ub_null

ub_null:     file format elf64-x86-64


Disassembly of section .text:

0000000000001169 <is_valid>:
    1169:	f3 0f 1e fa          	endbr64 
    116d:	48 85 ff             	test   %rdi,%rdi
    1170:	74 1d                	je     118f <is_valid+0x26>
    1172:	48 83 ec 08          	sub    $0x8,%rsp
    1176:	48 8d 35 87 0e 00 00 	lea    0xe87(%rip),%rsi        # 2004 <_IO_stdin_used+0x4>
    117d:	e8 de fe ff ff       	call   1060 <strcmp@plt>
    1182:	85 c0                	test   %eax,%eax
    1184:	0f 94 c0             	sete   %al
    1187:	0f b6 c0             	movzbl %al,%eax
    118a:	48 83 c4 08          	add    $0x8,%rsp
    118e:	c3                   	ret    
    118f:	b8 00 00 00 00       	mov    $0x0,%eax
    1194:	c3                   	ret    

GCC 如何优化,除取决于编译选项外,同样取决于程序员编写什么样的源代码,这一点不足为奇。然而,当优化等级升至 O2 时,更为不好的事情发生了:

lyazj@HelloWorld:~$ gcc -Wall -Wshadow -Wextra ub_null.c -o ub_null -O2
ub_null.c: In function ‘is_valid’:
ub_null.c:7:11: warning: comparison is always false due to limited range of data type [-Wtype-limits]
    7 |   if(head == 0x80) return 0;
      |           ^~
In function ‘is_valid’,
    inlined from ‘main’ at ub_null.c:15:3:
ub_null.c:9:10: warning: argument 1 null where non-null expected [-Wnonnull]
    9 |   return strcmp(str, "expected string") == 0;
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ub_null.c:2:
ub_null.c: In function ‘main’:
/usr/include/string.h:156:12: note: in a call to function ‘strcmp’ declared ‘nonnull’
  156 | extern int strcmp (const char *__s1, const char *__s2)
      |            ^~~~~~
lyazj@HelloWorld:~$ ./ub_null
Segmentation fault
lyazj@HelloWorld:~$ objdump --disassemble=is_valid -j.text ub_null

ub_null:     file format elf64-x86-64


Disassembly of section .text:

00000000000011b0 <is_valid>:
    11b0:	f3 0f 1e fa          	endbr64 
    11b4:	48 83 ec 08          	sub    $0x8,%rsp
    11b8:	48 8d 35 45 0e 00 00 	lea    0xe45(%rip),%rsi        # 2004 <_IO_stdin_used+0x4>
    11bf:	e8 9c fe ff ff       	call   1060 <strcmp@plt>
    11c4:	85 c0                	test   %eax,%eax
    11c6:	0f 94 c0             	sete   %al
    11c9:	48 83 c4 08          	add    $0x8,%rsp
    11cd:	0f b6 c0             	movzbl %al,%eax
    11d0:	c3                   	ret    

值得注意的是,现在段错误来自 strcmp() 中的 NULL dereference,且 is_valid() 的反汇编出奇地简单,GCC 同时干掉了两个 if 语句!因为我们首先访问了 str 处的 1 字节,由于 NULL dereference 是典型的 UB,编译器便假定了 str != NULL,这样第二个 if 语句也可以被优化掉!现在,我们产生了具有严重漏洞的 is_valid() 函数,当 str == NULL 时,程序将发生严重错误。

解决 bug 的方法是显然的,即将 head 转换为 unsigned char 再比较,并调换两个 if 语句的顺序。NULL dereference,这是一个曾经让 Linux 内核爆出严重漏洞的 UB,我们刚刚成功复现了这一过程。诚然,此处的程序是异常简单的,看出两个写反的 if 语句非常容易;但对于代码业务特别复杂的场景,特别是对一行代码需要数行注释的底层代码或其它核心代码而言,这个 bug 可能一致遗留下来,并成为一个长期休眠的不定时炸弹。它的出现让许多可能是至关重要的代码段,如第二个 if 语句失效,可能给程序使用者造成难以预计的后果。还好当前版本的 GCC 友好地给出了应有的警告,这也再次向我们证明,随意地忽略编译器给出的 Warning 是不可取的。

Google 等团队开发的 sanitizer 已经集成到了当前版本的 GCC 中,让我们用 sanitizer 更为有效地发现上述未定义行为:

lyazj@HelloWorld:~$ gcc -Wall -Wshadow -Wextra ub_null.c -o ub_null -O2 -fsanitize=undefined -fno-sanitize-recover=all
ub_null.c: In function ‘is_valid’:
ub_null.c:7:11: warning: comparison is always false due to limited range of data type [-Wtype-limits]
    7 |   if(head == 0x80) return 0;
      |           ^~
lyazj@HelloWorld:~$ ./ub_null
ub_null.c:6:8: runtime error: load of null pointer of type 'const char'

看到这里,是不是很轻松就发现了两个 if 语句写反的问题?文章来源地址https://www.toymoban.com/news/detail-603236.html

到了这里,关于【持续更新】C/C++ 踩坑记录(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 工作记录------单元测试(持续更新)

    之前的工作中从来没有写过单元测试,新入职公司要求写单元测试, 个人觉得,作为程序员单元测试还是必须会写的 于此记录一下首次编写单元测试的过程。 在src目录下,创建与main同级的目录。 其中test/java目录下编写测试类 test/resources目录下放置测试所需要的配置文件。

    2024年02月14日
    浏览(24)
  • 爬虫学习记录(持续更新)

    1.使用webdriver报错 AttributeError: \\\'str\\\' object has no attribute \\\'capabilities\\\' 解决:目前使用的selenium版本是4.11.2,可以不必设置driver.exe的路径,selenium可以自己处理浏览器和驱动程序,因此,使用Selenium Webdriver创建对象

    2024年02月13日
    浏览(30)
  • react hook问题记录(持续更新)

    实际使用react hook的时候遇到的一些问题记录下来了,温故而知新。 例子1:界面上有个按钮,点击按钮界面上数值会增加1和2 但是实际的结果是: 点击按钮,界面展示的是 0,2。跟预期需要展示的0,1,2不一样 例子2:点击按钮,执行三次setState,希望加3 但是实际的结果是:

    2024年01月15日
    浏览(24)
  • 软件使用错误(警告)记录(持续更新)

     本博客用以记录在软件使用过程中所遇到的错误和关键性的警告,以及这些警告和错误的解决方法,方便日后查看以及能为其他遇到同样问题的人提供一个可能的解决方法。需要注意的是,此处记录的方法是根据本人遇到的问题记录的,所以在解决自己遇到的问题的时候需

    2023年04月08日
    浏览(39)
  • 微信产品对接问题记录集锦(持续更新)

         1.商户平台中进行关联订阅号操作,显示:当前商户号暂不支持关联该类型的appid      2.微信支付接入前需要的配置信息      3.商户平台中添加JSAPI支付授权目录操作中添加之后没有显示问题      4.基于微信中的H5项目对应的支付方式是哪种,需要哪些配置信息   

    2024年02月09日
    浏览(39)
  • [Elasticsearch] ES更新问题踩坑记录,直面秋招

    基本可以定位是在es更新这块出问题了 看对应代码 final TableDocBean docBean = baseSearchService.getById(id); setValueForBean(afterColumns, docBean); log.info(“update table data to es: {}”, JSON.toJSONString(docBean)); baseSearchService.update(docBean); 代码先通过表id 获取对应ES文档,然后赋值 执行更新数据操作 这块

    2024年03月25日
    浏览(41)
  • 记录工作项目中使用的插件(持续更新中)

    1.HighLightingSystem 用于3D物体高亮显示 在项目中的使用:导入插件后在需要高亮显示的3d物体上附加Highlighter组件,在需要显示高亮效果的摄像机上附加Highlighting Renderer组件。在代码中调整Highlighter属性即可控制物体高亮效果的开关、闪烁。 使用场景:提示玩家点击,或鼠标进入

    2024年02月05日
    浏览(34)
  • SQLServer 常用命令记录,持续更新.....(有问题可以留言)

       在SQL Server中,您可以使用内置的JSON功能来操作JSON数据。SQL Server 2016及更高版本引入了对JSON的原生支持。以下是一些常见的JSON操作: JSON数据的查询: 使用 JSON_VALUE 函数来提取JSON对象中的特定属性值。 使用 JSON_QUERY 函数来提取JSON对象或数组。 使用 JSON_UNQUOTE 函数来删除

    2024年02月09日
    浏览(28)
  • 【配置跑通Omni-Swarm(omni swarm:开源的多机器人协同SLAM算法)持续踩坑排雷更新中。。。】

    旨在记录配置Omni-Swarm过程 Omni-swarm: A Decentralized Omnidirectional Visual-Inertial-UWB State Estimation System for Aerial Swarms Omni-swarm是一种用于空中群体的分布式全向视觉惯性超宽带(visual-inertial-UWB)状态估计系统。为了解决可观测性、复杂初始化、精度不足和缺乏全局一致性等问题,引入

    2024年02月04日
    浏览(32)
  • {工作记录}遇到过的网络攻击合集&&爬虫User-Agent记录..{持续更新}

    奇怪的攻击增加了!Exp!up!up! (最新更新时间:2022年10月31日 更新内容:爬虫UA头) “看不懂是啥攻击,所以记一下。”——harusaruhi 2022年5月1日 2022年5月1日 2022年5月2日 2022年5月3日 2022年5月4日 2022年5月8日 2022年5月21日 2022年5月27日 2022年10月5日 2024年1月22日 User-Agent:Xenu Link

    2023年04月14日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包