【发布时间】:2013-04-21 19:13:15
【问题描述】:
这是一个关于如何在简单的一点内拥有“腰带和吊带”安全性的问题 C 代码。一个老生常谈的问题是如何确保 可以将数据移动到某个被调用函数内的缓冲区中,而不必担心堆 返回后内存损坏。上面写了一些很棒的东西 关于这个话题的网站,至少对我来说,仍然不清楚我们在哪里得到真正的 总安全。所以我写了以下内容:
/*********************************************************************
* The Open Group Base Specifications Issue 6
* IEEE Std 1003.1, 2004 Edition
*********************************************************************/
#define _XOPEN_SOURCE 600
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int use_buffer( const char *strin, char **strout, size_t bufsize ) {
size_t len;
int result = -1;
printf ( "in use_buffer() we have address of strout = %p\n", &strout );
printf ( " and that contains an address of %p\n", strout );
printf ( " which points to a buffer address %p\n", *strout );
/* check for null data */
if ( ( strin == NULL ) || ( *strout == NULL ) )
return result;
/* check for zero length data */
if ( strlen(strin) == 0 )
return result;
/* ensure we have a non-zero size buffer to write to ?
* belt and suspenders safety here is not assured. We have
* no way to know if the calling routine actually did
* allocate memory of size bufsize.
*/
len = strlen(strin);
if ( bufsize < len )
return result;
strncpy ( *strout, strin, len );
return len;
}
int main ( int argc, char *argv[] ) {
char *some_buffer;
int retval;
size_t buflen;
if ( argc < 2 ) {
printf ( "usage: %s somestring\n", argv[0] );
return ( EXIT_FAILURE );
}
buflen = (size_t) ( 4 * 4096 );
some_buffer = calloc( buflen, sizeof( unsigned char) );
if ( some_buffer == NULL ) {
perror ( "Could not calloc a 16Kb byte buffer." );
return ( EXIT_FAILURE );
}
printf ( "main() has a 16Kb buffer ready at address = %p\n",
&some_buffer );
retval = use_buffer( argv[1], &some_buffer, buflen );
if ( retval > 0 )
printf ( "Maybe we have %i bytes copied into a buffer.\n", retval );
free ( some_buffer );
some_buffer = NULL; /* belt and suspenders */
return ( EXIT_SUCCESS );
}
编译并运行它,我看到了:
$ ./use_buffer "foo of the bar"
main() has a 16Kb buffer ready at address = ffffffff7ffff710
in use_buffer() we have address of strout = ffffffff7ffff630
and that contains an address of ffffffff7ffff710
which points to a buffer address 100101440
Maybe we have 14 bytes copied into a buffer.
其中一个地址确实看起来不正确。其中之一就是不一样。
真的,很难知道为什么前三个地址在其他一些内存区域中离得很远,而最后一个看起来可能是一些本地堆内存?
以上方法绝对安全带吊带吗?我的意思是该函数将使用我们知道已预先分配的缓冲区,并且该函数无法将其搞砸。我怀疑该函数是否可以对存储在 strout 中的地址调用 free()。这就像用房间钥匙入住酒店然后放火烧房间一样。站在里面的时候。我想这可以做到..但会很疯狂。
所以这里有两个问题:(1)函数有没有办法验证分配的缓冲区大小?即使方法是触发内存违规。然后(2)将空指针传递给函数然后允许函数根据需要调用/分配缓冲区并传回地址是否安全?
我怀疑(2)已经被打死了,答案是“安全无保障”。 (旁注:顺便说一句,该死的好电影。)
考虑一下这个代码位:
/*********************************************************************
* The Open Group Base Specifications Issue 6
* IEEE Std 1003.1, 2004 Edition
*********************************************************************/
#define _XOPEN_SOURCE 600
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int bad_buffer( const char *strin, char **strout ) {
size_t len;
int result = -1;
char *local_buffer;
/* check for null data */
if ( strin == NULL )
return result;
/* check for zero length data */
len = strlen(strin);
if ( len == 0 )
return result;
/*
* safety not guaranteed ?
*/
local_buffer = calloc( len+1, sizeof( unsigned char) );
printf ( " in bad_buffer() we have local_buffer at = %p\n",
&local_buffer );
strncpy ( local_buffer, strin, len );
*strout = local_buffer;
return len;
}
int main ( int argc, char *argv[] ) {
char *some_buffer;
int retval;
size_t buflen;
if ( argc < 2 ) {
printf ( "usage: %s somestring\n", argv[0] );
return ( EXIT_FAILURE );
}
retval = bad_buffer( argv[1], &some_buffer );
printf ( "in main() we now have some_buffer at addr %p\n", some_buffer );
if ( retval > 0 )
printf ( "Maybe we have %i bytes copied into a buffer.\n", retval );
printf ( "main() says the buffer contains \"%s\"\n", some_buffer );
free ( some_buffer ); /* really ? main() did not allocate this !? */
some_buffer = NULL;
return ( EXIT_SUCCESS );
}
当我编译并运行时,我看到了:
$ ./bad_buffer foobar
in bad_buffer() we have local_buffer at = ffffffff7ffff6b0
in main() we now have some_buffer at addr 100101330
Maybe we have 6 bytes copied into a buffer.
main() says the buffer contains "foobar"
这里似乎有些诡异。该函数执行了 calloc,然后将缓冲区的地址填充到 strout 内的 address 中。所以 strout 是一个指向指针的指针,所以我很好。让我害怕的是,函数分配的内存在完成后没有权利或理由被认为是安全的,我们又回到了 main()。
所以问题号 (2) 代表“允许函数调用/分配所需的缓冲区是否有任何安全性?”
【问题讨论】:
-
@Cratylus:它在
use_buffer的顶部。 OP,不能保证您将始终收到动态分配的缓冲区的地址,该缓冲区靠近先前分配的缓冲区。你有一个指向指针的指针。 指针本身的地址通常与它所指的地址有很大不同 -
如果我这样做
char *p = malloc(size),p将分配自动存储持续时间,实际上,&p将是堆栈中某处的地址。p的 值 是动态分配缓冲区的地址(实际上是在堆上)。这就是它们不同的原因。但是,不需要解释;您永远不会,也不会一次,通过任何形式的语言保证动态分配的缓冲区的地址应该在彼此之间的一小段距离内。 -
你的前提是错误的。同样,指针本身的地址很可能不会位于作为其值的地址附近的任何位置。它们是两个完全不同的东西。如果我是编译器,我会说“哦,我需要在这个函数中分配一个指针。酷,我会把它放在堆栈上。哦,它是用
malloc的返回值初始化的。malloc会在运行时找到一个空位并返回一个地址。这将是指针的值”。malloc与 pointer 本身 的存储位置无关,只是用 value 对其进行初始化。 -
@paullanken:是的。如果动态分配的缓冲区是限定范围的,那么就不会有内存泄漏之类的事情(当然,C 中的编程也会变得更加困难。如何在不到处复制大副本的情况下共享内存?)当你通过指向或来自函数的指针 复制(C 中的所有内容都按值传递)。指针的值,即它所指的,当然是不变的,但是指针本身就是一个副本。
-
我很少需要将一个字符串复制到另一个字符串的中间。如果我愿意,我会使用 memcpy()。请记住:
strncpy(dst,src,len)总是触及len字节。如果src的实际长度恰好小于这个值,它将用 NUL 填充其余部分。很少需要 Strncpy()。