首页 > 编程语言 >C/C++整形变量溢出问题

C/C++整形变量溢出问题

时间:2024-03-26 22:25:54浏览次数:25  
标签:int MAX unsigned len C++ 整形 溢出 整型

参考

C语言的整型溢出问题 | 酷 壳 - CoolShell

 

 


 

概述

整形溢出分为无符号(unsigned)整型溢出和有符号(signed)整型溢出

无符号整型溢出

对于unsigned整型溢出,C的规范是有定义的——“溢出后的数会以2 ^ ( 8 * sizeof ( type ) )作模运算”

比如,两个unsigned int类型求和溢出

unsigned int a = 4294967295;  // 最大值,2^32-1
unsigned int b = 2;
unsigned int sum = a + b;  // (2^32 - 1 + 2) % (2^(8*4))
std::cout << "Sum: " << sum << std::endl;  // 输出结果会是 1,发生了溢出

 

有符号整形溢出

对于signed整型的溢出,C的规范定义是“undefined behavior”

也就是说,有符号整型的溢出问题取决于编译器

 

 

整型溢出的危害

示例一:整型溢出导致死循环

int len = 0;

while(len< MAX_LEN) {
    len += readFromInput(fd, buf);         // <---- [1]
    buf += len; 
}

  

在这段代码的[1]处,存在整型溢出的风险。具体来说,当 len 的值接近 MAX_LEN 时,如果 readFromInput 函数返回一个超出 int 类型表示范围的正值,那么累加操作 len += readFromInput(fd, buf) 可能会导致整型溢出。这种情况下,len 可能无法正确地表示实际读取的数据长度,从而导致程序行为出现异常,比如len溢出变为负数,不断地死循环。

解决方案

int len = 0;
while(len < MAX_LEN) {
    int bytesRead = readFromInput(fd, buf);
    if (bytesRead <= 0) {
        break;  // 读取出错或者已经读取到末尾,退出循环
    }
    if (len > MAX_LEN - byteRead) {
        bytesRead = MAX_LEN - len;  // 限制本次读取的字节数,以免超过 MAX_LEN
    }
    len += bytesRead;
    buf += bytesRead;
}

对 readFromInput 函数的返回值进行了检查。这样可以确保在累加 len 的过程中不会发生整型溢出,并且在读取失败的情况下可以正确地终止循环。

 

示例二:整型转型时的溢出

int copy_something(char *buf, int len)
{
    #define MAX_LEN 256
    char mybuf[MAX_LEN];
     ... ...
     ... ...
     if(len > MAX_LEN){ // <---- [1]
         return -1;
     }
     return memcpy(mybuf, buf, len);
}

[1]处的if语句存在整型溢出的问题,具体来说,len是个signed int,而memcpy则需一个size_t的len,也就是一个unsigned 类型。于是,len会被类型转换为unsigned,此时,如果我们给len传一个负数,会通过了if的检查,但在memcpy里会被提升为一个正数,于是我们的mybuf就是overflow了。这个会导致mybuf缓冲区后面的数据被重写。

 

解决方案

unsigned int copy_something(char *buf, unsigned int len)
{
    #define MAX_LEN 256u  // 将 MAX_LEN 修改为无符号整型常量
    char mybuf[MAX_LEN];
    if(len > MAX_LEN){
        return -1;  // 应该返回错误值,确保逻辑正确
    }
    memcpy(mybuf, buf, len);  // 这里应该直接调用 memcpy 函数进行内存拷贝
    return len;
}

 涉及到内存大小的变量,一般设置成unsigned int或者size_t类型,避免溢出问题

 

示例三: 分配内存

关于整数溢出导致堆溢出的很典型的例子是,OpenSSH Challenge-Response SKEY/BSD_AUTH 远程缓冲区溢出漏洞。

下面这段有问题的代码摘自OpenSSH的代码中的auth2-chall.c中的input_userauth_info_response() 函数:

size_t nresp = packet_get_int();
if (nresp > 0) {
    response = xmalloc(nresp*sizeof(char*));     // <---- [1]
  for (i = 0; i < nresp; i++) 
    response[i] = packet_get_string(NULL);
}

 

这里以32位系统为例

nresp是size_t类型,大小为0 ~ 2^32-1,(size_t在32位系统上定义为 unsigned int,也就是32位无符号整型。在64位系统上定义为 unsigned long ,也就是64位无符号整形)

sizeof(char*) == 4,因为32位系统上,指针占四个字节

这个示例是一个解数据包的示例
packet_get_int()通常会返回一个包的长度len
假如说“精心设计”一个len,比如0x40000001,(unsigned int 最大值是0xffffffff,除以4就是0x40000000,再加1就是”精心设计“的len)
然后nresp读到这个0x40000001后 , 通过计算 nresp*sizeof(char*) , 0x40000001 * 4 , 结果是0x100000004, 就会溢出,结果取模(无符号整数溢出取模),得到最终的结果4
因此malloc申请的内存大小的大小就是4
但是此时nresp的大小为1073741825(对应十六进制0x40000001)
因此for循环会循环nresp次,此前申请的4字节空间很快就会被覆盖,后面的循环中很可能会被恶意利用

当后续的数据被拷贝到这个过小的内存空间时,就会导致堆缓冲区溢出,覆盖了原本的内存内容,包括后面的数据和代码。

这种类型的漏洞可能会被恶意攻击者利用来执行任意的恶意代码,导致拒绝服务、远程代码执行,或者其他潜在的安全问题。

 

示例四:缓冲区溢出导致安全问题

int func(char *buf1, unsigned int len1,
         char *buf2, unsigned int len2 )
{
   char mybuf[256]; 

   if((len1 + len2) > 256){    //<--- [1]
       return -1;
   } 

   memcpy(mybuf, buf1, len1);
   memcpy(mybuf + len1, buf2, len2); 

   do_some_stuff(mybuf); 

   return 0;
}

在注释标记为 [1] 的位置,程序会检查 len1 和 len2 的总和是否超过了 mybuf 的大小。如果总和超过 256,则会返回 -1,这可以防止 mybuf 超出其分配的内存范围。

然而,这段代码中并没有考虑到 len1 和 len2 是无符号整数,因此它们不会被检查是否小于0,同时也没有检查它们是否大于或等于无符号整数的最大值。如果 len1 和 len2 非常大,甚至超过了无符号整数的最大值,那么 (len1 + len2) 的结果会小于256(无符号整数溢出),但仍可能导致缓冲区溢出。

(注:通常来说,在这种情况下,如果你开启-O代码优化选项,那个if语句块就全部被和谐掉了——被编译器给删除了)

 

示例五:size_t的溢出

for (int i= strlen(s)-1;  i>=0; i--)  { ... }
for (int i=v.size()-1; i>=0; i--)  { ... }

上面这两个示例是经常用的从尾部遍历一个数组的for循环。

第一个是字符串,第二个是C++中的vector容器。

strlen()和vector::size()返回的都是 size_t,size_t在32位系统下就是一个unsigned int。

如果strlen(s)和v.size() 都是0的情况,于是strlen(s) – 1 和 v.size() – 1 都不会成为 -1,而是成为了 (unsigned int)(-1),一个正的最大数。导致程序越界访问。

 

标签:int,MAX,unsigned,len,C++,整形,溢出,整型
From: https://www.cnblogs.com/zangwhe/p/18097757

相关文章

  • C++ map踩坑
    目录!!!不要直接使用[]来操作map,否则会有意想不到的错误。尽量使用map提供的函数(count、find、insert、erase)来操作map242.有效的字母异位词描述:给定两个字符串s和t,编写一个函数来判断t是否是s的字母异位词。注意:若s和t中每个字符出现的次数都相同,则称s和t......
  • C++ 用户输入与数据类型详解:建立基本计算器及变量类型
    C++用户输入你已经学习了cout用于输出(打印)值。现在我们将使用cin来获取用户输入。cin是一个预定义变量,它使用提取运算符(>>)从键盘读取数据。在下面的示例中,用户可以输入一个数字,该数字存储在变量x中。然后我们打印x的值:示例intx;cout<<"Typeanumber:";//......
  • C++ 用户输入与数据类型详解:建立基本计算器及变量类型
    C++用户输入你已经学习了cout用于输出(打印)值。现在我们将使用cin来获取用户输入。cin是一个预定义变量,它使用提取运算符(>>)从键盘读取数据。在下面的示例中,用户可以输入一个数字,该数字存储在变量x中。然后我们打印x的值:示例intx;cout<<"Typeanumber:"......
  • C++文件类(文件流类)及用法详解
    open()fstreamifstreamofstream打开指定文件,使其与文件流对象相关联。is_open()检查指定文件是否已打开。close()关闭文件,切断和文件流对象的关联。swap()交换2个文件流对象。operator>>fstreamifstream重载>>运算符,用于从指定文件中读取数据。gcoun......
  • 蓝桥杯 试题 基础练习 十进制转十六进制 C++
    问题描述十六进制数是在程序设计时经常要使用到的一种整数的表示方式。它有0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F共16个符号,分别表示十进制数的0至15。十六进制的计数方法是满16进1,所以十进制数16在十六进制中是10,而十进制的17在十六进制中是11,以此类推,十进制的30在十六进制......
  • C++的format函数
    C++20引入了<format>库,它提供了类似于Python的格式化字符串的功能。你可以使用std::format函数来格式化字符串。format   格式控制标记  [[fill]align][sign][#][0][wigth][precision][type]下面依次讲解format的格式控制标记的基本用法:1.[[fill]align]这个格式......
  • CMakeLists_find_package以及C++基本语法
    命令catkin_make等效于以下指令:cd~/catkin_wscdsrccatkin_init_workspacecd..mkdirbuildcdbuildcmake../src-DCMAKE_INSTALL_PREFIX=../install-DCATKIN_DEVEL_PREFIX=../devel命令cmake与makeinstall-DCMAKE_BUILD_TYPE=:releasedebug-DCMAK......
  • 一个C/C++出身的程序员,如果想成为一个黑客,需要多久时间?
    黑客,在没有学习编程的人眼中,是无所不能的存在,盗密码,黑网站,网络入侵,偷取数据等,一台电脑,全部搞定!而且很多同学的话开始学习编程的原因就是被黑客的这个技术所吸引的。说起来,好像笔者当年也觉得黑客很厉害,所以的话多多少少我成为C/C++工程师也是有以前的因素的影响,不过后来接触......
  • ccf-csp-2020-12-2期末预测之最佳阈值(c++满分题解)
    这个题暴力是可以有70分的,下面是暴力代码:(注释写的比较清楚了,也很好理解)#include<iostream>#include<vector>#include<set>#include<algorithm>usingnamespacestd;boolsort1(pair<int,int>vec1,pair<int,int>vec2)//对阈值从小到大排序{ returnvec1.first<=ve......
  • CUTLASS: Fast Linear Algebra in CUDA C++
    https://developer.nvidia.com/blog/cutlass-linear-algebra-cuda/EfficientMatrixMultiplicationonGPUs计算密集度=(时间复杂度/空间复杂度)=O(N^3)/O(N^2)=O(N)//naivefor(inti=0;i<M;++i)for(intj=0;j<N;++j)for(intk=0;k<......