数组参数的健全性检查(strlen 等)
无法通过搜索找到答案(可能是错误的关键字),因此我正在创建一个新问题。
如何处理带有字符串参数的 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?
Couldn't find the answer by serching (maybe bad keywords), so I am creating a new question.
How do you handle parameter checking for dllexported methods with string parameters. The general rule is never trust user, but in reality? For example:
int foo(const char *bar)
{
if(!bar)
return FAIL;
???
}
Say the user of the library calls our function like:
foo(reinterpret_cast<char*>(0x00000008));
That should cause an AV on first:
strlen(bar);
Is there a way to guard against this? Correct approach to handle the error?
I know IsBadReadPtr is out of the question, because this function is in a class of dangerous and never to be used. But is there even a way I should and could handle the problem? I can't __declpec(dllexport) std::string, can I? Moreover, even if I would, the std::string has some sort of thread local storage or statics that cause access violations when used from different modules, as far as I know (caused by statics or different heaps?).
Is there a security risk in using these functions, stack overflow (R/E)IP overwrite, or is it just going to cause safe AV?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
到目前为止你只能照顾客户。如果他们将垃圾传递给你的函数,这是你的问题吗?
You can only babysit the client so far. If they pass garbage to your function, is it your problem?
“永远不要信任用户”这句话适用于不同的上下文 - 当“用户”是运行客户端程序并将数据发送到您正在编写的服务器程序的人时。
当“用户”是将您的代码用作库的人时,它应该更接近于:“始终编写代码,以便任何出错的事情确实都是用户的错;如果确实出错,则责怪用户”。 :)
"Never trust the user" is a saying that applies in a different context - when the "user" is a person who runs a client program and sends data to a server program that you are writing.
When the "user" is someone who is using your code as a library, it should be closer to: "Always write your code so that anything that goes wrong really is the user's fault; then blame the user if something does go wrong". :)
您的代码的责任是它自己的。客户有责任对自己要输入的数据进行错误检查。此外,您几乎无法确定有效指针的有效性。它指向非空吗?那么你能检查的就这么多了。
如果您担心安全问题,最好的办法就是 catch &处理/抛出异常和/或返回某种错误通知。指针本质上对它们有一定程度的危险。静态类型不会使您避免无效指针和访问冲突。
Your code's responsibility is its own. It is the client's responsibility to error check their own data that they're going to input. Additionally, there is little you can do to ascertain the validity of a valid pointer. Does it point to non-null? Then that's about as much as you can check.
If you are concerned about security, best you can do is catch & handle/throw exceptions and/or return error notifications of some sort. Pointers inherently have a degree of danger to them. Static-typing won't save you from invalid pointers and access violation.
您可能想要更改函数签名以包含字符串的长度。如果您正在读取字符串,则意味着如果字符串未正确以 NULL 终止,您的程序将不会继续读取该字符串之外的内容。如果您从函数写入字符串,那么您必须将缓冲区的长度作为参数,并且必须检查您写入的数据不得超过缓冲区可以容纳的数据。
沃利克的回应值得牢记。如果你的用户将垃圾传递到你的函数中,你只能做这么多来优雅地失败。
You might want to change the function signature to include the length of the string. If you're reading from the string, it'll mean that your program will not continue reading beyond the string if it was not correctly NULL terminated. If you're writing to the string from your function, then you must take the length of the buffer as a parameter and you must check that you do not write more data than the buffer can hold.
Wallyk's response is good to have in mind. If your user passes garbage into your function, there's only so much you can do to fail gracefully.
您可以使用操作系统函数来测试该地址是否映射到真实的 RAM 地址。但这个解决方案只解决了你的问题的一半。因为即使您知道内存地址是有效地址,您仍然不知道用户是否向您传递了现有但错误的地址。
他们可以简单地通过,
foo( (char*)&very_important_variable);
那么你能做什么呢?我猜你真的什么也做不了。
You can probably use OS functions to test if the address is mapped to real RAM address. But this solution only half-solved your problem. Because even you know the memory address is valid address, you still don't know whether users are passing existing but wrong address to you.
They can simply pass,
foo( (char*)&very_important_variable);
So what can you do then? You can't really do anything I guess.
该 DLL 是您仅在工作场所使用的 DLL 还是您在外部提供的 DLL?如果您仅在内部使用 std::string ,那么现在通常是安全的,因为您的所有代码都将构建并链接到同一个运行时库。否则 std::string 的危险是违反“单一定义规则”。与 boost::shared_ptr 等相同。但我们希望能够在我们的 C++ 库中使用它们。
通常防御非法 NULL 指针的最佳选择是断言,但如果您正在运输并且您的客户将不会获得无济于事的调试版本。
Is the DLL one that you use only in your workplace or one you ship externally? If it is one you use only internally using std::string in it is generally safe nowadays because all your code will be built and linked against the same runtime library. The danger in std::string otherwise is breaking the "one-definition rule". Same with boost::shared_ptr etc. But we want to be able to use those in our C++ libraries.
The best option usually to defend against illegal NULL pointers is an assert but if you are shipping and your clients will not get debug version that won't help.
上面的帖子已经说了大部分了。您基本上无法完全保护自己免受客户端代码的侵害。客户端可能只使用 OpenProcessMemory 并弄乱所有内部数据结构。
出于调试目的,您绝对可以使用 IsBadReadPoiner。这里唯一需要注意的是,一旦防护页面触发,您应该关闭(如果可能的话,优雅地)您的应用程序并重新开始。我喜欢做的一件事是在调试版本中放置一个又大又难看的消息框,如果 IsBadReadPoiner 被击中,该消息框就会弹出。
The above posts have said most of it. You basically can't fully protect yourself from client code. The client could just use OpenProcessMemory and just mess up all your internal data structures.
For debugging purposes you can definitely use IsBadReadPoiner. The only caveat here is that once the guard page triggered you kind of should shut down (gracefully if possible) your application and start over. One thing I like to do is put a big and ugly message box in the debug build that pops up if IsBadReadPoiner is hit.
我将反对这里流行的方法,说a)一个库应该对客户端的错误具有鲁棒性(在合理的范围内),b)当程序因代码中的AV而崩溃时,你看起来很糟糕,即使你可以指着手册中的一行并说“告诉你了!”。这是政治家的态度,而不是软件工程的态度...
不是Windows程序员,我无法给出具体的建议,但似乎至少返回代码或异常在这里是合适的。
I'll take against the popular approach here, saying that a) a library should be robust (to a reasonable extent) against client's errors, and b) when a program crashes with AV in your code, you look bad, even though you can point to a line in the manual and say "told ya!". This is a politician, rather than software engineering, attitude...
Not being Windows programmer I can't give a specific advise, but seems that at least a return code or an exception are appropriate here.
没有普遍适用的方法来防止这种情况发生; C/C++ 没有通用的指针验证工具。
正如您所展示的,您可以防范 NULL 指针,但不能(轻松)防范无效指针:
某些编译器/库/堆分配器类(可选)对所有堆内存进行零初始化,因此在此类平台上,“未初始化”堆包含零- 在此类平台上,上述内容对于防止使用未初始化的堆很有用。但这会影响性能,因此 C/C++ 标准不强制要求这样做。它对未初始化的临时对象(在堆栈上而不是堆上创建,因此不能由堆分配器进行零填充)没有帮助。
为了防止任何杂散指针,您必须实现自己的分配器,并提供钩子调用(上面的
isValid()
的更复杂的版本),允许对象实例查询有关分配器的状态“嘿分配器,你之前见过我的this
吗?”,并让基于堆栈的对象的所有类构造函数在创建/删除时使用“有效性跟踪器”(取消)注册对象实例地址。每次调用任何对象方法时都搜索堆分配器映射,这将是一种非常重量级的方法。我在生产实践中还没有看到这一点,尽管一些调试工具(我想到了 valgrind)具有分配器跟踪和一些有限的杂散检测。如果作为库的用户,你迫切地想要作弊,那么什么会阻止你这样做:
即使该方法会使用分配器验证
this
,它也会发现它是好的/已知的,但是你会怎么做?告诉它“真的是一个Y”吗?一般来说,图书馆的弹性在很大程度上取决于用户。多少保姆就太多了?
There is no generically applicable way to guard against this; C/C++ do not have a generic pointer-validation facility.
As you've shown, you can fence against NULL pointers but not (easily) against invalid ones:
Some compilers / libraries / heap allocator classes (optionally) zero-initialize all heap memory and hence on such platforms, "uninitialized" heap contains zeroes - on such platforms the above is useful to guard against the use of uninitialized heap. But that costs performance and therefore isn't mandated by the C / C++ standards. And it doesn't help with uninitialized temporary objects (that are created on the stack not on the heap, and hence cannot be zero-filled by the heap allocator).
To guard against any stray pointer, you'd have to implement your own allocators, and provide hook calls (a much more elaborate version of
isValid()
above) allowing an object instance to query the allocator state regarding "hey allocator have you seen mythis
before ?", and make all class constructors for stack-based objects (de)register the object instance address with the "validity tracker" on creation/deletion. It'd be a very heavyweight approach to search the heap allocator maps each time any object method is called. I've not seen this in production practice, though some debugging tools (valgrind comes to mind) have allocator tracking, and some limited stray detection.And if as a user of the library you desperately want to cheat, what stops you from doing:
Even if that method would validate
this
with the allocator, it'd find it ok/known but how would you tell that it "really is an Y" there ?In general, a library's resilience depends, to a large degree, on the user. How much nannying is too much ?