本节书摘来自华章计算机《编写高质量代码:改善c程序代码的125个建议》一书中的第1章,建议2-6,作者:马 伟 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

建议2-6:防止无符号整数回绕

C99第6.2.5节的第9条规定是:涉及无符号操作数的计算永远不会产生溢出,因为无法由最终的无符号整型表示的结果将会根据这种最终类型可以表示的最大值加1执行求模操作。也就是说,如果数值超过无符号整型数据的限定长度时就会发生回绕,即如果无符号整型变量的值超过了无符号整型的上限,就会返回0,然后又从0开始增大;如果无符号整型变量的值低于无符号整型的下限,那么就会到达无符号整型的上限,然后从上限开始减小。这就像一个人绕着跑道跑步一样,绕了一圈,又返回到出发点,因此称为回绕。
为了加深大家对无符号整数运算产生回绕的理解,我们继续来看代码清单1-9所示的一个简单例子。

代码清单1-9 无符号整数运算示例
#include <stdio.h>
int main(void)
{unsigned int a = 4294967295;unsigned int b = 2;unsigned int c=4;printf("%u\n", a + b);printf("%u\n", b -c);return 0;
}

在代码清单1-9中,我们定义了3个无符号整型变量a、b与c。其中将变量a的值初始化为4294967295(即在32位机器上存储为0xffffffff)。当程序执行语句“a+b”时,其结果超出了无符号整型的限定值(UINT_MAX :0xffffffff),于是便产生向下回绕,因此输出的结果为1(即0xffffffff+0x00000002=0x00000001);当程序执行语句“b-c”时,其结果为负数,于是便产生向上回绕,因此返回的结果为4294967294(即0x00000002-0x00000004=0xfffffffe)。具体运行结果如图1-8所示。


从代码清单1-9中可以看出,无符号整数运算产生的回绕会给程序带来严重的后果,尤其是作为数组索引、指针运算、对象的长度或大小、循环计数器与内存分配函数的实参等的时候是绝对不允许产生回绕的。因此,针对无符号整数的运算,应该采用适当的方法来防止产生回绕。例如,代码清单1-10演示了如何简单地处理代码清单1-9中所产生的回绕。

代码清单1-10 代码清单1-9的解决方法
#include <stdio.h>
#include <stdlib.h>
int main(void)
{unsigned int a = 4294967295;unsigned int b = 2;unsigned int c=4;if(UINT_MAX-a<b){/*处理错误条件*/}else{printf("%u\n", a + b);}if(b<c){/*处理错误条件*/}else{printf("%u\n", b -c);}return 0;
}

在上面的代码中,通过一些条件对无符号操作数进行测试,从而避免了无符号操作数运算产生回绕。在实际的编程环境中,无符号整数的回绕很可能会导致缓冲区溢出,甚至导致攻击者可执行任意代码。例如,程序绕过代码中的大小判断部分的边界检测,可以导致缓冲区溢出,只要使用一般的技术就能够利用这个溢出程序。演示示例如代码清单1-11所示。

代码清单1-11 回绕导致的溢出示例
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{      unsigned short s;int i;char buf[100];if(argc < 3){return -1;}i = atoi(argv[1]);s = i;if(s >= 100){           printf("拷贝字节数太大,请退出!\n");         return -1;}printf("s = %d\n", s);memcpy(buf, argv[2], i);buf[i] = '\0';printf("成功拷贝%d个字节\n", i);  printf("buf=%s\n", buf);return 0;
}

在代码清单1-11中,程序需要将argv[2]的内容复制到buf中,并由argv[1]指定复制的字节数。这里需要特别注意的语句是“if(s >= 100)”,利用该语句进行了相对严格的大小检查:如果argv[1]的值大于等于buf数组的大小(100),则不进行复制。
运行代码清单1-11,当我们执行命令“1-11 4 mawei”时,程序运行正常,并成功地复制了字符串“mawe”到buf中,运行结果如图1-9所示。


https://yqfile.alicdn.com/552e104c3e2fcd0932a749a65af2a260b9e41af9.png" >

当我们执行命令“1-11 200 mawei”时,程序同样运行正常,运行结果如图1-10所示。


可当我们执行命令“1-11 65536 mawei”时,程序却意外地绕过了大小检查语句“if(s >= 100)”来执行相关的操作。原因很简单,程序从命令行参数中得到一个整数值并存放在整型变量i中,然后这个值被赋予了unsigned short类型的整数s,由于s在内存中是用16位进行存储的,而16位能够存储的最大十进制数是65535(即unsigned short存储的范围是0~65535),如果这个值不在unsigned short类型的存储范围内(0~65535),就会产生回绕。因此,当我们输入65536时,系统将会转换为0,从而绕过大小检查语句“if(s >= 100)”来执行余下的操作。可是这里我们将buf数组的大小初始化为100,所以在执行语句“memcpy(buf, argv[2], i)”时,程序就会产生异常而导致崩溃。其运行结果如图1-11与图1-12所示。


https://yqfile.alicdn.com/6fc4b345ebeb58a2c51cb73113f507006e541d12.png" >

其实,这类Bug很常见,而且很容易被攻击,这都是由于无符号整数发生回绕导致的。由于存在回绕,当一个有符号整数被解释成一个无符号整数时,它可能变得很大。比如,-1被当成无符号数时将会是十进制的4294967295,它是32位整数的最大值。如果我们加入的这个值被用作memcpy的参数,memcpy就会试图复制4GB数据,很明显这可能导致错误或破坏堆栈。
除此之外,无符号整数的回绕最可能被利用的情况之一就是利用计算结果来决定将要分配的缓冲区的大小。通常情况下,在程序需要为一组对象分配内存空间时,会将对象的个数乘以单个对象大小,然后用所乘结果来作为参数,从而调用malloc()或calloc()函数来分配内存。这时候,只要我们能够控制对象的个数或单个对象的大小,就有可能让程序分配错误大小的缓冲区。演示示例如代码清单1-12所示。

代码清单1-12 回绕导致的错误分配缓冲区示例
#include <stdio.h>
#include <stdlib.h>
int* copyarray(int *arr, int len);
int main(int argc, char *argv[])
{      int arr[] = {1,2,3,4,5};          copyarray(arr,atoi(argv[1]));      return 0;
}
int* copyarray(int *arr, int len)
{       int i=0;int *newarray = (int *)malloc(len*sizeof(int));          if(newarray == NULL)  {         /*处理newarray == NULL的情况*/      }  printf("为newarray成功分配%d字节内存\n",len*sizeof(int));printf("循环运行次数:%d(0x%x)\n",len,len);  for(i = 0; i < len; i++)  {         newarray[i] = arr[i];   } return newarray;
}

在代码清单1-12中,函数“int copyarray(int arr, int len)”需要将arr的内容复制到newarray中,对象的个数由len参数来指定。其中,程序使用了对象的个数乘以单个对象大小的乘积来作为malloc() 函数的参数,从而对newarray进行内存分配,即内参分配语句为“int newarray = (int )malloc(len*sizeof(int))”。
运行代码清单1-12,当我们执行命令“1-12 8”时,程序运行正常,并成功地为newarray分配了内存,并将arr的内容复制到newarray中,运行结果如图1-13所示。


https://yqfile.alicdn.com/62bb161a470a7e6efd9e701647595c6cb1f7e897.png" >

这样看来,程序貌似没有任何问题。但是当我们执行命令“1-12 1073741824”时,问题就出现了,抛出异常“Unhandled exception at 0x004010d2 in 1-12.exe: 0xC0000005: Access violation writing location 0x00387000。”。运行结果如图1-14所示。


https://yqfile.alicdn.com/ef6c130061868559815734e313890bde3cbae5f7.png" >

是什么原因导致这样的结果呢?
其实很简单,是因为函数“int copyarray(int arr, int len)”没有检查参数len而导致运算回绕失败。在通过语句“int newarray =(int ) malloc(lensizeof(int))”给newarray分配内存时,这里将参数设置为1073741824(十六进制是0x40000000),而“sizeof(int)”的返回结果为4(十六进制是0x4)。当运算表达式“0x400000000x4”时,就发生了无符号整数运算回绕,所得的结果为0x0(即0x40000000*0x4=0x0)。因此,为newarray分配的内存为0。
除此之外,在通过语句“int newarray =(int ) malloc(len*sizeof(int))”给newarray分配内存时,由于参数len 的原因而造成运算回绕,所以我们可以利用它来分配一个任意长度的缓冲区。如上面将len参数设置为1073741824,就可能出现在没有为newarray分配内存的情况下,却向其中复制了数组元素,而且循环的次数还非常多,严重时会造成系统崩溃。当然,你还可以通过选择合适的值赋给len参数以使得循环反复执行导致缓冲区溢出。同时,还可以通过覆盖malloc的控制结构来执行任意恶意代码,从而实施对堆溢出的攻击。
在本节的最后,还需要说明的是,并不是每种运算符号都会令无符号操作数运算产生回绕,表1-5给出了可能会导致回绕的操作符。


https://yqfile.alicdn.com/90187ea6f2e335797307546fc85eb1dc001613c5.png" >

《编写高质量代码:改善c程序代码的125个建议》——建议2-6:防止无符号整数回绕...相关推荐

  1. 读《编写高质量iOS与OS X代码的52个有效方法》

    又看了一遍<编写高质量iOS与OS X代码的52个有效方法>这本书,做一个简单的总结,其中runtime和GCD那些的不是太详细,要想很详细估计写的东西比篇文字都多,但恰巧又是iOS的重点 ...

  2. 如何编写高质量和可维护的代码

     如何编写高质量和可维护的代码 我们怎么做才能既不需要写很多注释,又能保证代码易于理解呢? 其中一个主要的方法就是让代码自文档化.其优势在于,既不用写注释,又能使得代码易于维护. 下面就是三种使得 ...

  3. [读书笔记]读《Effective Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法》(一)...

    第一条:了解Objective-C 语言的起源 Objective-C为C语言添加了面向对象特性,是其超集.Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型.接收一条消 ...

  4. 《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》笔记

    2019独角兽企业重金招聘Python工程师标准>>> 这本书很早有了解过,评价都不错,但最近才终于把这本书看完,整本书介绍了很多个提高Objective-C的方法,都是平时用得很多 ...

  5. 【Python】使用31条规则编写高质量且美丽的Python代码

    Raymond Hettinger在pycon US 2013 视频,幻灯片上的讲话. 代码示例和直接引用都来自Raymond的演讲.我在这里复制它们是为了我自己的启发和希望别人会发现它们像我一样方便 ...

  6. Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法笔记-协议与分类...

    23.通过委托与数据源协议进行对象间通信 如果要在委托对象上调用可选方法,那么必须提前使用类型信息查询方法判断这个委托对象能否响应相关选择子. if ( [_delegate respondsToSe ...

  7. 编写高质量代码改善C#程序的157个建议——建议148:不重复代码

    建议148:不重复代码 如果发现重复的代码,则意味着我们需要整顿一下,在继续前进. 重复的代码让我们的软件行为不一致.举例来说,如果存在两处相同的加密代码.结果在某一天,我们发现加密代码有个小Bug, ...

  8. 编写高质量代码改善C#程序的157个建议——建议86:Parallel中的异常处理

    建议86:Parallel中的异常处理 建议85阐述了如何处理Task中的异常.由于Task的Start方法是异步启动的,所以我们需要额外的技术来完成异常处理.Parallel相对来说就要简单很多,因 ...

  9. 《编写高质量代码:改善c程序代码的125个建议》——建议3-5:避免使用浮点数作为循环计数器...

    本节书摘来自华章计算机<编写高质量代码:改善c程序代码的125个建议>一书中的第1章,建议3-5,作者:马 伟 更多章节内容可以访问云栖社区"华章计算机"公众号查看. ...

最新文章

  1. 某大厂程序员吐槽:老家亲戚狮子大开口,竟跟自己借八十万给儿子买房!
  2. 关于召开全国大学生智能车竞赛--航天智慧物流项目
  3. Bochs调试及相关仿真工具的使用方法
  4. python拆堆和堆叠的操作_python - 如何合并不同的DFS并堆叠值? - 堆栈内存溢出
  5. 构建高并发高可用安全的IT系统-高并发部分
  6. Win XP2实用的修复工具
  7. application实现网页计数_手把手教你利用爬虫爬网页(Python代码)
  8. Codeforces Round #284 (Div. 2): D. Name That Tune(概率DP)
  9. python zip函数_Python zip()函数
  10. 【Java】 环境变量如何配置?
  11. Matlab-基于模型不确定补偿的RBF网络机器人自适应控制仿真
  12. python比对excel表数据中的差异_Python比对EXCEL数据
  13. PS2汉化2 - 自制程序的运行与调试
  14. 输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。
  15. arduino数字端口输出电压可驱动多大继电器呢_「白皮书」数字信号与脉冲序列调理...
  16. 软考-系统架构设计师(软件架构风格)
  17. 设计模式之观察者模式——猫抓老鼠1
  18. 【每日新闻】2019年大数据10大发展趋势出炉!
  19. 火狐扩展(Firefox Extension)FillForm开发手记(更新中)
  20. “悟空”来也!未上市就签几亿订单,看优必选机器人梦想的超级路径

热门文章

  1. 上传ML模型,一键可视化,终于能看懂神经网络到底在干啥了
  2. iPhone拍人像,人头直接不见了,什么情况?
  3. 你的4nm安卓旗舰芯片来了!骁龙8 Gen 1:支持8K HDR,功耗降30%,雷军:小米12首发...
  4. 三手火箭载二手飞船送四名平民上太空,马斯克负责创造历史,另一位富豪出钱买单...
  5. 谷歌最新黑科技:裸眼3D视频通话,宛如真人面对面!Jeff Dean:魔镜啊魔镜
  6. 柔宇冲刺科创板IPO:3年营收5亿净亏31亿,乐视掘墓人刘姝威坐镇董事会
  7. 讲座预告 | 全年最值得看的英仙座流星雨要来 如何一晚看上百流星
  8. Zoom重金并购25人安全初创公司,市值一夜大涨23亿美元
  9. 移动版“全功能”Photoshop发布!还有AI剪视频一键传抖音、一键抠图功能上线 | Adobe MAX 2019...
  10. 百度e-staff王路离职投身VC,曾负责市场公关,今年主导智慧城市