代码之家  ›  专栏  ›  技术社区  ›  cchampion

vector.resize函数在大小过大时损坏内存

  •  6
  • cchampion  · 技术社区  · 15 年前

    发生的事情是我正在读取加密数据包,我遇到了一个损坏的数据包,它返回了一个非常大的长度随机数。

    size_t nLengthRemaining = packet.nLength - (packet.m_pSource->GetPosition() - packet.nDataOffset);
    
    seckey.SecretValues.m_data.resize(nLengthRemaining);
    

    std::vector<unsigned char> . 由于数据包损坏,NLENGTHLINING太大,因此resize函数抛出。问题不在于resize抛出(我们处理异常),而是resize已经损坏了内存,这会导致以后出现更多异常。

    std::vector<unsigned char>::size_type nMaxSize = seckey.SecretValues.m_data.max_size();
    if(seckey.SecretValues.m_data.size() + nLengthRemaining >=  nMaxSize) {
        throw IHPGP::PgpException("corrupted packet: length too big.");
    }
    seckey.SecretValues.m_data.resize(nLengthRemaining);
    

    这段代码使用std::vector max_size成员函数来测试nLengthRemaining是否更大。但这一定不可靠,因为nLengthRestaining仍然小于nMaxSize,但显然仍然足够大,足以导致resize出现问题(nMaxSize为4xxxxxxxx,nLengthRestaining为3xxxxxxxx)。

    另外,我还没有确定要抛出什么异常。这不是std::length\u错误,也不是std::bad\u alloc。它抛出的异常对我来说真的不太重要,但我很想知道。

    顺便说一句,正如你所知,这段代码在正常情况下工作正常。这种数据包损坏的情况是唯一让它发疯的地方。请帮忙!谢谢

    更新:

    另一更新: 我只是证明这是我在VisualStudio6开发机器上使用的STL版本中的一个bug。我编写了这个示例应用程序:

    #include "stdafx.h"
    #include <vector>
    #include <iostream>
    #include <math.h>
    #include <typeinfo>
    
    typedef std::vector<unsigned char> vector_unsigned_char;
    
    void fill(vector_unsigned_char& v) {
        for (int i=0; i<100; i++) v.push_back(i);
    }
    
    
    void oput(vector_unsigned_char& v) {
        std::cout << "size: " << v.size() << std::endl;
        std::cout << "capacity: " << v.capacity() << std::endl;
        std::cout << "max_size: " << v.max_size() << std::endl << std::endl;
    }
    
    void main(int argc, char* argv[]) {
        {
            vector_unsigned_char v;
    
            fill(v);
    
            try{
                v.resize(static_cast<size_t>(3555555555));
            }catch(std::bad_alloc&) {
                std::cout << "caught bad alloc exception" << std::endl;
            }catch(const std::exception& x) {
                std::cerr << typeid(x).name() << std::endl;
            }catch(...) {
                std::cerr << "unknown exception" << std::endl;
            }
    
            oput(v);    
            v.reserve(500);
            oput(v);
            v.resize(500);
            oput(v);
        }
    
        std::cout << "done" << std::endl;
    }
    

    在我的VS6开发机器上,它的行为与加密项目相同,它会造成各种破坏。当我在我的VisualStudio2008机器上构建并运行它时,resize将抛出一个std::bad_alloc异常,并且向量不会被破坏,正如我们预期的那样!是时候去看EA运动了,NCAA足球,呵呵!

    3 回复  |  直到 15 年前
        1
  •  7
  •   Michael Burr    15 年前

    我认为 vector::max_size() 它几乎总是一个“硬编码”的东西——它与系统/库准备动态分配多少内存无关。您的问题似乎是向量实现中的一个bug,当分配失败时,它会破坏东西。

    “Bug”这个词可能太重了。 vector::resize() 定义为 vector::insert() 标准是这样说的 向量::插入() :

    如果异常不是由T的复制构造函数或赋值运算符引发的,则不会产生任何影响

    因此,似乎有些时候 resize() 允许操作损坏向量,但如果操作是异常安全的,那就更好了(我认为期望库这样做不会有失偏颇,但可能比我想象的要难)。

    • 更改或更新没有损坏错误的库(您使用的编译器/库版本是什么?)
    • 而不是对照 向量::最大大小() 设置 nMaxSize 达到你自己合理的最大值,做你上面所做的,但使用该阈值代替。

    编辑:

    向量::调整大小() 这可能与您的问题有关,尽管查看补丁我真的不知道如何(实际上这是一个bug) 向量::插入() ,但如前所述, 调整大小() insert() )1.我想值得一游 Dinkumwares' page for bug fixes to VC6 并应用修复程序。

    <xmemory> 该页面上的补丁-不清楚这里讨论的bug是什么,但是 向量::插入() 打电话吗 _Destroy() vector<> 是否定义了名称 _Ty 所以你可能遇到了这个问题。一件好事——你不必担心管理对标题的更改,因为微软再也不会碰它们了。只需确保补丁进入版本控制并得到记录。

    SGI's STLPort's 库以获得比VC6更好的STL支持。我还没有这样做,所以我不确定这些库的工作情况如何(但我也没有在STL中使用VC6)。当然,如果你有选择转移到一个更新版本的VC,一定要做。


    再编辑一次:

    谢谢你的测试计划。。。

    VC6的 _Allocate() 默认分配器的实现(在 < )使用带符号的int指定要分配的元素数,如果传入的大小为负数(这显然是您正在执行的操作,当然在测试程序中也是如此),则 _分配() 函数强制请求的分配大小为零并继续。请注意,零大小的分配请求几乎总是成功的(并非如此) vector 检查是否存在故障),因此 函数愉快地尝试将其内容移动到新的块中,而新的块还不够大。因此,堆被破坏,它很可能会命中一个无效的内存页,而且不管怎样,您的程序被冲洗掉了。

    INT_MAX 一次完成对象。在大多数情况下可能不是一个好主意(VC6或其他)。

    另外,您应该记住,VC6使用了一种预先标准的习惯用法,即从 new 当分配失败而不是抛出 bad_alloc .

        2
  •  5
  •   mmmmmmmm    15 年前

    您不能依赖库来帮助您,因为它无法:

    如上所述:先检查!

        3
  •  4
  •   Community CDub    7 年前

    Michael's answer . 如果 std::vector<>::resize() 在矢量展开中,我看到两种可能性:

    1. 或者,向量事先确定请求的大小过大并抛出。

    std::vector<unsigned char> 我们可以放心地取消1号,剩下2号。如果您不使用任何特殊的分配器,那么 std::allocator 应该使用,而且,好的,那会调用 new 分配内存。和 刚出现的 std::bad_alloc . 然而,你说你抓不到这个,所以我不知道发生了什么。

    不管它是什么,它都应该来源于 std::exception ,因此您可以这样做来了解:

    try {
      my_vec.resize( static_cast<std::size_t>(-1) );
    } catch(const std::exception& x) {
      std::cerr << typeid(x).name() << '\n';
    }
    

    结果如何?

    无论如何,不管是什么,我相当肯定它不会损坏内存。这可能是std-lib实现中的一个bug(如果你问我,这不太可能,除非你使用的是非常旧的一个),或者你在其他地方做了一些错误的事情。


    编辑 既然你说你用的是VS6。。。

    你应该早点说。VC6是十多年前发布的,当时MS在std委员会失去了投票权,因为他们在会议上出现的时间太长了。他们提供的std-lib实现来自Dinkumware(好),但由于法律问题,它是针对VC5的实现(非常糟糕),它有很多更小更大的bug,甚至不支持成员模板,尽管VC6编译器支持它。老实说,你对这种老产品有什么期待?

    Dinkumware 仍然出售他们优秀库的VC6t版本。(事实上,我会很惊讶,但他们过去有一个,你永远不知道…)

    至于例外情况:在早期的VC版本中(包括VC6,不包括VC8,即VS.NET2005,但我不确定VC7.1),默认情况下,访问冲突可能会被 catch(...) . 因此,如果这样的catch块捕获了一些东西,您就不知道这是否是C++异常。我的建议是只使用 捕获(…) 混淆 throw; 为了让这个例外继续下去。如果你这样做了,你会在AV上得到一个真正的崩溃,并且能够在调试器中堆栈跟踪它们。如果您不这样做,AV将被吞没,然后您将被困在一个应用程序中,该应用程序在您不知情的情况下变得疯狂。但是,除了使用AV’ed应用程序中止之外,做任何事情都没有意义。AV是一种 在那之后,所有的赌注都输光了。