数组参数的完整性检查(strlen等)

时间:2010-12-08 09:13:11

标签: c++ c windows dll

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

如何使用字符串参数处理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?

9 个答案:

答案 0 :(得分:8)

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

答案 1 :(得分:5)

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

当“用户”是使用您的代码作为库的人时,它应该更接近:“总是编写代码,以便任何出错的东西确实是用户的错;然后如果有的话就责备用户错误”。 :)

答案 2 :(得分:2)

您的代码是自己的责任。客户有责任对自己要输入的数据进行错误检查。此外,您无法确定有效指针的有效性。它是否指向非null?那就是你可以查看的内容。

如果您担心安全问题,那么您最好能做到的就是捕获&amp;处理/抛出异常和/或返回某种类型的错误通知。指针固有地对它们有一定程度的危险。静态类型不会使您免于无效指针和访问冲突。

答案 3 :(得分:1)

您可能希望更改函数签名以包含字符串的长度。如果你正在读取字符串,这意味着如果没有正确地终止NULL,你的程序将不会继续读取超出字符串的内容。如果你到函数中的字符串,那么 必须 将缓冲区的长度作为参数,你必须检查你写的数据不比缓冲区能容纳的数据多。

Wallyk的回应很好。如果您的用户将垃圾传递给您的函数,那么您可以做的就是优雅地失败。

答案 4 :(得分:0)

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

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

那么你能做什么呢?我猜你真的不能做任何事情。

答案 5 :(得分:0)

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

通常用于防御非法NULL指针的最佳选择是断言但是如果您正在发货并且您的客户端将无法获得无效的调试版本。

答案 6 :(得分:0)

上述帖子大部分都是这样说的。你基本上不能完全保护自己免受客户端代码。客户端可以只使用OpenProcessMemory,只是弄乱所有内部数据结构。

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

答案 7 :(得分:0)

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

不是Windows程序员我不能给出具体的建议,但似乎至少在这里适当的返回代码或例外。

答案 8 :(得分:-1)

没有一般适用的方法来防范这种情况; 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吗?并且让所有基于堆栈的对象(de)的类构造函数在创建/删除时用“有效性跟踪器”注册对象实例地址。每次调用任何对象方法时,搜索堆分配器映射都是一种非常重要的方法。我没有在生产实践中看到这一点,虽然有些调试工具(想到valgrind)有分配器跟踪和一些有限的杂散检测。

如果作为图书馆的用户,你迫切想要作弊,那么阻止你做什么:

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

即使该方法使用分配器验证this,它也会发现它已经确定/已知但是你怎么会告诉它“它真的是Y”呢?

通常,图书馆的弹性在很大程度上取决于用户。多少保姆太多了?