【问题标题】:vector.resize function corrupting memory when size is too large当 size 太大时,vector.resize 函数会破坏内存
【发布时间】:2010-12-09 14:05:50
【问题描述】:

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

size_t nLengthRemaining = packet.nLength - (packet.m_pSource->GetPosition() - packet.nDataOffset);

seckey.SecretValues.m_data.resize(nLengthRemaining);

在此代码中,m_data 是std::vector<unsigned char>。由于数据包损坏,nLengthRemaining 太大,因此 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 是否更大。不过这肯定不可靠,因为 nLengthRemaining 仍然小于 nMaxSize,但显然仍然大到足以导致调整大小出现问题(nMaxSize 为 4xxxxxxxxx,nLengthRemaining 为 3xxxxxxxxx)。

另外,我还没有确定 resize 会抛出什么异常。它不是 std::length_error 也不是 std::bad_alloc。它抛出了什么异常对我来说真的不太重要,但我很想知道。

顺便说一句,您知道,这段代码在正常情况下确实可以正常工作。这种数据包损坏的情况是唯一发疯的地方。请帮忙!谢谢。

更新:

@迈克尔。现在,如果数据包大于 5 MB,我将忽略该数据包。我将与其他团队成员讨论验证数据包的可能性(它可能已经存在,我只是不知道)。我开始认为这确实是我们版本的 STL 中的一个错误,它抛出的异常甚至不是 std::exception,这让我感到惊讶。我会尝试从我的主管那里了解我们也在运行哪个版本的 STL(我将如何检查?)。

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

// VectorMaxSize.cpp : 定义控制台应用程序的入口点。 //

#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 开发机器上,它具有与加密项目相同的行为,它会造成各种破坏。当我在我的 Visual Studio 2008 机器上构建并运行它时,resize 将抛出一个 std::bad_alloc 异常并且向量不会被破坏,正如我们所预料的那样!是时候参加一些 EA Sport NCAA 足球了,呵呵!

【问题讨论】:

  • 我很好奇您使用的是什么编译器/stl 版本。如果分配失败,我可以访问的实现不会破坏向量对象。
  • @cchampion:“在我的 VS6 开发机器上……”如果你早点这么说,我们就不会在这上面浪费这么多时间了。那是10多年前的技术!当然,它是越野车。看我的回答。
  • 查看我最近的编辑 - 问题是 VC6 的 &lt;xmemory&gt; 标头中的错误(但不是 Dunkumware VC 错误页面上讨论的错误)。

标签: c++ stl vector resize


【解决方案1】:

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

“Bug”这个词可能太强了。 vector::resize() 是根据 vector::insert() 定义的,标准是这样说的 vector::insert()

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

所以似乎有时resize() 操作被允许破坏向量,但如果该操作是异常安全的(我认为这不会超出预期)图书馆来做到这一点,但也许比我想象的要难)。

您似乎有几个合理的选择:

  • 更改或更新到没有损坏错误的库(您使用的是什么编译器/库版本?)
  • 不要检查 vector::max_size(),而是将 nMaxSize 设置为您自己合理的最大值,然后执行您上面的操作,但改用该阈值。

编辑:

我看到您正在使用 VC6 - vector::resize() 中肯定有一个错误可能与您的问题有关,尽管查看补丁我真的不知道如何(实际上这是 @987654332 中的错误@,但如前所述,resize() 调用 insert())。我想访问Dinkumwares' page for bug fixes to VC6 并应用修复程序是值得的。

该问题也可能与该页面上的&lt;xmemory&gt; 补丁有关 - 目前尚不清楚那里讨论的错误是什么,但vector::insert() 确实调用了_Destroy()vector&lt;&gt; 确实定义了名称@987654339 @所以你可能会遇到这个问题。一件好事 - 您不必担心管理对标头的更改,因为 Microsoft 再也不会接触它们了。只需确保补丁进入版本控制并记录在案即可。

请注意,“Effective STL”中的 Scott Meyers 建议使用 SGI'sSTLPort's 库来获得比 VC6 提供的更好的 STL 支持。我还没有这样做,所以我不确定这些库的效果如何(但我也没有将 VC6 与 STL 一起使用)。当然,如果您可以选择迁移到更新版本的 VC,请务必这样做。


再修改:

感谢测试程序...

VC6 的 _Allocate() 默认分配器实现(在 &lt;xmemory&gt; 中)使用带符号的 int 来指定要分配的元素数量,以及传入的大小是否为负(这显然是你正在做的 - 当然在您的测试程序中)_Allocate() 函数将请求的分配大小强制为零并继续。请注意,零大小的分配请求几乎总是会成功(不是vector 无论如何都会检查失败),因此vector::resize() 函数会愉快地尝试将其内容移动到新块中,这还不够大至少可以说。所以堆被破坏了,它很可能会碰到一个无效的内存页面,不管怎样——你的程序被水洗了。

所以底线是永远不要要求 VC6 一次性分配超过 INT_MAX 的对象。在大多数情况下(VC6 或其他)可能不是一个好主意。

另外,您应该记住,VC6 使用了一个预标准的习惯用法,即在分配失败时从 new 返回 0,而不是抛出 bad_alloc

【讨论】:

  • "如果除了 T 的复制构造函数或赋值运算符之外抛出异常,则没有任何影响" IRTA "就好像 resize() 没有被调用。"而且我相当肯定向量上的任何操作都不应该破坏内存。
  • insert() 操作可能会导致复制/赋值操作(当向量内容被复制到新分配时)——这些操作被允许“产生影响”。例如,它不应该做任何像破坏堆那样糟糕的事情,但目前还不清楚这是否是 OP 发生的事情。在这些条件下的异常允许导致向量发生变化(可能不是向量中的所有元素都变为新元素)。他的代码可能会发现向量不再有意义。无论哪种方式都不是很好的行为,我同意 STL 实现可能会更好地处理这种情况。
  • 你是对的,尽管在 vector&lt;unsigned char&gt; 中复制/分配元素不应该导致任何异常 - 在我看来,这似乎指向一个错误的 STL 实现,它无法处理记忆情况不错。我会对正在使用的平台/编译器/库的详细信息感兴趣。
  • “在这些条件下允许异常导致向量发生变化”如果真的是这样,我会感到惊讶。另外,我看不出如何从您引用的内容中阅读此内容。
  • 我在标准中阅读该行的方式是,如果在 vector::insert() 调用中引发异常,则向量将不会发生任何事情(“无影响”),除非异常来自复制 ctor 或赋值运算符,在这种情况下可能会产生一些(未指定)效果。再说一次,标准文档并不以清晰明了而闻名,所以我可能会偏离基础。
【解决方案2】:

我强烈建议您在使用可能错误的参数调用库函数之前检查您的数据是否损坏!

对您的数据包使用某种哈希码或校验和算法。 您不能依靠图书馆来帮助您,因为它无法做到: 可能是您给它一个损坏但仍然有效(从库的角度来看)的大小,它非常大,因此它分配了例如 768MB 的 RAM。如果系统中有足够的可用内存,这可能会起作用,但如果在您的 1024MB 机器上运行的其他程序消耗过多内存,则可能会失败。

如上所说:先检查!

【讨论】:

  • 我同意。我认为你的问题的根源是你依靠你的加密算法来告诉你它“假装”的大小。您确实需要使用填充(如 MD5 那样)来强制执行块大小,或者有另一种提供大小信息的带外方式。
  • 我同意,我们确实需要一些方法来验证数据包。我会在星期一向这个项目的其他程序员提到这一点。现在我会跳过超过 5 MB 的数据包。
【解决方案3】:

当您说“调整大小已损坏内存”时,我不明白您的意思。你怎么确定?

FWIW,我不同意Michael's answer。如果std::vector&lt;&gt;::resize() 抛出向量扩展,我看到两种可能性:

  1. 用于填充新空间(或复制元素)的构造函数之一抛出或
  2. 用于增长向量的分配器做了
  3. 或预先确定的向量请求的大小太大并抛出。

有了std::vector&lt;unsigned char&gt;,我们可以安全地解除#1,留下#2。如果您不使用任何特殊分配器,则应使用std::allocator,并且AFAIK,它将调用new 来分配内存。而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 实现中的一个错误(如果你问我,除非你使用一个非常旧的,否则不太可能)或者你在其他地方做错了什么。


编辑现在你说你正在使用 VS6...

你早该这么说的。 VC6 是十多年前发布的,当时 MS 因为太长时间没有出现在会议上而在标准委员会中失去了投票权。他们发布的标准库实现来自 Dinkumware(好),但由于法律问题,它是 VC5 的版本(非常糟糕),有很多越来越大的错误,甚至不支持成员模板,即使VC6 编译器支持它。老实说,你对这么老的产品有什么期望?

如果你不能切换到一个像样的 VC 版本(我建议至少 VC7.1 aka VS.NET 2003,因为这是向标准一致性迈出重大飞跃的版本),至少看看 Dinkumware 是否仍然出售他们优秀库的 VC6t 版本。 (实际上,我会感到惊讶,但他们曾经有一个,而你永远不知道......)

至于例外:在早期的 VC 版本中(这包括 VC6,不包括 VC8 aka VS.NET 2005,不过我不确定 VC7.1)默认情况下,访问冲突可能会被 catch(...) 捕获.因此,如果这样的 catch 块捕获了某些东西,您甚至都不知道这是否是 C++ 异常。我的建议是只使用catch(...)throw; 一起使用,以便让该异常继续存在。如果你这样做了,你会在 AV 上遇到真正的崩溃,并且能够在调试器中对它们进行堆栈跟踪。如果您不这样做,则 AV 将被吞并,然后您就会被一个在您不知情的情况下发疯的应用程序所困。但是除了使用 AV'ed 应用程序中止之外,做任何事情都是没有意义的。 AV 是未定义行为的结果之一,之后,所有的赌注都被取消了。

【讨论】:

  • 我说它损坏内存的原因是因为简单的日志语句开始抛出异常,而且一大堆与内存相关的断言不断弹出。调整大小功能后,整个程序失去了理智。如果您通过调整合理长度,则不会发生这种情况。如果我跳过那个数据包也不会发生。我将尝试该代码来确定异常的类型,我不知道 typeid!谢谢。
  • 如果你不知道:你必须#include &lt;typeinfo&gt;
  • 信不信由你, const std::exception& 处理程序没有抓住它。我不知道这个例外是什么。现在我真的开始相信我们正在使用的 STL 版本中存在错误。我将在星期一与程序员负责人讨论这个问题。
  • 要成功捕获访问冲突(也称为 SEH 异常),您需要设置编译器设置 /Eha。 @cchampion:这可以解释为什么你没有捕捉到异常,因为它甚至可能不是 C++ 异常
猜你喜欢
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-05
  • 2021-12-31
  • 1970-01-01
  • 2011-04-21
  • 2014-04-15
相关资源
最近更新 更多