【问题标题】:Sanity check of array parameters (strlen and that like)数组参数的完整性检查(strlen 等)
【发布时间】:2010-12-08 09:13:11
【问题描述】:

无法通过搜索找到答案(可能是错误的关键字),所以我正在创建一个新问题。

您如何处理带有字符串参数的 dllexported 方法的参数检查。一般规则是永远不要信任用户,但实际上呢?例如:

int foo(const char *bar)
{
    if(!bar)
        return FAIL;

    ???
}

假设库的用户像这样调用我们的函数:

foo(reinterpret_cast<char*>(0x00000008));

这应该首先导致 AV:

strlen(bar);

有没有办法防止这种情况发生?处理错误的正确方法?

我知道 IsBadReadPtr 是不可能的,因为这个函数属于危险类并且永远不会被使用。但是有没有办法我应该并且可以处理这个问题?我不能 __declpec(dllexport) std::string,可以吗?此外,即使我愿意,据我所知,std::string 具有某种线程本地存储或静态,当从不同模块使用时会导致访问冲突(由静态或不同的堆引起?)。

使用这些函数是否存在安全风险、堆栈溢出 (R/E)IP 覆盖,还是只会导致安全的 AV?

【问题讨论】:

  • 我看不出std::string 会如何防范此类事情。您仍然可以将无效指针传递给其构造函数。
  • @visitor 用户会在“他们的”代码中得到错误。

标签: c++ c windows dll


【解决方案1】:

到目前为止,您只能照看客户。如果他们将垃圾传递给您的函数,是您的问题吗?

【讨论】:

    【解决方案2】:

    “永远不要相信用户”是一种说法,适用于不同的上下文 - 当“用户”是运行客户端程序并将数据发送到您正在编写的服务器程序的人时。

    当“用户”是将您的代码用作库的人时,它应该更接近于:“始终编写代码,使任何出错的地方都归咎于用户;如果出现问题,则责备用户错误的”。 :)

    【讨论】:

      【解决方案3】:

      您的代码的责任是它自己的。客户有责任对他们将要输入的数据进行错误检查。此外,您几乎无法确定有效指针的有效性。它是否指向非空?那你能查到的就差不多了。

      如果您担心安全性,最好的办法是捕获和处理/抛出异常和/或返回某种类型的错误通知。指针本身对它们有一定程度的危险。静态类型不会使您免于无效指针和访问冲突。

      【讨论】:

        【解决方案4】:

        您可能希望更改函数签名以包含字符串的长度。如果您从字符串中读取,则意味着如果字符串未正确以 NULL 终止,您的程序将不会继续读取字符串之外的内容。如果您从函数写入到字符串,那么您必须将缓冲区的长度作为参数,并且您必须检查您写入的数据不会超过缓冲区的容量。

        Wallyk 的反应很好记。如果您的用户将垃圾传递到您的函数中,那么您可以做的只有这么多才能优雅地失败。

        【讨论】:

          【解决方案5】:

          您可能可以使用操作系统函数来测试地址是否映射到实际 RAM 地址。但是这个解决方案只解决了你的问题的一半。因为即使您知道内存地址是有效地址,您仍然不知道用户是否将现有但错误的地址传递给您。

          他们可以简单地通过, foo((char*)&very_important_variable);

          那你能做什么呢?我猜你真的什么都做不了。

          【讨论】:

            【解决方案6】:

            DLL 是您仅在工作场所使用的还是您在外部发布的?如果它是一个你只在内部使用的 std::string 现在它通常是安全的,因为你的所有代码都将被构建并链接到同一个运行时库。否则 std::string 的危险在于打破“单一定义规则”。与 boost::shared_ptr 等相同。但我们希望能够在我们的 C++ 库中使用它们。

            通常防御非法 NULL 指针的最佳选择是断言,但如果您正在交付并且您的客户不会获得无用的调试版本。

            【讨论】:

              【解决方案7】:

              上面的帖子已经说了大部分。您基本上无法完全保护自己免受客户端代码的影响。客户端可以只使用 OpenProcessMemory 并弄乱您所有的内部数据结构。

              出于调试目的,您绝对可以使用 IsBadReadPoiner。这里唯一需要注意的是,一旦触发保护页面,您应该关闭(如果可能的话,优雅地)您的应用程序并重新开始。我喜欢做的一件事是在调试版本中放置一个又大又丑的消息框,如果 IsBadReadPoiner 被击中,它就会弹出。

              【讨论】:

                【解决方案8】:

                我将反对这里流行的方法,说 a) 库应该是健壮的(在合理的范围内)防止客户的错误,b) 当程序在你的代码中出现 AV 崩溃时,你看起来很糟糕,甚至尽管您可以指向手册中的一行并说“告诉你!”。这是一种政治家,而不是软件工程的态度……

                不是 Windows 程序员,我无法给出具体建议,但似乎这里至少有一个返回码或异常是合适的。

                【讨论】:

                  【解决方案9】:

                  没有通用的方法来防止这种情况; C/C++ 没有通用的指针验证工具。

                  正如您所展示的,您可以屏蔽 NULL 指针,但不能(轻松地)屏蔽无效指针:

                  template <class T> bool isValid(T* ptr) { return ptr != NULL; }
                  class X {
                      int someVal;
                      int getSomeVal(void) const { return isValid<X>(this) ? someVal : -EINVAL; }
                  };
                  
                  // the following will set 'j' to -EINVAL
                  X* nullX = (X*)NULL;
                  int j = nullX->getSomeVal();
                  
                  // the following will make getSomeVal() crash:
                  X* invalptr = reinterpret_cast<X*>(0xdefeca7ed);
                  int i = invalptr->getSomeVal();
                  

                  一些编译器/库/堆分配器类(可选)将所有堆内存初始化为零,因此在此类平台上,“未初始化”堆包含零 - 在此类平台上,上述内容对于防止使用未初始化的堆很有用。但这会降低性能,因此不受 C / C++ 标准的强制要求。而且它对未初始化的临时对象没有帮助(它们是在堆栈上而不是在堆上创建的,因此不能由堆分配器填充零)。

                  为了防止任何杂散指针,您必须实现自己的分配器,并提供挂钩调用(上面isValid() 的更详细版本)允许对象实例查询分配器状态关于“嘿,分配器有您在“?”之前看到了我的this,并让所有基于堆栈的对象的类构造函数(取消)在创建/删除时使用“有效性跟踪器”注册对象实例地址。每次调用任何对象方法时,搜索堆分配器映射都是一种非常重量级的方法。我在生产实践中没有看到过这种情况,尽管一些调试工具(想到 valgrind)具有分配器跟踪和一些有限的杂散检测。

                  如果作为图书馆的用户,你非常想作弊,是什么阻止了你:

                  X* objX = new X();
                  Y* objY = reinterpret_cast<Y*>(objX);
                  Y->someMethodThatAccessesThingsWayOutsideTheSizeOf_X();
                  

                  即使该方法会使用分配器验证this,它也会发现它没问题/已知,但你如何判断它“真的是一个 Y”?

                  一般来说,图书馆的弹性在很大程度上取决于用户。多少保姆是太多了?

                  【讨论】:

                  • 0xdefeca7ed 可以是 Windows 中的有效指针,带有 LARGEADDRESSAWARE 标志。此外,isValid(this) 测试可以优化,因为根据定义,this 永远不会为 NULL。您需要关闭该部分的优化器。为什么它甚至是一个模板?所有指针都转换为void*
                  • 当您声称 this 不能为 NULL 时,我认为您不知道您在说什么。 this 为 NULL 是一种非常有效的情况,实际上可以故意使用它。如果您的应用程序只是分配了足够的内存,那么您可以将0xdefeca7ed 作为有效地址(在 x86 上)是正确的。不过,大多数非 x86 架构总是会在该指针处捕获 - 因为它未对齐。
                  • 澄清一下,this == NULL 可以用于非虚拟成员函数。我还没有看到会拒绝它的编译器。另见stackoverflow.com/questions/2679080/…
                  猜你喜欢
                  • 2010-09-29
                  • 1970-01-01
                  • 2017-09-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多