【问题标题】:Heap buffer overflow in stack function堆栈函数中的堆缓冲区溢出
【发布时间】:2020-08-20 17:04:50
【问题描述】:

所以我创建了一个程序,该程序使用称为堆栈的结构来创建堆栈及其所有操作。

结构:

typedef struct {
        int *v;     /* contents of the stack */
        int cap;    /* capacity of v, i.e. how many elements can fit in v */
        int sz;     /* number of elements currently stored in v */
    } stack;

程序运行良好,但是当我使用 fsantize 时,它说 Push 函数中的堆上存在缓冲区溢出,我不明白为什么,因为我重新分配了我需要的字节并释放了我不需要的字节.

程序:

#include <stdio.h> 
#include <stdlib.h>
#include <string.h>

typedef struct {
        int *v;     /* contents of the stack */
        int cap;    /* capacity of v, i.e. how many elements can fit in v */
        int sz;     /* number of elements currently stored in v */
    } stack;

void init(stack * s)
{
    s->v = (int*) calloc(4,sizeof(int));
    s->cap = 4;
    s->sz = -1;
}

int is_empty(stack * s)
{
    if (s->sz == -1)
        return 1;
    else
        return 0;
}

void push(stack * s, int e)
{
    if (s->sz+1 <= s->cap)
    {
        s->sz++;
        s->v[s->sz] = e;
    }
    else
    {
        int *nv;
        s->cap++;
        s->sz++;
        nv = (int*) realloc(s->v, sizeof(int)*s->cap);
        free(s->v);
        s->v = nv;
        s->v[s->sz] = e;
    }
}

int pop(stack * s)
{
    if (is_empty(s) == 0)
    {
        int top = s->v[s->sz];
        s->sz--;
        return top;
    }
    else
    {
        printf("Impossible the stack isn't empty\n");
        return 0;
    }

}

void destroy(stack * s)
{
    //frees the stack bytes that were allocated
    free(s->v);
    free(s);
}

int main()
{
    int i;
    stack *pilha = (stack*) malloc(sizeof(stack));
    init(pilha);
    if (is_empty(pilha) == 1)
        printf("The stack is empty\n");
    pop(pilha);
    for (i = 0; i<=4;i++)
        push(pilha,i);
    push(pilha,5);
    printf("The top is:%d\n",pilha->v[pilha->sz]);
    if (is_empty(pilha) == 0)
        printf("The stack isn't empty\n");
    destroy(pilha);
    return 0;
}

【问题讨论】:

    标签: c struct stack dynamic-memory-allocation function-definition


    【解决方案1】:

    函数push无效。

    if 语句中的这个条件

    if (s->sz+1 <= s->cap)
    

    可能是未定义行为的原因。为简单起见,我们假设s-&gt;cap 等于1。因此,您可以只推送一个元素而无需调整动态分配的数组的大小。所以在推送一个新值之后s-&gt;sz 将等于0。如果不调整数组大小,您可能无法再推送一个新值。但是,if 语句中的条件将评估为 true,并且您将写入分配的数组之外的内存。

    还有这个代码sn-p

        nv = (int*) realloc(s->v, sizeof(int)*s->cap);
        free(s->v);
    

    无效。在 realloc 调用成功的情况下,s->v 指向的内存被释放(或重新使用)。所以再次调用 free 将调用未定义的行为。即是否会尝试释放已重新分配的内存或释放新分配的内存。

    函数push可以定义例如如下方式

    int push( stack *s, int e )
    {
        int success = 0;
    
        if ( ( success = s->sz+1 < s->cap ) )
        {
            s->v[++s->sz] = e;
        }
        else
        {
            int *nv = realloc( s->v, sizeof( int ) * ( s->cap + 1 ) );
            success = nv != NULL;
    
            if ( success )
            {
                s->v = nv;
                ++s->cap;
                s->v[++s->sz] = e;
            }
        }
    
        return success;
    }
    

    但无论如何最好将数据成员sz的初始值设置为0。在这种情况下,数据成员将反映堆栈的当前大小。

    函数pop的返回值不明确。返回值 0 可以是存储在堆栈中的有效值。此外,该功能不应发出任何消息。函数的调用者将决定是否发出消息(如果有的话)。

    也不需要动态分配stack类型的对象本身。它可以具有自动存储时长,并且是一个局部变量。

    当初始化堆栈的函数还有第二个参数允许指定创建的堆栈的容量而不是使用幻数4时会好得多。

    下面是一个演示程序,展示了如何定义堆栈及其函数。

    #include <stdio.h> 
    #include <stdlib.h>
    
    typedef struct 
    {
        int *v;     /* contents of the stack */
        size_t cap;    /* capacity of v, i.e. how many elements can fit in v */
        size_t sz;     /* number of elements currently stored in v */
    } stack;
    
    int init( stack * s, size_t capacity )
    {
        s->sz  = 0;
        s->cap = 0;
    
        s->v = calloc( capacity, sizeof( int ) );
    
        int success = s->v != NULL; 
    
        if ( success )
        {
            s->cap = capacity;;
        }       
    
        return success;
    }
    
    int is_empty( const stack *s )
    {
        return s->sz == 0;
    }
    
    int push( stack *s, int e )
    {
        int success = 0;
    
        if ( ( success = s->sz < s->cap ) )
        {
            s->v[s->sz++] = e;
        }
        else
        {
            int *nv = realloc( s->v, sizeof( int ) * ( s->cap + 1 ) );
            success = nv != NULL;
    
            if ( success )
            {
                s->v = nv;
                ++s->cap;
                s->v[s->sz++] = e;
            }
        }
    
        return success;
    }
    
    int pop( stack *s, int *value )
    {
        int success = !is_empty( s );
    
        if ( success )
        {
            *value = s->v[--s->sz];
        }
    
        return success;
    }
    
    void destroy( stack *s )
    {
        free( s->v );
        s->v = NULL;
        s->cap = 0;
        s->sz = 0;
    }
    
    int main( void )
    {
        stack pilha;
        init( &pilha, 4 );
    
        if ( is_empty( &pilha ) )
        {
            printf( "The stack is empty\n" );
        }        
    
        const int N = 5;
    
        for ( int i = 0; i < 5; i++ )
        {
            push( &pilha, i );
        }
    
        push( &pilha, N );
    
        while ( ! is_empty( &pilha ) )
        {
            int value;
            pop( &pilha, &value );
            printf( "the current top value is %d\n", value );
        }
    
        destroy( &pilha );
    
        if ( is_empty( &pilha ) )
        {
            printf("The stack isn't empty\n");
        }
    
        return 0;
    }
    

    程序输出是

    The stack is empty
    the current top value is 5
    the current top value is 4
    the current top value is 3
    the current top value is 2
    the current top value is 1
    the current top value is 0
    The stack isn't empty
    

    【讨论】:

    • 实际上并不是if 语句中的条件调用了未定义的行为。
    • @MarcoBonelli 错误条件是调用未定义行为的原因。
    • 没错,新的措辞要好得多:)
    【解决方案2】:

    这一行:

    if (s->sz+1 <= s->cap)
    

    包含一个逻辑错误:如果s-&gt;sz+1 == s-&gt;cap 您需要更多空间。例如,如果s-&gt;cap4,则只有4 元素的空间(索引从03),但对于s-&gt;sz == 3,您输入if,结果为:

    s->sz++;         // 4
    s->v[s->sz] = e; // s->v[4] overflow!
    

    正确的检查方法是if (s-&gt;sz+1 &lt; s-&gt;cap),甚至先增加值:

    s->sz++;
    
    if (s->sz < s->cap) {
        // ...
    

    这个:

    nv = (int*) realloc(s->v, sizeof(int)*s->cap);
    free(s->v);
    s->v = nv;
    

    也是错的。首先,您假设realloc() 分配新内存并且您需要free() 旧缓冲区:您不需要,realloc() 会在需要时为您执行此操作。其次,您假设 realloc() 不会失败(正如您在代码中的其他任何地方所做的那样,malloc()calloc() 等)。第三,您正在强制转换返回值(再次与您在代码中的其他任何地方所做的一样),这是您不应该的(请参阅Do I cast the result of malloc?)。

    你应该做的是:

    nv = realloc(s->v, sizeof(int)*s->cap);
    if (nv == NULL) {
        // Handle error, abort execution.
    }
    
    s->v = nv;
    

    检查if (nv == NULL) 应该在每次调用malloc()realloc()calloc() 之后进行。

    【讨论】:

    • 我按照你告诉我的想法做了,然后重新输入推送到这个void push(stack * s, int e) { s-&gt;sz++; if (s-&gt;sz &lt; s-&gt;cap) { s-&gt;v[s-&gt;sz] = e; } else { int *nv; s-&gt;cap++; s-&gt;sz++; nv = realloc(s-&gt;v, sizeof(int)*s-&gt;cap); if (nv == NULL) {} s-&gt;v = nv; s-&gt;v[s-&gt;sz] = e; } },它仍然会导致缓冲区溢出,所以如果你能写出修改后的版本,我会非常感激,因为我仍然不明白我的错误是什么
    • @MartimCorreia 在第二种情况下,您将s-&gt;sz 增加两次,从else 中删除s-&gt;sz++
    • 是的,我就是这么做的,我只是在我想睡觉的时候才明白你的意思,谢谢伙计
    猜你喜欢
    • 2017-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多