1

这是一个关于如何在一段简单的 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 中的地址中。所以 strout 是一个指向指针的指针,所以我很好。让我害怕的是,函数分配的内存在完成后没有权利或理由被认为是安全的,我们又回到了 main()。

所以问题号(2)代表“允许函数调用/分配所需的缓冲区有什么安全性吗?”

4

2 回答 2

3

其中一个地址确实看起来不正确。其中之一就是不一样。

真的,很难知道为什么前三个地址在其他一些内存区域中离得很远,而最后一个看起来可能是一些本地堆内存?

some_buffer(或其别名strout)是存储在堆栈中的局部变量,main并指向堆中的地址。所以它们是不同内存区域的地址

于 2013-04-21T19:25:37.653 回答
1
$ ./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.

这里有 3 个第一个值在堆栈中。查看您的代码,中间的实际上是该函数的本地参数的地址,而 1st 和 3rd 是main(). 请注意堆栈是如何向下增长的,因此它位于高地址,并且被调用的函数参数低于调用函数的变量。

第 4 个值是一种特殊情况,因为它是argv字符串的地址。这些字符串要么是全局变量(在它们自己的程序地址空间部分中),要么它们甚至可以位于操作系统特定的特殊地址上,而不靠近其他任何东西。


$ ./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"

在这里,第一个地址在堆栈中,是函数中局部变量的地址。第二个地址是堆中的内存,指针的main()


是的,一目了然,您的代码是安全的。您似乎将指针值与指针变量的地址混淆了。考虑一下:

char *p1 = malloc(10);
char *p2 = p1;
printf("%p %p %p %p\n", &p1, &p2, p1, p2);

上面将打印类似

ffffffff7ffff710 ffffffff7ffff702 100101440 100101440

首先是变量的地址p1,它是这里和堆栈中的局部变量。第二个是 的地址p2,也是局部变量和堆栈中的地址。然后最后两个地址作为malloc调用的返回值,分配给p1并复制到p2. free无论您将地址传递多少次,或者即使您丢失了地址(在这种情况下您有内存泄漏),该分配的块都将一直保留到您。当你free这样做时,任何仍然指向该区域的指针都会变成悬空指针,并且不应该被取消引用。

于 2013-04-21T19:31:57.697 回答